Skip to content

Memory Hebbian

github-actions[bot] edited this page Aug 23, 2026 · 8 revisions

🧠 4-Layer Cognitive Graph

Biological Analog: The brain doesn't retrieve memories by content similarity alone. It uses associative networks (neurons that fire together wire together), temporal sequences (what happened next?), n-body event groupings (multi-entity episodes), and synaptic tag cross-capture (conceptually linked contexts). Spector Memory implements all four as graph structures that augment vector recall, supported by a central EntityDirectory.


Architecture Overview

graph TB
    subgraph "RecallPipeline"
        RP["Vector Search → 6-Phase Scoring → Top-K Seed Set"]
    end

    RP --> S5c["Step 5c: Hebbian<br/>Spreading Activation"]
    RP --> S5d["Step 5d: Temporal<br/>Chain Extension"]
    RP --> S5e["Step 5e: Entity<br/>Graph Traversal"]
    RP --> S5f["Step 5f: Cross-Capture<br/>Tag Traversal"]

    S5c --> M["Merge & Dedup → Re-sort → Final Top-K"]
    S5d --> M
    S5e --> M
    S5f --> M

    subgraph "Layer 1 — Hebbian Association"
        HG["HebbianGraphMemory<br/>CSR co-activation edges"]
        CAT["CoActivationRecordMemory<br/>Tag-level STDP learning"]
    end

    subgraph "Layer 2 — Temporal Causal"
        TC["TemporalChainMemory<br/>Session-linked sequences"]
    end

    subgraph "Layer 3 — Hyperedge Event-Episode"
        HEG["HyperEntityGraphMemory<br/>n-body entity groupings"]
        ED["EntityDirectory<br/>Identity companion registry"]
    end

    subgraph "Layer 4 — Cross-Capture Graph"
        CCG["CoActivationRecordMemory<br/>Tag co-occurrence traversal<br/>+ inverted index"]
    end

    S5c --> HG
    S5c --> CAT
    S5d --> TC
    S5e --> ED
    S5e --> HEG
    S5f --> CCG

    style RP fill:#4a90d9,color:white
    style M fill:#00b894,color:white
    style HG fill:#e74c3c,color:white
    style TC fill:#f39c12,color:white
    style HEG fill:#e91e63,color:white
    style CCG fill:#00b4d8,color:white
Loading

tip: Graceful Degradation Each graph step is additive — it can only ADD candidates to the result set, never remove. If a graph is null, empty, or throws an exception, the step is a no-op. Zero risk of regression.


Layer 1: Hebbian Association Graph

"Neurons that fire together, wire together." — Donald Hebb, 1949

How It Works

The Hebbian graph stores memory-to-memory edges with association weights. When two memories are co-ingested within the same session, their edge is strengthened. During recall, the graph discovers associated memories that pure vector similarity might miss.

graph LR
    A["Memory #42<br/>'database error'"] ---|"weight: 0.83<br/>co-ingested 5×"| B["Memory #87<br/>'connection pool'"]
    A ---|"weight: 0.47<br/>co-ingested 2×"| C["Memory #103<br/>'retry strategy'"]
    B ---|"weight: 0.63<br/>co-ingested 3×"| C

    style A fill:#e74c3c,color:white
    style B fill:#3498db,color:white
    style C fill:#2ecc71,color:white
Loading

Key Properties

Property Value
Max degree 24 neighbors per memory (configurable)
Edge format 12B — 4B neighbor + 4B weight + 2B lastCycle + 1B bridgeScore + 1B flags
Storage layout CSR (Compressed Sparse Row) — stores only actual edges, ~90% memory reduction vs. fixed-width
Eviction Multi-signal importance scoring (weight, recency, bridge centrality, redundancy, arousal, Zeigarnik)
Decay 0.9× multiplicative factor per consolidation cycle; bridge-protected edges floored instead of evicted
Spreading activation BFS with depth=2, attenuated by edge weight
Persistence Binary CSR file (V3 format, "HCSR" magic) with offset + edge segments

