ADR-0046: Single Engram, Four Stores Storage Architecture¶
| Field | Value |
|---|---|
| Status | Accepted (Implemented) |
| Date | 2026-09-03 |
| Authors | Spector Maintainers & Architecture Working Group |
| Deciders | Spector Technical Steering Committee (TSC) |
| Supersedes | None |
| Superseded By | ADR-0030 (Engram Header Alignment) |
| Last Verified | 2026-09-16 (Verified against main) |
1. Context¶
In cognitive memory systems, memories undergo multi-stage life cycles—from raw episodic captures to consolidated semantic concepts, procedural skills, and transient working memory tokens.
Rationale & Architectural History¶
ADR-0006 correctly introduced a variable-length conversation store. The cutover stopped halfway.
flowchart TD
subgraph Broken["After ADR-0006"]
R1["remember SEMANTIC"] --> Slab["semantic slot: header + vector"]
Slab --> HNSW["VectorIndex SEMANTIC-only"]
Slab --> BM25["BM25"]
Slab --> MIDX["IndexRecordMemory"]
R2["rememberEpisodic"] --> Log["EpisodicLogMemory punned header + CBOR"]
Log --> Sleep["ReflectPathway"]
Log -.->|no BM25 / vector / directory| X["raw episode unreachable"]
Sleep -->|lossy summaries| QA["QA 28–38% on proper nouns"]
Router["router.episodic null"] --> Skip["gather skip"]
end That is also an MF-001 miss: turns lack a real encoding header (NF6 / M3), HNSW can disagree with the episode store on existence (NF0 / M1), and cosine-only nomination cannot recover proper nouns (M2).
Alignment with MF-001 Standards¶
Physical layout is not the model (MF-001 §12). This ADR is a realization of the algebra on the existing substrate.
| MF-001 | Realization |
|---|---|
| \(T = (\mathrm{id},\ \mathrm{tier},\ \mathrm{payload},\ \mathrm{header},\ \mathrm{loc})\) | MemoryId + MemoryType + payload + EncodingHeader + Location |
| NF0 / M1 single engram | Existence = tier store and not tombstoned. Rebuild indexes from stores |
| NF1 / M3 header completeness | Encoding header on every durable record |
| NF2 / M8 lineage | LineageRecordMemory (or PROVENANCE_LOG if it already holds parents) |
| NF3 / M10 isolation | Unchanged: one partition + runtime bundle per rememberer |
| NF6 emotional completeness | Episodes get \(I\), valence, arousal at remember |
| NF7 source honesty | source on the encoding header; default recall gates SIMULATED |
| M2 guaranteed access | RecallPathway unions lexical, dense, association, salience |
| M4 physical independence | Callers use SpectorMemory / ids / MemoryType, never offsets |
| M5 tier physics | Four stores, same header type, distinct physics |
| M7 forgetting | StrengthRecordMemory holds \(D\)/\(S\); forget does not lower \(S\) |
| M9 reconsolidate | RecallPathway updates \(D\); payload rewrite only via reconsolidate + lineage |
Working memory remains NF6-exempt.
2. Problem Statement¶
Prior to this architectural decision, the transition from fixed-size legacy records to variable-length episodic storage was incomplete:
- Dual Write Universes: Episodic logs and text/vector stores maintained independent write paths, leading to potential inconsistency and split-brain states where index records existed without corresponding durable engrams.
- Scattered Headers: Memory metadata and flags were partially stored in secondary header slabs and partially on payload records, violating Single Source of Truth principles.
- Facade Bloat:
DefaultSpectorMemorygrew into an unmaintainable monolithic class attempting to implement dozens of unrelated orchestration and data access responsibilities.
3. Decision Drivers¶
- One Identity Per Memory (NF0 / M1): Every memory is anchored by a single authoritative engram ID. Projections (lexical tokens, vector embeddings, graph edges, and strength values) are secondary indices referencing this ID.
- Physical Tier Separation: Store schemas must reflect biological memory boundaries: episodic conversations, semantic facts, procedural habits, and transient working memory.
- Off-Heap Performance: High-throughput writes and recalls must avoid JVM garbage collection pressure using structured off-heap layouts.
- API Stability: Maintain backward compatibility for existing callers while establishing a unified
rememberparadigm.
4. Considered Options¶
Rejected Approaches¶
- Mongo / Postgres / JSON as the SIMD record or the source of truth for \(D/S/I\)
- One shared file that stores facts and skills beside episodes
TraceKind(TURN/FACT/SKILL) on the episode fileHEADER_SLABas an authoritative store- Inlining episode vectors after variable payload
- Putting \(D\)/\(S\) on the episode record
- A new
MemoryEngineinterface rememberEpisode(...)as a second persistence universe- Using
ReflectPathwayas search - Shared BM25/HNSW across rememberers (M10 / MF-T10)
Option Comparison¶
- Option 1: Central Document Database Engine: Replace low-level off-heap stores with a document store (e.g. SQLite/RocksDB/CBOR engine). Rejected due to query latency overhead and lack of zero-copy vector/token scans.
- Option 2: Standalone Header Slab (
HEADER_SLAB): Maintain a central global table of all engram headers. Rejected because it introduces a second source of truth and write-synchronization bottleneck. - Option 3: Four Dedicated Stores with On-Record Headers & Facade Delegation (Selected): Treat memories as unified engrams across 4 distinct physical stores (
EpisodicMemory,SemanticMemory,ProceduralMemory,WorkingMemory), with the encoding header embedded directly on the record.
5. Decision Outcome¶
Core Architectural Decisions¶
- Treat every durable memory as one engram: id +
MemoryType+ encoding header + payload + location. Faces (text, vector, strength, associations) are projections of that id. They must not be independently authoritative (MF-001 NF0 / M1). -
Keep four stores, one payload schema each. Do not put facts and skills in the conversation file.
EpisodicMemory— episodes (rename ofEpisodicLogMemory)SemanticMemory— factsProceduralMemory— skillsWorkingMemory— evictable thoughts
-
Place the encoding header on the record itself (episode prefix or fact/skill slot). Do not invent
HEADER_SLABas a source of truth. A derived header scan file is allowed later if walks show up in p99. - Keep strength (
D,S, use counts) off the episode record. RenameRegionId.AUDIT→RegionId.STRENGTH(keep numeric id 4) andAuditRecordMemory→StrengthRecordMemory. - One write verb:
SpectorMemory.remember(..., MemoryType.EPISODIC, ...).rememberEpisodic(...)becomes a compatibility default that fillsIngestionContextand calls the sameRememberPathway. - Do not add
MemoryEngine. SplitDefaultSpectorMemoryby delegation ontoDefaultMemoryRemember,DefaultMemoryRecall,DefaultMemoryReflection,DefaultMemoryAdminView. Pathways stay the implementation;RecallPipelinestays deprecated. - Do not replace Panama with a document database. JSON/CBOR is the episode payload codec, not the query engine.
Public API Specification¶
SpectorMemory
extends MemoryRemember
extends MemoryRecall
extends MemoryReflection
extends MemoryAdminView
spectorMemory.remember(id, text, MemoryType.EPISODIC, source, context, tags);
spectorMemory.recall(query, options);
Role, session, sequence belong on IngestionContext / episode payload.
classDiagram
class SpectorMemory {
<<interface>>
}
SpectorMemory --|> MemoryRemember
SpectorMemory --|> MemoryRecall
SpectorMemory --|> MemoryReflection
SpectorMemory --|> MemoryAdminView
class DefaultMemoryRemember
class DefaultMemoryRecall
class DefaultMemoryReflection
class DefaultMemoryAdminView
DefaultMemoryRemember ..|> MemoryRemember
DefaultMemoryRecall ..|> MemoryRecall
DefaultMemoryReflection ..|> MemoryReflection
DefaultMemoryAdminView ..|> MemoryAdminView
class DefaultSpectorMemory {
-MemoryRemember remember
-MemoryRecall recall
-MemoryReflection reflection
-MemoryAdminView admin
}
DefaultSpectorMemory ..|> SpectorMemory
DefaultSpectorMemory --> DefaultMemoryRemember
DefaultSpectorMemory --> DefaultMemoryRecall
DefaultSpectorMemory --> DefaultMemoryReflection
DefaultSpectorMemory --> DefaultMemoryAdminView Java cannot extend two impl classes. DefaultSpectorMemory delegates. It owns cortex lifetime (stores, indexes, soul) and passes those into the four constructors. Callers still depend on SpectorMemory.
Pathways, not pipelines:
MemoryRemember → RememberPathway
MemoryRecall → RecallPathway
MemoryReflection → ReflectPathway / DreamPathway
RecallPipeline remains deprecated.
Physical Store Topologies & Header Placement¶
flowchart TB
SM["SpectorMemory"] --> RP["RememberPathway"]
SM --> QP["RecallPathway"]
SM --> FP["ReflectPathway"]
RP --> EM["EpisodicMemory"]
RP --> SEM["SemanticMemory"]
RP --> PM["ProceduralMemory"]
RP --> WM["WorkingMemory"]
EM --> EH["EncodingHeader on episode record"]
SEM --> EH2["EncodingHeader on fact slot"]
PM --> EH3["EncodingHeader on skill slot"]
RP --> ST["StrengthRecordMemory"]
RP --> VX["VectorIndex"]
RP --> LX["MemoryBM25Index"]
RP --> DIR["IndexRecordMemory"]
FP --> LIN["LineageRecordMemory"] Episode record (header rides the store)¶
+0 WalkPrefix 32B id, time, payloadBytes, checksum
+32 EncodingHeader 64B I, valence, arousal, tags, source, flags
+96 episode payload N codec-hidden (CBOR or packed)
next = 96 + N
This is not a cognitive record (header + vector). The vector stays in the vector index / slab. Strength stays in RegionId.STRENGTH.
Fact / skill slots¶
Keep fixed-stride packing inside SemanticMemory / ProceduralMemory. Encoding header at byte 0 of the slot. Inline vectors may remain until a later shared-slab pass.
Accessors¶
| Store | Accessor | Address |
|---|---|---|
EpisodicMemory | EpisodicMemoryHeaderAccessor | recordOffset + WalkPrefix.SIZE |
SemanticMemory | SemanticMemoryHeaderAccessor | slot * stride |
ProceduralMemory | ProceduralMemoryHeaderAccessor | slot * stride |
One EncodingHeaderLayout (today HeaderLayout64 / V2). Javadoc on the accessors states the fields; javadoc on the store states that semantic payload is a fact and procedural payload is a skill.
No HEADER_SLAB region as the home of the header. Optional later: a derived scan projection rebuilt from episode records, same status as BM25.
Strength Region Management¶
Today’s RegionId.AUDIT (id 4) + AuditRecordMemory is the strength face (ADR-0028): \(S\), \(D\), recall counts, last use, ACT-R.
| Today | This ADR |
|---|---|
RegionId.AUDIT | RegionId.STRENGTH (id 4 unchanged) |
AuditRecordMemory | StrengthRecordMemory |
AuditRecordLayout | StrengthRecordLayout |
Encoding identity does not move here. PROVENANCE_LOG (26) stays a different region.
Semantic Lineage & Provenance¶
LineageRecordMemory. Prefer existing RegionId.PROVENANCE_LOG if it already stores parent ids; otherwise add RegionId.LINEAGE.
ReflectPathway writes MemoryType.SEMANTIC + MemorySource.DISTILLED and a lineage row. Parent episodes stay recallable.
Write & Recall Pipelines¶
sequenceDiagram
actor Caller
participant API as SpectorMemory
participant WR as DefaultMemoryRemember
participant P as RememberPathway
participant EM as EpisodicMemory
participant ST as StrengthRecordMemory
participant IX as indexes
Caller->>API: remember(id, text, EPISODIC, source, context, tags)
API->>WR: remember(...)
WR->>P: remember(...)
P->>P: HeaderFactory / soul to EncodingHeader
P->>EM: append(prefix, header, episode)
P->>ST: init(D0, S0)
P->>IX: embed, BM25, register EPISODIC
API-->>Caller: id sequenceDiagram
actor Caller
participant API as SpectorMemory
participant RD as DefaultMemoryRecall
participant P as RecallPathway
participant H as Hydrator
participant ST as StrengthRecordMemory
Caller->>API: recall(query, options)
API->>RD: recall(...)
RD->>P: recall(...)
P->>P: lexical union dense union association union salience
P->>P: hard gates
P->>H: load by Location.type
P->>ST: current + onRecall
API-->>Caller: List of CognitiveResult Hydration:
EPISODIC → EpisodicMemory.read(loc)
SEMANTIC → SemanticMemory.read(loc)
PROCEDURAL → ProceduralMemory.read(loc)
WORKING → WorkingMemory.read(loc)
DenseRelay must not drop type != SEMANTIC. Never semantic.readHeader(episodeOffset).
Salience over episodes walks EncodingHeader at recordOffset + 32. Arousal is not a cue filter; it only slows decay of \(D\).
Kernel Impact & Region Mappings¶
The kernel already has RECORD, APPEND, GRAPH, BUNDLE. This ADR changes contracts, not primitives.
Keep: Memory, AbstractMemory, SMKM MemoryHeader, MemoryShape, MemoryId, bundle machinery, IdentityBundle, kernel.codec, graph layouts, IndexRecordMemory.
Change:
| Type | Change |
|---|---|
RegionId | Rename AUDIT → STRENGTH. Add LINEAGE only if provenance cannot hold parents. Do not put vectors in EPISODIC |
EpisodicLogMemory | Rename EpisodicMemory. Append WalkPrefix + EncodingHeader + payload |
EpisodicLogLayout / EpisodicFieldAccessor | Split into WalkPrefix, EncodingHeaderLayout, EpisodeCodec, EpisodicMemoryHeaderAccessor |
HeaderLayout64 / V2 | Become / wrap EncodingHeaderLayout |
CognitiveMemoryRouter | Stop mapping EPISODIC to a nullable legacy slab. Expose the four stores |
PostIngestSync | Body of RememberPathway after append; allow EPISODIC into vector + directory |
SemanticRecallStrategy | Becomes a DenseRelay inside RecallPathway; type-aware hydrate |
TierScanStrategy | Must not return on episodic() == null in log mode |
DefaultSpectorMemory | Delegate to four ISP impls |
Add: WalkPrefix, EpisodeCodec, store-named header accessors, StrengthRecordMemory (rename), LineageRecordMemory, DefaultMemoryRemember, DefaultMemoryRecall, DefaultMemoryReflection, DefaultMemoryAdminView.
Remove after dual-read: EpisodicRecordMemory, production writes through punned EpisodicFieldAccessor, offset/164 graphSlot fallbacks.
6. Pros and Cons of the Options¶
Positive¶
- Single Source of Truth: Eliminates index-without-payload anomalies; the engram record is the sole authority.
- Clean Cognitive Model: Four dedicated stores match cognitive taxonomy without schema contamination.
- Predictable Performance: Fixed-stride scans for facts/skills and streaming chunking for episodic payloads.
- Decoupled Facade:
DefaultSpectorMemorybecomes a pure coordinator delegating to pathway implementations.
Negative / Trade-offs¶
- Store Migration: Existing v1 records require migration tooling to adopt the new on-record header layouts.
- Variable Length Record Parsing: Requires explicit byte-length validation on reading episodic payloads to prevent buffer overflows.
7. Implementation Plan¶
Phased Cutover¶
gantt
title ADR-0010
dateFormat YYYY-MM-DD
axisFormat %b %d
section A
Index episodes and hydrate EPISODIC :a1, 2026-09-08, 14d
section B
Real encoding header on episode record :b1, after a1, 21d
section C
Facade split and STRENGTH rename :c1, after b1, 21d
section D
Lineage and delete EpisodicRecordMemory :d1, after c1, 21d
section E
MF-T01 T03 T10 :e1, after d1, 14d | Phase | Work | Exit |
|---|---|---|
| A | RememberPathway for EPISODIC: decoded text to BM25, embed to vector index, IndexRecordMemory.register(EPISODIC), write real \(I\)/valence/arousal. RecallPathway hydrates episodes from EpisodicMemory. Stop skipping log-mode partitions | Townsend / Vertex / Prentice raw-hit@15 before reflect() |
| B | New episodes: WalkPrefix + EncodingHeader + payload. EpisodicFieldAccessor read-only | No production punned writes |
| C | Split DefaultSpectorMemory. Rename AUDIT → STRENGTH | Facade is delegation-only; id 4 still maps |
| D | LineageRecordMemory on consolidate. Delete EpisodicRecordMemory. Kill offset/164 | NF2/M8 green |
| E | MF-T01, T03, T10; source gate; monotonic \(S\) | Fixtures pass without flattening \(I\)/valence |
Operational Risks & Mitigations¶
| Risk | Mitigation |
|---|---|
| Bundle rename of AUDIT | Enum name only; id 4 unchanged; codec accepts old logs |
| Dual-write latency | Same embed as today; extra copies, not extra embeds |
| Shared ANN mixes facts and episodes | Type-aware hydrate + over-fetch; split ANN only if p99 requires |
Relays still call episodic() == null | Inventory isEpisodicLogMode() before deleting the legacy slab |
| Facade split misses a method | Generate delegates from SpectorMemory; parity test against current DefaultSpectorMemory |
| Benchmark inflation | Never ingest gold episodes as SEMANTIC-only; report hit@15 and QA separately |
Acceptance Criteria¶
remember(..., EPISODIC, ...)writes episode payload, encoding header, strength row (\(D_0,S_0\)), and directory loc- No encoding-header byte is used as role, session, token count, or body length
RecallPathwaynominates from at least lexical, dense, and salienceEPISODIChits hydrate fromEpisodicMemory, not the semantic slotReflectPathwaywrites distilled children with lineage and does not erase parents- Dream/simulate cannot mint
source=experienced forgetdoes not decrease \(S\)DefaultSpectorMemorydoes not contain remember/recall scoring logicEpisodicRecordMemoryhas zero production callers before deletion- MF-T01 / T03 / T10 run without flattening \(I\)/valence
Initial Pull Request Scope¶
RememberPathway: onMemoryType.EPISODIC, extract text, BM25, embed,vectorIndex.add,IndexRecordMemory.register(EPISODIC).- Persist \(I\), valence, arousal on the episode (real header bytes, even if prefix is still transitional).
RecallPathway/ dense relay:loc.type == EPISODIC→EpisodicMemory.read+ header accessor.- Stop
returnwhenrouter.episodic() == nullin log mode. - Benchmark: keyword queries hit raw episode text before
reflect().
Do not wait on the facade split, the STRENGTH rename, or LineageRecordMemory for Phase A.
Open Questions & Long-Term Roadmap¶
- Fact vectors stay inline in the semantic slot, or join a shared vector slab later?
- Lineage in
PROVENANCE_LOGvs newLINEAGEregion? - Derived episode-header scan file only after measuring walk cost?
- How much of
SpectorMemoryAdminmoves withDefaultMemoryAdminViewin the same PR as the remember/recall split?
Physical design remains outside MF-001. This ADR succeeds if the algebra closes on SpectorMemory and a live episode cannot vanish because the first index was the wrong one.
8. Code Reference & Verification¶
The single-engram, four-store architecture is verified across the codebase:
- Engram Storage Base:
memory/spector-kernel/src/main/java/com/spectrayan/spector/kernel/record/AbstractEngramMemory.java - Physical Stores:
memory/spector-kernel/src/main/java/com/spectrayan/spector/kernel/record/EpisodicMemory.javamemory/spector-kernel/src/main/java/com/spectrayan/spector/kernel/record/SemanticMemory.javamemory/spector-kernel/src/main/java/com/spectrayan/spector/kernel/record/ProceduralMemory.javamemory/spector-kernel/src/main/java/com/spectrayan/spector/kernel/record/WorkingMemory.java
- Strength Region:
memory/spector-kernel/src/main/java/com/spectrayan/spector/kernel/record/StrengthRecordMemory.java - Ingestion & Inverted Pathways:
memory/spector-memory/src/main/java/com/spectrayan/spector/memory/cortex/pathway/RememberPathway.java