Skip to content

feat: brain-fidelity recall substrate (PPR + Gaps 1/3/4/5)#39

Merged
muhammadkh4n merged 5 commits into
mainfrom
feat/brain-fidelity-recall
Jun 10, 2026
Merged

feat: brain-fidelity recall substrate (PPR + Gaps 1/3/4/5)#39
muhammadkh4n merged 5 commits into
mainfrom
feat/brain-fidelity-recall

Conversation

@muhammadkh4n

Copy link
Copy Markdown
Owner

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

  • fix(graph): bind PPR personalization vector — spreading activation hardcoded
    activation = 1.0, dropping the per-seed vector-relevance weights that core
    already computed and passed through the port. Now bound:
    reduce(activation = coalesce($seedWeights[seedId], 1.0), ...).
  • feat(core): salience-gated plasticity + near-duplicate merge (Gaps 1+3)
    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).
  • feat(core): salience-gate co-recalled + replay edges (finish Gap 3) — the
    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.
  • feat(core): context reinstatement + lateral inhibition (Gaps 4+5) — recall
    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

  • core 518/518, graph pure tests pass, tsc clean
  • unit + integration tests per mechanism (graph PPR integration test runs under NEO4J_TEST_READY)
  • validate on a real corpus before enabling aggressive thresholds in prod

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.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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.

Changes

Salience-gated memory associations and semantic deduplication

Layer / File(s) Summary
Salience-gating primitives
packages/core/src/ingestion/plasticity.ts, packages/core/test/ingestion/plasticity.test.ts
New salienceGate(baseStrength, sourceSalience, targetSalience, options?) function implements weakest-link rule: applies linear attenuation based on minimum endpoint salience, with configurable lowCut drop threshold and fullStrengthAt ramp interval; tested against noise-dropping, bounds, and clamping behavior.
Near-duplicate detection module
packages/core/src/ingestion/near-duplicate.ts, packages/core/test/ingestion/near-duplicate.test.ts
New cosineSimilarity and findNearDuplicate functions detect similar embeddings; threshold-based matching returns the highest-similarity candidate above the threshold or null; tested for correctness across orthogonal, opposite, identical vectors and invalid candidates.
Memory ingestion with deduplication and temporal edge gating
packages/core/src/memory.ts, packages/core/test/memory.test.ts
ingest() now applies near-duplicate detection before storing new episodes; matching episodes are reinforced via recordAccess instead of creating redundant entries. Temporal edges after insertion are salience-gated via a per-episode salience map passed to AssociationManager. Tests verify deduplication merges similar turns and respects disabled thresholds.
Salience-gated association edge creation
packages/core/src/systems/association-manager.ts, packages/core/test/systems/association-manager.test.ts
createTemporalEdges accepts optional salienceById map and gates edge strength per pair; createCoRecalledEdges gates pairwise co-recall edges by optional salience field on each memory. Edges with gated strength ≤ 0 are skipped. Tests verify noise suppression, full-strength retention, and backward compatibility.
Personalized spreading activation
packages/graph/src/spreading-activation.ts, packages/graph/src/neural-graph.ts, packages/graph/test/spreading-activation-seed-binding.test.ts, packages/graph/test/spreading-activation-ppr.test.ts
SpreadingActivation.activate() now accepts seedActivations?: Map<string, number> to enable per-seed initial walk weights. Cypher query uses coalesce($seedWeights[seedId], 1.0) instead of fixed 1.0 activation. Tests verify personalized seeding is threaded through to queries and integration tests confirm weighted neighbors receive expected asymmetric activation.
Recall with context reinstatement and hub inhibition
packages/core/src/retrieval/spreading-activation.ts, packages/core/src/retrieval/engine.ts, packages/core/test/retrieval/spreading-activation-gaps.test.ts
stageActivate accepts contextTopics?: string[], looks up their entity nodes in the graph, and injects them into seedActivations with soft CONTEXT_SEED_WEIGHT. After spreading activation, nodes flagged properties.isBridge === true are suppressed by HUB_INHIBITION_FACTOR. Engine now extracts primed topics from sensory and passes them to stageActivate. Tests verify context topics seed additional activation and bridge nodes are down-weighted.
Hippocampal replay with salience-gated topical edges
packages/core/src/consolidation/dream-cycle.ts
Dream-cycle Cypher now returns seed salience (defaulting to 0.5) alongside memoryId. Topical edge weight calculation applies salienceGate(overlapWeight, sourceSalience, targetSalience) and skips edges when gated strength ≤ 0.
Reconsolidation with salience-enriched co-recalled edges
packages/core/src/retrieval/reconsolidation.ts
stageReconsolidate now fetches episode salience for top-5 recalled episodes from storage and builds {id, type, salience} payloads before calling createCoRecalledEdges, enabling salience gating of co-recalled associations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble through the nodes at night,
I hush the noisy edges tight,
Where salience sleeps the weak links fade,
Duplicates folded in the glade,
Context hums and hubs bow light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: a brain-fidelity recall substrate improvement implementing PPR (Personalized PageRank) and addressing architectural gaps (1/3/4/5).
Description check ✅ Passed The description comprehensively explains the purpose, mechanisms, and compatibility considerations of the changes, directly relating to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/brain-fidelity-recall

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Align the missing-salience fallback in dream-cycle with salienceGate semantics.

Non-episode Neo4j :Memory nodes (e.g., semantic/procedural) are created without salience, so the 0.5 fallback is expected—but salienceGate only drops edges when min(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

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3daef and 2d85b7f.

📒 Files selected for processing (17)
  • packages/core/src/consolidation/dream-cycle.ts
  • packages/core/src/ingestion/near-duplicate.ts
  • packages/core/src/ingestion/plasticity.ts
  • packages/core/src/memory.ts
  • packages/core/src/retrieval/engine.ts
  • packages/core/src/retrieval/reconsolidation.ts
  • packages/core/src/retrieval/spreading-activation.ts
  • packages/core/src/systems/association-manager.ts
  • packages/core/test/ingestion/near-duplicate.test.ts
  • packages/core/test/ingestion/plasticity.test.ts
  • packages/core/test/memory.test.ts
  • packages/core/test/retrieval/spreading-activation-gaps.test.ts
  • packages/core/test/systems/association-manager.test.ts
  • packages/graph/src/neural-graph.ts
  • packages/graph/src/spreading-activation.ts
  • packages/graph/test/spreading-activation-ppr.test.ts
  • packages/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.
@muhammadkh4n muhammadkh4n merged commit 47a34b0 into main Jun 10, 2026
1 of 2 checks passed
@muhammadkh4n muhammadkh4n deleted the feat/brain-fidelity-recall branch June 10, 2026 11:12
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