How It's Used

  • Ingestion: When memories are co-ingested within the same session, the bidirectional edge between them is strengthened
  • Recall: After the 6-phase scorer produces a seed set, the graph discovers associated memories via 2-hop BFS. These are added to the result set with 0.3× score attenuation

CoActivationTracker — Tag-Level Associations

Beyond memory-to-memory edges, the CoActivationTracker tracks tag co-occurrence patterns:

  • Undirected co-activation counts: How often two tags appear together in ingested memories
  • Directed STDP edges: Spike-Timing Dependent Plasticity — if tag A is consistently recalled before tag B, the directed edge A→B is strengthened, creating predictive associations

info: STDP — Spike-Timing Dependent Plasticity This creates predictive associations: "when I think of A, I should also think of B." The listener runs after each recall on a Virtual Thread, updating STDP weights with zero impact on recall latency.


Layer 4: Cross-Capture Graph

Synaptic tags that co-occur share Plasticity-Related Proteins, creating associative bridges between conceptually related memory traces. — Sajikumar & Frey, 2004

Neuroscience Basis

In neuroscience, Synaptic Tagging and Capture (STC) theory shows that synaptic tags form associative networks via three mechanisms:

  1. Cross-Tagging: Tagged synapses on the same neuron share PRPs (Plasticity-Related Proteins) across pathways
  2. Memory Co-allocation: Temporally proximate events with shared tags get co-allocated to overlapping neuronal populations
  3. Dendritic Clustering: Co-tagged synapses spatially cluster on dendrites for efficient PRP sharing

How It Works

The Cross-Capture Graph reuses the existing CoActivationRecordMemory tag co-occurrence data for graph traversal during recall. It adds a tag → memory inverted index that maps each synaptic tag to the set of memories carrying that tag.

Query: "How do we handle database connection pooling?"
Query tags: {database, connection-pool}

Cross-Capture Graph traversal:
  database → [timeout (0.87), retry (0.72), postgres (0.68)]
  connection-pool → [hikari (0.91), database (0.87), config (0.74)]

Discovered memories via related tags:
  → "HikariCP configuration best practices" (via hikari tag)
  → "retry backoff strategy for DB connections" (via retry tag)
  → "PostgreSQL timeout settings" (via postgres + timeout tags)

Key Properties

Property Value
Data source OffHeapPairTable (existing co-occurrence counts)
Inverted index ConcurrentHashMap<Long, CopyOnWriteArrayList<Integer>> (tag hash → memory slots)
Fan-factor attenuation 1/√(degree) — ACT-R spreading activation dilution
Attenuation factor 0.25× (configurable via spector.memory.cross-capture.attenuation)
Max tag neighbors 5 (configurable via spector.memory.cross-capture.max-tag-neighbors)
Max memories per tag 10 (configurable via spector.memory.cross-capture.max-memories-per-tag)
Index lifecycle Rebuilt on startup from memory headers; updated during ingestion
Kernel shape MemoryShape.HASHTABLE (ordinal 8) — honest shape per ADR-0009

How It's Used

  • Ingestion: Each extracted synaptic tag is indexed in the inverted index
  • Recall: Step 5f traverses the co-occurrence graph to find related tags, then looks up memories carrying those tags. Added to the result set with configurable attenuation
  • Graceful degradation: If the inverted index is empty or the cross-capture step throws, recall proceeds unchanged

What It Finds That Others Miss

Signal Hebbian Temporal Entity Cross-Capture
Different vocabulary, same concepts
Never co-ingested Maybe
Different sessions
No explicit entities

Layer 2: Entity-Relationship Graph

"What was the budget of the project managed by the person who met with me yesterday?"

The Entity Graph stores typed entities and typed relations extracted from ingested text. This enables multi-hop knowledge traversal that pure vector similarity cannot achieve.

Entity Extraction

Entities are extracted at ingestion time via the EntityExtractor SPI:

Mode Description
NONE (default) No extraction — entity graph features disabled
LLM Uses an LLM with a structured prompt to identify entities and relations
CUSTOM Any user-provided EntityExtractor implementation

Enable LLM entity extraction:

SpectorMemory.builder()
    .entityExtractionMode(EntityExtractionMode.LLM)
    .textGenerationProvider(provider)
    .build();

