Skip to content

fix(sleep): make epistemic promotion reachable — namespace spread never fired - #175

Merged
salishforge merged 1 commit into
mainfrom
fix/epistemic-promotion-reachable
Jul 27, 2026
Merged

fix(sleep): make epistemic promotion reachable — namespace spread never fired#175
salishforge merged 1 commit into
mainfrom
fix/epistemic-promotion-reachable

Conversation

@salishforge

Copy link
Copy Markdown
Owner

The defect

Phase 5.12 promotes a memory to established only when it has positive retrievals from ≥ 2 distinct namespaces. That can never happen:

  • every search path filters warm rows to the caller's namespace (memory-manager.ts:975, 1015, 1052, 1092)
  • retrieval_log records that same namespace
  • namespace is immutable after insert — no SET namespace anywhere in src/

A row's retrieval evidence is therefore always single-namespace, so COUNT(DISTINCT namespace) cannot reach 2.

Nothing has ever been promoted to established in production. That also silently starved bootstrapAgent(), which copies only established rows — so cross-agent transfer learning moves zero memories. Two shipped features, both inert.

Both had passing tests, because the suite hand-inserted retrieval_log rows carrying a namespace the system cannot produce for that row.

The fix

Replace the namespace clause with a day-spread test: positive retrievals on ≥ 2 distinct calendar days. Preserves the documented intent (corroboration from independent sessions rather than one burst) and is reachable.

Not redundant with evidence_count >= 3: that counter increments once per sleep cycle that saw feedback in the trailing 24h, so several cycles in one busy day can reach 3 without any independent confirmation. Both clauses now do distinct work.

Tests

  • Reachability guard — drives promotion through the real query() + feedback() paths, backdating only a timestamp. If promotion ever becomes unreachable again, this fails. (Its poll is anchored to a pre-call id watermark because retrieval logging is fire-and-forget — memory-manager.ts:942.)
  • Invariant pin — asserts no retrieval row carries a namespace differing from its memory, so a future reintroduction of namespace-spread fails loudly with the reason.
  • Flake fix — promotion tests now each run on their own agent. They all call engine.run(), which mutates every eligible row for that agent, so the shared agent let a later cycle retroactively promote an earlier test's fixture. Reproduced at ~1-in-4, then absent across 8 consecutive runs.
Suite Result
test:epistemic-confidence 33/33 (×8 runs)
test:bootstrap 22/22
test:contested-conflicts 9/9
test:integration 24/24
type-check / lint clean

Note

This is step 1 of the profiles-over-namespaces direction: it unblocks established status independent of which scoping model wins, and removes the last load-bearing use of namespace as a semantic signal.

🤖 Generated with Claude Code

https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru

…er fired

Phase 5.12 required positive retrievals from >= 2 distinct namespaces to
promote a memory to 'established'. That threshold is structurally
unsatisfiable: every search path filters warm rows to the caller's
namespace (memory-manager.ts:975/1015/1052/1092), retrieval_log records
that same namespace, and namespace is immutable after insert. A row's
retrieval evidence is therefore always single-namespace, so the count
could never reach 2.

Consequence: nothing was ever promoted to 'established' in production.
That also silently starved bootstrapAgent(), which copies only
'established' rows — so cross-agent transfer moved zero memories. Two
shipped features, both inert, both with passing tests: the suite
hand-inserted retrieval_log rows carrying a namespace the system cannot
produce for that row.

Replaces the clause with a day-spread test: positive retrievals on >= 2
distinct calendar days. This preserves the documented intent
(corroboration from independent sessions, not one burst) and is
reachable. It is not redundant with evidence_count >= 3 — that counter
increments once per sleep cycle that saw feedback in the trailing 24h,
so several cycles in one busy day can reach 3 without independent
confirmation.

Tests:
- New suite drives promotion through the real query() + feedback() paths
  and backdates only a timestamp, so it fails if promotion ever becomes
  unreachable again. Its poll is anchored to a pre-call id watermark
  because retrieval logging is fire-and-forget.
- Pins the invariant that made the old rule dead (no retrieval row may
  carry a namespace differing from its memory), so reintroducing a
  namespace-spread requirement fails loudly.
- Promotion tests now each run on their own agent. They all call
  engine.run(), which mutates every eligible row for the agent, so a
  shared agent let a later cycle retroactively promote an earlier test's
  fixture — a ~1-in-4 flake, reproduced and then absent across 8 runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru
@salishforge
salishforge merged commit 5fcff2d into main Jul 27, 2026
23 of 24 checks passed
@salishforge
salishforge deleted the fix/epistemic-promotion-reachable branch July 27, 2026 23:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant