Skip to content

feat(memory): implement SYNAPSE spreading activation retrieval over entity graph (#1888)#2080

Merged
bug-ops merged 2 commits intomainfrom
issue-1888-synapse
Mar 21, 2026
Merged

feat(memory): implement SYNAPSE spreading activation retrieval over entity graph (#1888)#2080
bug-ops merged 2 commits intomainfrom
issue-1888-synapse

Conversation

@bug-ops
Copy link
Owner

@bug-ops bug-ops commented Mar 21, 2026

Summary

Implement SYNAPSE spreading activation algorithm for entity graph retrieval. Activation scores propagate hop-by-hop from seed entities with exponential decay, lateral inhibition, and temporal recency weighting.

Key improvements:

  • Replaces fixed-depth BFS with activation-based ranking on large graphs
  • Multi-path convergence signal preserved via clamped-sum activation merge
  • Lateral inhibition prevents echo-chamber activation in dense clusters
  • Per-hop pruning enforces memory bound (max_activated_nodes)
  • MAGMA edge-type filtering preserves subgraph scoping (causal/semantic/temporal/entity)
  • 500ms timeout protection to bound context assembly latency

Architecture

New module: crates/zeph-memory/src/graph/activation.rs (290 LOC)

  • SpreadingActivation engine with spread() async method
  • ActivatedNode (entity_id, activation, depth)
  • ActivatedFact (edge + score)
  • Temporal recency formula: 1/(1 + age_days * temporal_decay_rate)

Integration:

  • graph_recall_activated() in retrieval.rs
  • recall_graph_activated() on SemanticMemory
  • fetch_graph_facts() in context assembly with config gate
  • GraphStore::edges_for_entities() batch query with SQLite bind-limit safety

Config: [memory.graph.spreading_activation]

  • enabled (bool, default false)
  • decay_lambda (f32, 0.0 < x ≤ 1.0, default 0.85)
  • max_hops (u32, >= 1, default 3)
  • activation_threshold (f32, default 0.1)
  • inhibition_threshold (f32, default 0.8)
  • max_activated_nodes (usize, default 50)

Testing

6309 tests, all passing (no changes to existing tests)
11 new unit tests covering:

  • Empty graph, single seed, linear chains
  • Diamond graph multi-path convergence
  • Dense cluster inhibition
  • Per-hop pruning enforcement
  • Temporal decay effects
  • Edge-type filtering
  • Batch seed loading

Clippy: zero warnings
Format: passes cargo +nightly fmt --check

Fixes

All architect recommendations and critic findings:

  • ✅ SA-INV-{01..09}: invariant compliance verified
  • ✅ CRIT-01: SQLite bind limit safety with MAX_BATCH_ENTITIES const
  • ✅ CRIT-02: Lateral inhibition checks both activation maps
  • ✅ CRIT-03: Per-hop pruning for max_activated_nodes bound
  • ✅ MAJOR-01: Clamped sum for multi-path convergence
  • ✅ MAJOR-02: 500ms timeout on spread()
  • ✅ MAJOR-03/SUGGEST-02: Config validation (decay_lambda, max_hops, thresholds)
  • ✅ SUGGEST-01: Depth tracking for ranking
  • ✅ SUGGEST-04: DRY seed-matching helper
  • ✅ SUGGEST-05: Tracing at seed init, per-hop, completion

References

…ntity graph (#1888)

Spreading activation engine propagates activation scores hop-by-hop from seed
entities over the knowledge graph with:
- Exponential decay per hop (decay_lambda parameter, default 0.85)
- Edge confidence weighting and temporal recency (reuses temporal_decay_rate)
- Lateral inhibition (nodes above inhibition_threshold stop receiving activation)
- Per-hop pruning to enforce max_activated_nodes bound (SA-INV-04)
- Clamped sum activation merge to preserve multi-path convergence signal
- MAGMA edge-type subgraph filtering for causal/semantic/temporal/entity edges
- Seed initialization from fuzzy entity search (DRY shared with graph_recall)

New module: crates/zeph-memory/src/graph/activation.rs (290 lines)
- SpreadingActivation engine with spread() async method
- ActivatedNode (entity_id, activation, depth) and ActivatedFact (edge + score)
- Temporal recency weighting formula: 1/(1 + age_days * temporal_decay_rate)
- Depth tracking for max-activation hop (used for result ranking)

Integration:
- graph_recall_activated() in retrieval.rs wraps spreading activation
- recall_graph_activated() on SemanticMemory delegates to retrieval
- fetch_graph_facts() in context assembly routes based on config.spreading_activation.enabled
- 500ms tokio::time::timeout() wraps spread() to bound latency

Config: [memory.graph.spreading_activation]
- enabled (bool, default false)
- decay_lambda (f32, validated 0.0 < x <= 1.0, default 0.85)
- max_hops (u32, validated >= 1, default 3)
- activation_threshold (f32, default 0.1)
- inhibition_threshold (f32, default 0.8, cross-validated > activation_threshold)
- max_activated_nodes (usize, default 50)

GraphStore::edges_for_entities() batch query
- Fetches all edges where source_entity_id IN (...) OR target_entity_id IN (...)
- SQLite bind limit safety: chunks at 490 IDs (safe under SQLITE_MAX_VARIABLE_NUMBER)
- Respects edge_types filter (MAGMA integration)
- Reuses valid_to IS NULL active-edge filter

CLI/TUI/Wizard:
- --init wizard includes spreading_activation toggle in graph-memory block
- /status reports "Graph recall: spreading activation (lambda=X.XX, hops=N)" when enabled
- Config defaults and migrations included

Testing: 11 unit tests covering:
1. Empty graph → empty results
2. Single seed, no edges → seed only
3. Linear chain A→B→C with max_hops=2 → all activated, scores decay
4. Linear chain with max_hops=1 → C not activated
5. Diamond graph → multi-path convergence signal preserved via clamped sum
6. Dense cluster → inhibition_threshold prevents runaway
7. max_activated_nodes pruning → per-hop top-N enforcement
8. Temporal decay → recent edges higher activation
9. decay_lambda=1.0 → full propagation without decay
10. Edge-type filtering → causal-only subgraph traversal
11. Batch seed loading → multiple entities initialized simultaneously

Fixes all architect recommendations and critic findings (SA-INV-{01..09},
CRIT-{01..03}, MAJOR-{01..05}, SUGGEST-{01..06}):
- Per-hop pruning (CRIT-03)
- Lateral inhibition checks both maps (CRIT-02)
- SQLite bind limit safety with MAX_BATCH_ENTITIES const (CRIT-01)
- Clamped sum for multi-path convergence (MAJOR-01)
- 500ms timeout protection (MAJOR-02)
- Config validation (MAJOR-03)
- Tracing at seed init, per-hop, and completion (SUGGEST-05)
- Depth tracking for ranking (SUGGEST-01)
- Shared seed-matching helper (SUGGEST-04)

Total: 6309 tests, all passing. No clippy warnings.
@bug-ops bug-ops enabled auto-merge (squash) March 21, 2026 09:41
@github-actions github-actions bot added documentation Improvements or additions to documentation memory zeph-memory crate (SQLite) rust Rust code changes core zeph-core crate labels Mar 21, 2026
@github-actions github-actions bot added enhancement New feature or request size/XL Extra large PR (500+ lines) labels Mar 21, 2026
@bug-ops bug-ops merged commit 6279509 into main Mar 21, 2026
25 checks passed
@bug-ops bug-ops deleted the issue-1888-synapse branch March 21, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core zeph-core crate documentation Improvements or additions to documentation enhancement New feature or request memory zeph-memory crate (SQLite) rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

research(memory): SYNAPSE spreading activation retrieval over entity graph

1 participant