Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"test:dreams-compat": "node --import tsx/esm --test tests/dreams-compat.test.ts",
"test:dreams-anthropic": "node --import tsx/esm --test tests/dreams-anthropic.test.ts",
"test:dreams-bridge": "node --import tsx/esm --test tests/dreams-bridge.test.ts",
"test": "node --import tsx/esm --test --test-concurrency=1 tests/integration.test.ts tests/llm-paths.test.ts tests/http-api.test.ts tests/cache.test.ts tests/embedding-migration.test.ts tests/outcome-revision.test.ts tests/reflection-revision.test.ts tests/selective-forgetting.test.ts tests/multi-device.test.ts tests/dream-runs.test.ts tests/dreams-compat.test.ts tests/dreams-anthropic.test.ts tests/dreams-bridge.test.ts tests/sentiment-tagging.test.ts tests/adaptive-sleep.test.ts tests/epistemic-confidence.test.ts tests/explainable-memory.test.ts tests/causal-graph.test.ts tests/abstractions.test.ts tests/bootstrap.test.ts",
"test": "node --import tsx/esm --test --test-concurrency=1 tests/integration.test.ts tests/llm-paths.test.ts tests/http-api.test.ts tests/cache.test.ts tests/embedding-migration.test.ts tests/outcome-revision.test.ts tests/reflection-revision.test.ts tests/selective-forgetting.test.ts tests/multi-device.test.ts tests/dream-runs.test.ts tests/dreams-compat.test.ts tests/dreams-anthropic.test.ts tests/dreams-bridge.test.ts tests/sentiment-tagging.test.ts tests/adaptive-sleep.test.ts tests/epistemic-confidence.test.ts tests/explainable-memory.test.ts tests/causal-graph.test.ts tests/abstractions.test.ts tests/bootstrap.test.ts tests/contested-conflicts.test.ts",
"test:multi-device": "node --import tsx/esm --test tests/multi-device.test.ts",
"test:sentiment-tagging": "node --import tsx/esm --test tests/sentiment-tagging.test.ts",
"test:adaptive-sleep": "node --import tsx/esm --test tests/adaptive-sleep.test.ts",
Expand All @@ -54,6 +54,7 @@
"test:causal-graph": "node --import tsx/esm --test tests/causal-graph.test.ts",
"test:abstractions": "node --import tsx/esm --test tests/abstractions.test.ts",
"test:bootstrap": "node --import tsx/esm --test tests/bootstrap.test.ts",
"test:contested-conflicts": "node --import tsx/esm --test tests/contested-conflicts.test.ts",
"benchmark:longmemeval": "node --import tsx/esm benchmarks/longmemeval/run.ts",
"benchmark:download": "node --import tsx/esm benchmarks/longmemeval/download.ts",
"benchmark:ingest": "node --import tsx/esm benchmarks/longmemeval/ingest.ts",
Expand Down
79 changes: 57 additions & 22 deletions src/sleep-cycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1041,27 +1041,61 @@ ${wrapUserContent('related_memories', relatedList || 'None')}`;
}
}

const winnerId = scoreA >= scoreB ? a.id : b.id;
const strategy = scoreA >= scoreB
? `multi_factor(A=${scoreA},B=${scoreB})`
: `multi_factor(A=${scoreA},B=${scoreB})`;
// Epistemic honesty on close calls: a gap of at most one point — one
// factor's worth of noise on this small integer scale — must not pick a
// winner. Mark both memories 'contested' instead. The threshold is an
// ABSOLUTE delta, not a ratio: ratios invert on quantized scores (a
// pure-noise 1-vs-0 reads as 100% while a well-evidenced 6-vs-5 reads
// as 17%) and are distorted by the mutual +100 supersession offset.
// Superseded rows never contest — supersession is an absolute signal
// and marking stale facts 'contested' would resurface them as live
// disputes. Contested is not terminal: Phase 5.12 promotes contested
// rows that later earn the corroboration bar, and a decisive
// re-resolution below clears the winner.
const scoreDelta = Math.abs(scoreA - scoreB);
const aSuperseded = Boolean((a.metadata as Record<string, unknown>)?.['_superseded']);
const bSuperseded = Boolean((b.metadata as Record<string, unknown>)?.['_superseded']);

if (scoreDelta <= 1 && !aSuperseded && !bSuperseded) {
await this.pool.query(
`UPDATE memory_conflicts SET resolved = true, resolution_strategy = $2, resolved_at = now()
WHERE id = $1`,
[conflict.id, `contested(A=${scoreA},B=${scoreB},delta=${scoreDelta})`],
);
await this.pool.query(
`UPDATE warm_tier SET epistemic_status = 'contested'
WHERE id = ANY($1) AND agent_id = $2`,
[[a.id, b.id], agentId],
);
} else {
const winnerId = scoreA >= scoreB ? a.id : b.id;
const strategy = `multi_factor(A=${scoreA},B=${scoreB})`;
const loserId = winnerId === a.id ? b.id : a.id;

const loserId = winnerId === a.id ? b.id : a.id;
// Mark conflict resolved
await this.pool.query(
`UPDATE memory_conflicts SET resolved = true, winner_id = $2, resolution_strategy = $3, resolved_at = now()
WHERE id = $1`,
[conflict.id, winnerId, strategy],
);

// Mark conflict resolved
await this.pool.query(
`UPDATE memory_conflicts SET resolved = true, winner_id = $2, resolution_strategy = $3, resolved_at = now()
WHERE id = $1`,
[conflict.id, winnerId, strategy],
);
// Reduce loser's confidence (agent-scoped for defense-in-depth)
await this.pool.query(
`UPDATE warm_tier SET confidence = LEAST(confidence, 0.2),
metadata = metadata || '{"_conflict_loser": true}'::jsonb
WHERE id = $1 AND agent_id = $2`,
[loserId, agentId],
);

// Reduce loser's confidence (agent-scoped for defense-in-depth)
await this.pool.query(
`UPDATE warm_tier SET confidence = LEAST(confidence, 0.2),
metadata = metadata || '{"_conflict_loser": true}'::jsonb
WHERE id = $1 AND agent_id = $2`,
[loserId, agentId],
);
// A decisive win over a previously-contested dispute clears the
// winner's contested badge (back to provisional — winning one
// adjudication is not the corroboration bar for 'established').
await this.pool.query(
`UPDATE warm_tier SET epistemic_status = 'provisional'
WHERE id = $1 AND agent_id = $2 AND epistemic_status = 'contested'`,
[winnerId, agentId],
);
}

resolved++;
}
Expand Down Expand Up @@ -1719,14 +1753,15 @@ Extract the cross-cutting principles.`;
private async phaseEpistemicPromotion(agentId: string): Promise<number> {
// Promote provisional → established when sufficient evidence exists.
// 'inferred' rows (sleep-cycle derivations and bootstrapped transfers,
// v3.12) earn promotion by the same evidence bar — without this they
// would be terminally second-class: excluded by every epistemic filter
// except 'all' with no path out regardless of corroboration.
// v3.12) and 'contested' rows (close-call conflict resolutions) earn
// promotion by the same evidence bar — without an exit path either
// status is terminally second-class: excluded by the default epistemic
// filters with no way out regardless of corroboration.
const { rowCount: promoted } = await this.pool.query(
`UPDATE warm_tier
SET epistemic_status = 'established', last_corroborated_at = now()
WHERE agent_id = $1
AND epistemic_status IN ('provisional', 'inferred')
AND epistemic_status IN ('provisional', 'inferred', 'contested')
AND evidence_count >= 3
AND id IN (
SELECT rl.warm_tier_id
Expand Down
Loading
Loading