Skip to content

🧠 3-Layer Cognitive Graph

Packages: com.spectrayan.spector.memory.hebbian, .temporal, .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?), and semantic knowledge (who manages what project?). Spector Memory implements all three as off-heap graph structures that augment vector recall.


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"]

    S5c --> M["Merge & Dedup β†’ Re-sort β†’ Final Top-K"]
    S5d --> M
    S5e --> M

    subgraph "Layer 1 β€” Hebbian Association"
        HG["HebbianGraph<br/>164B/node, off-heap"]
        CAT["CoActivationTracker<br/>OffHeapPairTable + OffHeapEdgeTable"]
    end

    subgraph "Layer 2 β€” Entity-Relationship"
        EG["EntityGraph<br/>64B/entity, 12B/edge"]
        EX["EntityExtractor SPI<br/>LLM / NoOp / Custom"]
    end

    subgraph "Layer 3 β€” Temporal Causal"
        TC["TemporalChain<br/>16B/node, linked list"]
    end

    S5c --> HG
    S5c --> CAT
    S5d --> TC
    S5e --> EG

    style RP fill:#4a90d9,color:white
    style M fill:#00b894,color:white
    style HG fill:#e74c3c,color:white
    style EG fill:#9b59b6,color:white
    style TC fill:#f39c12,color:white

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

HebbianGraph β€” Memory-Level Associations

The HebbianGraph stores explicit memory-to-memory edges with association weights in an off-heap adjacency list.

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

Off-heap layout (164 bytes per node):

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ degree   β”‚ edges[0..19]: (neighborIdx:4B, weight:4B)    β”‚
β”‚ (4B)     β”‚ = 20 Γ— 8B = 160B                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key properties:

Property Value
Storage Off-heap MemorySegment via Panama
Max degree 20 neighbors per memory
Edge weight Float β€” strengthened on co-ingestion
Eviction Weakest edge evicted when degree exceeds MAX_DEGREE
Decay 0.9 multiplicative factor per consolidation cycle
Spreading activation BFS with depth=2, attenuated by edge weight
Persistence HGPH magic header, chunked 64KB FileChannel I/O

Pipeline integration:

  • Ingestion (Step 9b): When memories are co-ingested within the same session, HebbianGraph.strengthen(currentIdx, previousIdx, 1.0f) strengthens the bidirectional edge.
  • Recall (Step 5c): After the 6-phase scorer produces a seed set, HebbianGraph.activateNeighbors(seedIdx, depth=2) discovers associated memories. These are added to the result set with a 0.3Γ— score attenuation.

CoActivationTracker β€” Tag-Level Associations

The CoActivationTracker tracks tag co-occurrence patterns using two off-heap hash tables:

OffHeapPairTable β€” Undirected Co-Activation Counts

Tracks how often two tags appear together in ingested memories.

Slot layout (32 bytes):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ keyHashA  β”‚ keyHashB  β”‚ count    β”‚ flags β”‚
β”‚ 8 bytes   β”‚ 8 bytes   β”‚ 4 bytes  β”‚ ...   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜
  • Open-addressing hash table with linear probing
  • FNV-1a 64-bit hashing for tag strings
  • ~50% load factor for fast lookups

OffHeapEdgeTable β€” Directed STDP Edges

Tracks causal/predictive relationships between tags (Spike-Timing Dependent Plasticity):

Slot layout (40 bytes):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ sourceHash β”‚ targetHash β”‚ weight β”‚ lastMs   β”‚ actCount  β”‚ flags β”‚
β”‚ 8 bytes    β”‚ 8 bytes    β”‚ 4 bytesβ”‚ 8 bytes  β”‚ 4 bytes   β”‚ ...   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜
  • Weight clamped to [0.0, 1.0]
  • Temporal metadata for STDP learning rules
  • Persistence via COAX magic header with hashβ†’tag reverse map

STDP β€” Spike-Timing Dependent Plasticity

If tag A is consistently recalled before tag B, the directed edge A→B is strengthened. This creates predictive associations: "when I think of A, I should also think of B." The HebbianCoActivationListener runs after each recall on a Virtual Thread, updating STDP weights with zero impact on recall latency.


Layer 2: Entity-Relationship Graph

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

The EntityGraph stores typed entities (PERSON, PROJECT, ORG, ...) and typed relations (MANAGES, AUTHORED, PART_OF, ...) 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 Implementation Description
NONE (default) NoOpEntityExtractor No extraction β€” graph features disabled
LLM LlmEntityExtractor Uses TextGenerationProvider with a structured prompt
CUSTOM User-provided Any custom EntityExtractor implementation
// Enable LLM entity extraction via Builder
SpectorMemory.builder()
    .entityExtractionMode(EntityExtractionMode.LLM)
    .textGenerationProvider(provider)
    .build();

Type System

22 Entity Types: PERSON, ORGANIZATION, PROJECT, CONCEPT, EVENT, LOCATION, TOOL, SKILL, DOCUMENT, API, DATABASE, FRAMEWORK, PROTOCOL, METRIC, ROLE, TEAM, PRODUCT, SERVICE, WORKFLOW, DECISION, RISK, OTHER

21 Relation Types: MANAGES, AUTHORED, ATTENDED, PART_OF, RELATED_TO, CAUSES, DEPENDS_ON, USES, CREATED, MENTIONS, MEMBER_OF, ASSIGNED_TO, REPORTED_BY, BLOCKED_BY, IMPLEMENTS, EXTENDS, TESTED_BY, DEPLOYED_TO, MONITORS, TRIGGERS, OTHER

Off-Heap Layout

Entity Node (64 bytes, 8-byte aligned):

[type:1B][pad:7B][nameHash:8B][memRef0:4B][memRef1:4B][memRef2:4B][memRef3:4B]
[refCount:4B][degree:4B][edgeStart:4B][pad:20B]

Entity Edge (12 bytes):

[targetId:4B][relationType:4B][weight:4B]

Traversal: BFS with typed edge filtering, max 32 edges per entity, max 4 memory references per entity.

Pipeline integration:

  • Ingestion (Step 9d): Extract entities from text β†’ entityGraph.addEntity(name, type) β†’ entityGraph.linkEntityToMemory(eid, memoryIdx) β†’ entityGraph.addRelation(fromEid, toEid, relationType)
  • Recall (Step 5e): Extract entities from query β†’ find in graph by name β†’ 2-hop BFS β†’ collect memoriesForEntity(eid) β†’ add to result set with 0.25Γ— attenuation per hop
  • Persistence: ENTG magic header with on-heap nameIndex reconstruction on load

Layer 3: Temporal Causal Chain

"What happened after the deployment failed?"

The TemporalChain links memories ingested within the same session into a doubly-linked list, enabling temporal navigation.

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

Off-Heap Layout (16 bytes per node)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ prevIdx  β”‚ nextIdx  β”‚ sessionId β”‚ pad      β”‚
β”‚ 4 bytes  β”‚ 4 bytes  β”‚ 4 bytes   β”‚ 4 bytes  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

-1 is used as sentinel for "no link" (beginning or end of chain).

API:

Method Description
link(currentIdx, prevIdx, sessionId) Links two memories within a session
followForward(startIdx, maxHops) "What happened next?" β†’ List<Integer>
followBackward(startIdx, maxHops) "What happened before?" β†’ List<Integer>
save(Path) / load(Path) Persistence with TPCH magic header

Pipeline integration:

  • Ingestion (Step 9c): When a new memory is ingested within the same session, temporalChain.link(currentIdx, lastIngestedIdx, sessionId) creates the bidirectional link.
  • Recall (Step 5d): For each seed result, followForward(idx, 3) and followBackward(idx, 3) discover temporally adjacent memories. Forward links get 0.8Γ— score, backward links get 0.7Γ—.

Persistence

All graph components persist alongside episodic partitions in DISK mode:

Component File Magic Format
HebbianGraph hebbian.graph HGPH 16B header + raw segment bytes
CoActivationTracker coactivation.dat COAX 16B header + pair table + edge table + hash→tag map
EntityGraph entity.graph ENTG 16B header + entity segment + edge segment + name index
TemporalChain temporal.chain TPCH 16B header + raw segment bytes

All use chunked 64KB FileChannel I/O to avoid ByteBuffer overflow on large segments.


Error Framework

Graph operations use granular exceptions from the SpectorGraphException hierarchy:

SpectorMemoryException (SPE-310-xxx)
  └── SpectorGraphException (base)
      β”œβ”€β”€ SpectorHebbianException         (SPE-310-006)
      β”œβ”€β”€ SpectorTemporalChainException   (SPE-310-007)
      β”œβ”€β”€ SpectorEntityGraphException     (SPE-310-008)
      β”œβ”€β”€ SpectorCoActivationException    (SPE-310-009)
      β”œβ”€β”€ SpectorGraphPersistenceException(SPE-310-010)
      └── SpectorGraphDecayException      (SPE-310-011)

All pipeline catch sites use catch(RuntimeException) β†’ create granular exception β†’ log.warn(ex.getMessage()). No generic catches, no swallowed exceptions.


Memory Budget

Layer Per-Node At 100K memories At 1M memories
Hebbian (L1) 164B 16.4 MB 164 MB
CoActivation ~1MB total ~1 MB ~1 MB
Entity (L2) ~64B + edges ~8 MB ~80 MB
Temporal (L3) 16B 1.6 MB 16 MB
Total ~27 MB ~261 MB

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 3-layer graph creates emergent intelligence:

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 3) β†’ follows the chain: connection pool β†’ timeout config β†’ retry backoff β†’ adds all three
  5. Entity (Layer 2) β†’ "connection pool" mentions entity "DatabaseService" β†’ traverses DEPENDS_ON edge β†’ finds "Redis cache config" β†’ adds it

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


Next Steps