Open-Schema Type System

Spector uses an open-schema type registry — unlike traditional NER systems with fixed type sets, the entity graph accepts any type string the LLM identifies. Well-known types are pre-seeded for backward compatibility, but novel types (e.g., VEHICLE, REGULATION, RECIPE) are automatically registered on first use.

21 well-known entity types (pre-seeded):

Category Types
People & Org PERSON, ORGANIZATION, TEAM, ROLE
Projects PROJECT, PRODUCT, TASK
Knowledge CONCEPT, TOPIC, SKILL, DECISION
Technology TECHNOLOGY, TOOL, API, ARTIFACT
World EVENT, LOCATION, DATE_TIME
Process & Data PROCESS, METRIC, DOCUMENT
Catch-all OTHER

21 well-known relation types (pre-seeded):

Category Types
People MANAGES, REPORTS_TO, KNOWS, ASSIGNED_TO, AUTHORED
Work WORKS_ON, CREATED_BY, OWNS, IMPLEMENTS
Structure PART_OF, CONTAINS, DEPENDS_ON, USES
Causality CAUSES, BLOCKS, SUPERSEDES, PRECEDES, FOLLOWS
Location LOCATED_AT
General RELATED_TO, OTHER

tip: Dynamic Types If the LLM identifies an entity as SOFTWARE or a relation as DEPLOYED_ON, these are automatically registered in the type registry and stored as first-class types. No code changes or schema migrations required.

How It's Used

  • Ingestion: The LLM extracts entities from text → entities are added to the graph → entities are linked to their source memory (with weighted adjacency) → relations are added between entities
  • Recall: Entities are extracted from the query → matched in the graph by name → 2-hop BFS traversal → memory references collected → added to result set with 0.25× attenuation per hop × fan factor (1/√refCount, modeling ACT-R spreading activation dilution)
  • Consolidation: Entity–entity edges decay over reflection cycles. Entity→memory adjacency weights decay via LTD (Long-Term Depression, 0.95× per cycle, pruned below 0.2). Similar entity names are merged via Levenshtein distance. Fragmented adjacency blocks are compacted.
  • Reinforcement (LTP): When a memory re-mentions an already-linked entity, the adjacency weight is reinforced by +0.2 (Long-Term Potentiation) instead of creating a duplicate link.

Traversal

The entity graph supports typed BFS traversal with optional relation filtering:

Method Description
traverse(startEntity, filter, maxHops) BFS with optional relation type filter
collectMemories(startEntity, filter, maxHops) Collect all memory indices reachable within N hops
findEntity(name) Case-insensitive entity lookup
memoriesForEntity(entityId) All memory indices linked to an entity (unlimited)
fanFactor(entityId) Returns 1/√(refCount) for spreading activation dilution
memoryRefWeight(entityId, adjIdx) Read individual adjacency link weight
decayAdjacencyWeights(factor, threshold) LTD decay: multiply all weights, prune below threshold
compactAdjacency() Defragment adjacency segment, reclaim dead blocks

Off-Heap Layout

Entity nodes use a fixed 64-byte cache-line-aligned layout with a separate adjacency segment for entity→memory links:

Entity Node (64B, 8-byte aligned — V2):
  [type:4B][pad:4B][nameHash:8B]
  [adjOffset:4B][adjCount:4B][adjCapacity:4B][pad:4B]  ← pointer into adjacency segment
  [pad:4B][degree:4B][edgeStart:4B][pad:20B]

Entity Edge (16B — V2):
  [targetId:4B][relationType:4B][weight:4B]
  [lastCycle:2B][bridgeScore:1B][flags:1B]

Adjacency Entry (8B):
  [memIdx:4B][weight:4B]    ← weighted link to a memory slot

This design allows unlimited entity→memory associations (no fixed cap), with amortized O(1) growth via block doubling. Each entity starts with 8 adjacency slots and grows as needed. Max 48 entity–entity edges per entity (configurable), with multi-signal importance eviction.


Layer 3: Temporal Causal Chain

"What happened after the deployment failed?"

