feat: brain-fidelity recall substrate (PPR + Gaps 1/3/4/5)#39
Conversation
The recall path already computed per-seed activation weights from vector relevance (core stageActivate: seedActivations from m.relevance, plus entity and project seeds) and passed them through the graph port, but the final layer dropped them. NeuralGraph.spreadActivation forwarded only seedNodeIds, and SpreadingActivation.activate hardcoded `reduce(activation = 1.0, ...)`. The graph therefore ran a uniform exponential-decay walk instead of personalized (seed-weighted) spreading activation — the PPR mechanism it was supposed to provide was unplugged one layer above the Cypher. Forward opts.seedActivations into activate() and bind it as the walk's initial value: `reduce(activation = coalesce($seedWeights[seedId], 1.0), ...)`. Seeds with no weight default to 1.0, so callers that pass no map are byte-identical to before; only callers already supplying weights (recall, pattern-completion) change behavior. Tests: unit (stub driver) asserts the weights reach the query params and the Cypher binds them — the exact regression — and runs anywhere; an integration test asserts a 9:1 seed-weight ratio yields a 9:1 activation ratio with a 1:1 unweighted control (requires NEO4J_TEST_READY).
Brain-fidelity recall-quality work (Gaps 3 + 1 from the architecture spec).
Both attack the move-zero finding that the associative layer is poisoned by
noise and duplicate episodes.
Gap 3 - salience-gated plasticity: scoreSalience already scored every episode
(acknowledgment 0.10, decision 0.90) but association edges used flat constants,
so a heartbeat and a decision wired equally strong. New salienceGate() modulates
edge strength by the WEAKEST-LINK salience of the two endpoints (a salient
partner cannot rescue a noise endpoint) and drops the edge below a low-cut
("no plasticity without salience"). Wired into createTemporalEdges using the
salience already in-hand at ingest. Backward-compatible: no salience map keeps
the flat 0.3 behavior byte-identical.
Gap 1 - pattern separation (pragmatic tier): ingest inserted unconditionally,
so repeated turns (heartbeats, re-emitted tool output) piled up as distinct
rows and interfered at recall. New findNearDuplicate() checks the incoming
embedding against recent same-session episodes; on a hit (cosine >=
dedupeThreshold, default 0.95) the existing memory is reinforced via
recordAccess instead of storing a redundant copy. Conservative, configurable
(MemoryOptions.dedupeThreshold; <=0 disables), and non-destructive.
Pure primitives (salienceGate, findNearDuplicate) are unit-tested; the ingest
merge + reinforcement is covered end-to-end. core 509/509, tsc clean.
Deferred (storage boundary): gating co-recalled edge strength
(upsertCoRecalled) and dream-cycle replay/topical weights; the k-WTA
separation code (Gap 1 tier 2).
Extends salience-gated plasticity to the two remaining frequency-weighted edge sites — the Hebbian co-recall layer that move-zero identified as the poisoned one (heartbeats co-recalled repeatedly, accumulating high strength), and the dream-cycle hippocampal-replay edges. Rather than thread a strength delta through the upsertCoRecalled storage signature (which on PostgREST means an engram_upsert_co_recalled RPC + a schema migration on prod), the gate is applied at the edge-creation DECISION: a co-recall edge whose least-salient endpoint is noise is simply not created or strengthened. Same un-poisoning, zero schema/deploy risk, identical on both storage adapters. - createCoRecalledEdges: accepts optional per-memory salience and skips a pair when salienceGate() drops it (weakest-link; unknown salience -> 0.5, ungated). - reconsolidation: looks up encoding salience for the co-recalled episodes and passes it through. Non-episode (curated semantic/procedural) stay ungated. - dream-cycle replay: the seed query returns m.salience and the TOPICAL replay edge weight is salience-gated, so a replay edge between noise seeds forms weakly or not at all. Backward compatible: every existing caller passes no salience -> 0.5 -> unchanged. core 514/514, tsc clean. Deferred: SQL discoverTopicalEdges + causal/light/deep-sleep weights (lower, diffuse value); the graded strength-delta refinement via the RPC.
…s 4 + 5) Both wire signals that were already computed but never used at retrieval. Gap 4 - context reinstatement: the sensory buffer already tracks the topics primed by recent turns (the active conversational context), but recall seeded spreading activation only from query relevance + entities + project. Now the primed topics are looked up and added as soft context seeds (weight 0.45, below entity/project seeds), so the SAME query completes to different associations depending on what is currently in focus (encoding specificity). Best-effort and query-dominant. Gap 5 - lateral inhibition / fan effect: dream-cycle's betweenness pass already flags high-centrality hubs with isBridge, but recall ignored it. Activated nodes are now down-weighted (x0.5) when isBridge, applied before the primary/faint threshold split so a generic hub can fall out of the result set entirely. No-op when betweenness was never computed (no isBridge property present). Both land in stageActivate; the engine passes sensory.getPrimed() through. Backward compatible (no context topics -> unchanged; no isBridge -> unchanged). 4 new tests, core 518/518, tsc clean. Deferred: degree-based divisive normalization in the activation Cypher (a heavier, always-available alternative to the isBridge gate); per-topic boost weighting of context seeds.
|
Caution Review failedPull request was closed or merged during review WalkthroughAdds salience-gated plasticity and near-duplicate ingestion, gates temporal/co-recall/topical edges by endpoint salience, enables per-seed personalized spreading activation, and reinstates sensory context and hub inhibition during recall. ChangesSalience-gated memory associations and semantic deduplication
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/consolidation/dream-cycle.ts (1)
463-476:⚠️ Potential issue | 🟡 MinorAlign the missing-salience fallback in dream-cycle with salienceGate semantics.
Non-episode Neo4j
:Memorynodes (e.g., semantic/procedural) are created withoutsalience, so the 0.5 fallback is expected—butsalienceGateonly drops edges whenmin(salienceA, salienceB) <= 0.15(DEFAULT_LOW_CUT). Since episode DB default is 0.3, both 0.3 and 0.5 bypass “gated out”; 0.5 mainly boosts TOPICAL edge weights (0.5 => full baseStrength, 0.3 => ~0.6×). Switch the fallback to 0.3 or update the comment to describe this weighting effect, not “never gated out.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/consolidation/dream-cycle.ts` around lines 463 - 476, The seeds construction in dream-cycle uses a 0.5 fallback for missing m.salience which wrongly claims it "never gated out" and skews TOPICAL weighting; change the fallback to 0.3 to match episode DB default and salienceGate semantics (DEFAULT_LOW_CUT) or alternatively update the comment to explain that 0.5 increases TOPICAL edge weights rather than preventing gating; modify the code that builds seeds (the seedResult.map → seeds block) to use 0.3 as the default salience value (or adjust the inline comment) and ensure this aligns with salienceGate/DEFAULT_LOW_CUT behavior referenced elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/core/src/consolidation/dream-cycle.ts`:
- Around line 463-476: The seeds construction in dream-cycle uses a 0.5 fallback
for missing m.salience which wrongly claims it "never gated out" and skews
TOPICAL weighting; change the fallback to 0.3 to match episode DB default and
salienceGate semantics (DEFAULT_LOW_CUT) or alternatively update the comment to
explain that 0.5 increases TOPICAL edge weights rather than preventing gating;
modify the code that builds seeds (the seedResult.map → seeds block) to use 0.3
as the default salience value (or adjust the inline comment) and ensure this
aligns with salienceGate/DEFAULT_LOW_CUT behavior referenced elsewhere.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7b4b2120-f995-4077-a9c7-cdb1cc76f283
📒 Files selected for processing (17)
packages/core/src/consolidation/dream-cycle.tspackages/core/src/ingestion/near-duplicate.tspackages/core/src/ingestion/plasticity.tspackages/core/src/memory.tspackages/core/src/retrieval/engine.tspackages/core/src/retrieval/reconsolidation.tspackages/core/src/retrieval/spreading-activation.tspackages/core/src/systems/association-manager.tspackages/core/test/ingestion/near-duplicate.test.tspackages/core/test/ingestion/plasticity.test.tspackages/core/test/memory.test.tspackages/core/test/retrieval/spreading-activation-gaps.test.tspackages/core/test/systems/association-manager.test.tspackages/graph/src/neural-graph.tspackages/graph/src/spreading-activation.tspackages/graph/test/spreading-activation-ppr.test.tspackages/graph/test/spreading-activation-seed-binding.test.ts
The Gap 1 near-dup merge defaulted to 0.95-on, which collapsed the artificially near-identical embeddings used by e2e isolation tests (shared-axis fake embedder) and broke CI in @engram-mem/sqlite. More fundamentally, the merge's recall benefit is unvalidated — by the same gate that kept pattern completion opt-in, dedup should be opt-in until measured on real data. dedupeThreshold now defaults to 0 (off); set ~0.95 to enable. No ingest behavior change by default. The Gap 1 unit test opts in explicitly. sqlite 106/106, postgrest 71/71, core 518/518.
Production recall-quality changes from the brain-fidelity architecture work — a
brain-model memory that reconstructs the correct context from a partial cue,
reliably. Each commit wires a mechanism the architecture had the organ for but
was not using.
Commits
activation = 1.0, dropping the per-seed vector-relevance weights that corealready computed and passed through the port. Now bound:
reduce(activation = coalesce($seedWeights[seedId], 1.0), ...).near-identical recent turns reinforce the existing memory instead of storing a
redundant copy (pattern separation); temporal edges are gated by the salience
already scored at ingest (weakest-link).
Hebbian co-recall layer (the poisoned one) and dream-cycle replay edges no
longer form between noise endpoints. Gated at the edge-creation decision, so no
storage schema/RPC change.
seeds from the active conversational context (primed topics), and
high-betweenness hub nodes (isBridge) are suppressed at recall.
Compatibility
Additive and backward-compatible: no salience map -> unchanged edge weights;
dedupeThreshold: 0-> unchanged ingest; no primed topics / no isBridge ->unchanged recall. PPR affects only the associations channel, not primary ranking.
Test plan