The Temporal Chain links memories ingested within the same session into a doubly-linked list, enabling temporal navigation — both forward ("what happened next?") and backward ("what led to this?").

graph LR
    M1["Memory #12<br/>'deploy started'"] --> M2["Memory #13<br/>'tests passed'"]
    M2 --> M3["Memory #14<br/>'deploy failed'"]
    M3 --> M4["Memory #15<br/>'rollback initiated'"]

    style M1 fill:#3498db,color:white
    style M2 fill:#2ecc71,color:white
    style M3 fill:#e74c3c,color:white
    style M4 fill:#f39c12,color:white
Loading

How It's Used

  • Ingestion: When a new memory is ingested within the same session, a bidirectional link is created to the previously ingested memory
  • Recall: For each seed result, the chain follows forward (3 hops) and backward (3 hops) to discover temporally adjacent memories. Forward links get 0.8× score, backward links get 0.7×
Method Description
followForward(startIdx, maxHops) "What happened next?"
followBackward(startIdx, maxHops) "What happened before?"
link(currentIdx, prevIdx, sessionId) Link two memories within a session

Persistence

All graph components persist alongside memory data in DISK mode:

Component File Format
HebbianGraphMemory hebbian.graph CSR V3 ("HCSR" magic) — offset segment + edge segment. Auto-migrates legacy V2 files.
CoActivationRecordMemory coactivation.dat Pair table + edge table + hash→tag map
EntityDirectory entity-directory.graph Entity node segment + memory link adjacency segment + name index ("EDIR" magic)
HyperEntityGraphMemory hyper-entity.graph Hyperedge segment + vertex segment + incidence index + incidence list ("HYEG" magic)
TemporalChainMemory temporal.chain Raw linked-list segment ("TPCH" magic, V2)
TypeRegistryMemory entity-types.reg / relation-types.reg Type name ↔ ID mappings

Memory Budget

Layer Per-Node/Edge At 100K memories At 1M memories
Hebbian CSR (L1) 4B offset + 12B × avg degree (~2) ~2.8 MB ~28 MB
CoActivation ~1MB total ~1 MB ~1 MB
Cross-Capture Inverted Index (L4) 12B per tag×memory entry ~2-5 MB ~20-50 MB
Entity Directory 64B node + 8B × adj ~7.2 MB ~72 MB
HyperEntity (L3) 32B hyperedge + 8B × vertices + 4B incidence ~5 MB ~50 MB
Temporal (L2) 16B 1.6 MB 16 MB
Total ~20-23 MB ~187-217 MB

tip: CSR Memory Savings The CSR (Compressed Sparse Row) Hebbian layout stores only actual edges rather than pre-allocating MAX_DEGREE slots per node. At observed average degree ~2.0, this reduces Hebbian memory by ~90% compared to the legacy fixed-width layout (292B/node → ~28B/node).

This is small compared to the vector store (100K × 768-dim × 1B quantized = 75 MB).


Why This Matters for AI Agents

Traditional vector search treats each query independently. The 4-layer graph creates emergent intelligence:

example: Scenario: Multi-Signal Recall 1. Agent queries "why is the app slow?" 2. Vector search → finds memory about "application latency" 3. Hebbian (Layer 1) → that memory was co-ingested with "connection pool settings" → adds it to results 4. Temporal (Layer 2) → follows the chain: connection pool → timeout config → retry backoff → adds all three 5. Hyperedge Event-Episode (Layer 3) → looks up entity "DatabaseService" in the EntityDirectory and set-intersects its incidence list in the HyperEntityGraph → recalls a hyperedge connecting {Alice, Project Alpha, DatabaseService} representing Alice's recent DB migration event → adds it 6. Cross-Capture (Layer 4) → the tag "latency" co-occurs frequently with "prometheus" and "p99" in the co-occurrence graph → discovers a memory about "Prometheus alerting thresholds for p99 latency" that shares no entities, vocabulary, or session with the seed set → adds it

The final result set contains memories that no single retrieval signal could have found alone.

Layer 4: Hyperedge Entity Graph

Collapsing pairwise relationships into n-body groupings.

The HyperEntityGraph replaces the traditional binary Entity Graph model by grouping related entities into hyperedges — single graph atoms that connect 3-8 entities with typed roles.

graph TD
    subgraph "Binary EntityGraph (3 edges)"
        A1["Alice"] -->|MANAGES| B1["Project Alpha"]
        A1 -->|WORKS_AT| C1["Spectrayan"]
        B1 -->|BELONGS_TO| C1
    end

    subgraph "HyperEntityGraph (1 hyperedge)"
        HE["Hyperedge\n{Alice, Project Alpha, Spectrayan}\ntype: MANAGES_AT"]
        A2["Alice\nrole: AGENT"] --- HE
        B2["Project Alpha\nrole: OBJECT"] --- HE
        C2["Spectrayan\nrole: LOCATION"] --- HE
    end

    style HE fill:#9b59b6,color:white
    style A1 fill:#3498db,color:white
    style B1 fill:#2ecc71,color:white
    style C1 fill:#e74c3c,color:white
    style A2 fill:#3498db,color:white
    style B2 fill:#2ecc71,color:white
    style C2 fill:#e74c3c,color:white
Loading

Key Properties

Property Value
Max vertices per hyperedge 3-8 entities with typed roles
Max hyperedges per entity 64 (participation cap with LRU eviction)
Complexity reduction 40-60% fewer graph atoms vs. binary decomposition
Traversal Set intersection: O(hyperedges_per_entity × avg_vertices)

Off-Heap Layout

Hyperedge Node (32B):
  [edgeId:4B][type:4B][weight:4B][vertexCount:4B]
  [vertexOffset:4B][memoryIdx:4B][timestamp:8B]

Vertex Entry (8B):
  [entityId:4B][roleId:4B]

Incidence Index (4B × entityCapacity):
  [hyperedgeListOffset] → per-entity list of participating hyperedges

Incidence List Entry (4B):
  [hyperedgeId]

Graduation as the Primary Graph Structure

The HyperEntityGraph has graduated to be the sole primary graph structure for entity and event representation, completely replacing the legacy binary EntityGraph (excised in P4 graduation #456). Rather than decomposing events into multiple binary relationships, all entities and their roles in a memory are grouped directly into hyperedges. This provides a 40-60% reduction in graph atoms and eliminates semantic loss from binary decomposition. Identity mapping (name-to-ID conversion) and fan-factor attenuation are handled by a dedicated EntityDirectory companion.


🔗 Graph Expansion Integration

Graph expansion occurs during the later stages of the RecallPipeline. After vector, BM25, and sparse search produce the initial top-K seed candidate set, Spector traverses Hebbian, temporal, and entity edges to expand the context.

Graph Expansion Parameters

  • graphExpansionThreshold (float): The minimum score threshold a neighbor memory must meet to be added to the result set. Neighbors below this threshold are pruned to prevent context dilution. Defaults to 0.7.
  • enableTrace (boolean): When enabled, the returned trace results will log the exact edge traversed (e.g., Hebbian co-activation, temporal causal step, or entity relation) for each expanded candidate.

Execution in RecallPipeline

  1. Seed Set Generation: Layer 1/2/3 queries produce a seed candidate list.
  2. Edge Traversal: For each seed candidate, Hebbian CSR edges, Temporal chains, and Entity relations are traversed.
  3. Threshold Gating: Neighbor memories are evaluated. If a neighbor's activation score (weighted by edge weight, importance, and temporal decay) exceeds graphExpansionThreshold, it is added to the result set.
  4. Deduplication & Sorting: Fused scoring merges the original and expanded candidates, sorting them before returning the final top-K.

Next Steps

  • :material-lightning-bolt: [[6-Phase Scoring Pipeline|Memory--Scoring-Pipeline]] — the SIMD hot-loop that produces the seed set
  • :material-sleep: [[Habituation — Anti-Filter Bubble|Memory--Habituation]] — preventing repetitive recall
  • :material-head-cog: [[Dopamine — Surprise Detection|Memory--Dopamine]] — auto-importance scoring
  • :material-brain: [[Architecture|Memory--Architecture]] — how graphs fit in the full pipeline

🏠 Home


Clone this wiki locally