Long-term memory for Cyberine — survey + design
- Status: W1 (store + lineage plumbing) IMPLEMENTED 2026-06-07 via dogfood run
3eeac0ff—src/store/MemoryStore.ts,RunLineageonRun/GlobalRunRecord, env threading inlaunchBg.ts/cyberine-bg-run.ts, engine writes at all 3 terminal paths; E2E-verified (record + parent/chat lineage + auto previousRunId chain). W2 (outcome extraction + issue ledger writers) IMPLEMENTED 2026-06-07 via dogfood run7c48bdb4—parseOutcomeSections+ issue-event writers inMemoryStore.writeRecord,OUTCOME_TRAILERcontract on the implement step ofimplementTaskWorkflow.ts; E2E-verified (trailer parsed from verdict winner text, opened events deduped, fold lists open issues). W3 (retrieval + injection +cyberine memoryCLI) IMPLEMENTED 2026-06-07 via dogfood runsb0a376cb/4ac190d3/2438c7a5/4e11277d—memoryRetrieval.tsscorer + preamble builder + strip markers, dispatch injection inlaunchBg.ts, chat session-start injection inChatApp.tsx,cyberine memorysubcommand incyberine.ts; E2E-verified (live dispatched run received scored preamble, record task stripped clean, all 6 CLI verbs exercised). W4 (optional layers) IMPLEMENTED 2026-06-07 via dogfood runs1bd75f39/310188fc— ADD-only LLM distillation (CYBERINE_MEMORY_MODEL) + ollama embeddings with cosine axis (CYBERINE_MEMORY_EMBEDDINGS) inMemoryStore.ts/memoryRetrieval.ts, lineage chain line inRunDetail.tsx(locked by selftest scenario 11); E2E-verified against local ollama (llama3.1:8b distillation refined a record summary; nomic-embed-text vectors re-ranked a semantic query). - Researched/verified: 2026-06-07 — deep-research harness, 23 sources fetched, 25 claims adversarially verified (14 confirmed / 11 killed), grounded against the Cyberine codebase at HEAD
ca89b7e. - Goal (user ask): Cyberine phải có long-term memory, luôn traceable previous / current / next / issues — both a provenance trail AND retrievable cross-run memory.
Part 1 — Survey: how the field does agent memory (2024-2026)
1.1 Taxonomy
Agent memory is organized into five cognitive categories — episodic (experiences situated in time), semantic (abstract facts), procedural (skills, policies), working (in-context manipulation), sensory (perceptual buffering). The practical value lies in how memory is designed and used, not cognitive-science fidelity (arXiv:2602.06052, verified 3-0).
External memory substrates fall into four concrete types: vector index (embedding + ANN), text-record (sequential logs), structural store (graph/relational), hierarchical store. The same survey warns plain vector retrieval "can be unreliable" and injects noise when ranking is weak (arXiv:2602.06052, verified 3-0) — a reason NOT to make embeddings the only retrieval axis.
Mapping to Cyberine today:
| Taxonomy | Cyberine equivalent today |
|---|---|
| Episodic | run.json + events.jsonl per run (raw, non-lossy) — exists |
| Semantic | distilled facts / open issues across runs — missing |
| Procedural | workflow defs, skills, .local/dogfood/ — exists (outside memory scope) |
| Working | chat apiMessages, loop messages array — exists, session-scoped |
1.2 Reference architectures (verified findings)
| System | Storage model | What Cyberine can take from it |
|---|---|---|
| MemGPT/Letta (arXiv:2310.08560) | 3-tier: context window → searchable recall DB → vector archive, OS-paging analogy (verified 3-0) | The tier split: keep full history out of context, page in summaries on demand |
| Zep/Graphiti (arXiv:2501.13956) | Tri-tier knowledge graph: episode subgraph (non-lossy raw) → semantic entity subgraph → community summaries; bi-temporal edges with 4 timestamps (t'_created, t'_expired, t_valid, t_invalid) — contradictions invalidate, never delete (verified 3-0) | Non-lossy episodic base + derived semantic layer; invalidate-not-delete for issue state |
| Zep retrieval | 3 parallel axes — cosine similarity, Okapi BM25, breadth-first graph traversal — fused via RRF/MMR (verified 2-1) | Multi-axis retrieval beats vector-only; keyword + recency + graph-hop is legitimate without a vector DB |
| Mem0 (arXiv:2504.19413) | LLM-driven 4-op consolidation: ADD / UPDATE / DELETE / NOOP per ingestion; top-s=10 semantic fetch (verified 2-1) | LLM-curated memory works, BUT contradiction-resolution accuracy degrades 61% → 25% as evidence count grows — keep consolidation simple |
| FluxMem (arXiv:2602.14038) | Memory-structure selection as a learnable gate (Beta Mixture Model) over 3 memory levels (verified 3-0) | Overkill at Cyberine scale; supports "no single fixed structure is optimal" |
| PROV-AGENT (arXiv:2508.02866, IEEE e-Science 2025) | Extends W3C PROV with 6 AI classes (AIAgent, AgentTool, AIModelInvocation, AIModel, Prompt, ResponseData) + standard PROV relations (used, wasGeneratedBy, wasAssociatedWith, wasInformedBy) (verified 3-0) | The lineage vocabulary: a run record should answer used what / generated what / informed by which prior run |
1.3 Two verified warnings that shape the design
- Summarization drift (arXiv:2603.07670, verified 2-1): each compression pass silently discards low-frequency details; after enough passes the agent "remembers" a sanitized, generic history that fails on edge cases. → Cyberine must keep raw episodic records append-only and non-lossy; only indexes get compacted, never the records themselves.
- Checkpoint ≠ provenance (arXiv:2603.21692, verified 3-0): LangGraph-style checkpoints capture execution state but not why the agent acted, what it concluded, how that shaped strategy. Cyberine
run.jsonhas exactly this gap — it storeswinnerOutput(the diff) but not the conclusions/issues/next-steps. → The memory record must capture structured outcome fields, not just artifacts.
1.4 What was NOT verified (do not build on these)
- Zep's headline benchmark numbers (94.8% DMR, 90% lower latency) — refuted 0-3 in verification.
- "Multi-signal scoring (recency + relevance + importance) outperforms pure similarity" (Generative Agents) — refuted 0-3 as a claim; used below only as a design heuristic, not a research-backed fact.
- Bi-temporal invalidation may only pay off at multi-user scale (open question in research) — Cyberine is single-user, so we use append-only +
supersededByinstead of 4 timestamps.
Part 2 — Ground truth: what Cyberine has and lacks (HEAD ca89b7e)
Existing persistence surface:
RunStore—.local/runs/<runId>/run.json+events.jsonl(src/store/RunStore.ts:32);Run/RunStep/WorkerRunshapes insrc/store/runTypes.ts:58-79.GlobalRegistry—$CYBERINE_HOME/runs/<runId>.json,GlobalRunRecord(src/store/GlobalRegistry.ts:23-41), tombstone delete.ChatSessionStore—$CYBERINE_HOME/chat-sessions/<id>.jsonincl. fullapiMessages(src/store/ChatSessionStore.ts:51-61).- Hooks —
PreToolUse | PostToolUse | PreDispatch | RunStart | RunEndwith CC exit-code contract (src/core/hooks.ts:90).
Gaps (verified by codebase sweep 2026-06-07):
- Zero run-to-run linkage — no
parentRunId/previousRunId/sourceSessionIdanywhere; runs are isolated UUIDs. Task.context(src/core/types.ts) is an unused stub — natural injection point exists but nothing populates it.- No outcome summary —
winnerOutputholds raw diff/text; nothing stores "what was accomplished, what issues remain, what's next". - No cross-run retrieval —
listRuns()sorts byupdatedAtonly. - Chat↔run link is in-memory only —
chatRunsRefMap inChatApp.tsxis never persisted; after resume, a session cannot say which runs it dispatched. RunEndhook payload lacks step outcomes (needsRunStore.get(runId)to read them).- No
$CYBERINE_HOME/memory/namespace;CYBERINE_HOMEresolution is copy-pasted in 5 files (extractcyberineHome()helper first).
Part 3 — Design
3.0 Principles
- Episodic base is non-lossy and append-only (Graphiti episode-subgraph pattern; avoids summarization drift). One memory record per run, never rewritten — corrections happen via
supersededBy. - Lineage is first-class, retrieval is layered on top. Traceability (previous/current/next/issues) must work with
grepalone; embeddings are an optional enhancement, never a dependency. - No vector DB. Single-user scale (<10k records): brute-force cosine over JSON-stored vectors is milliseconds; BM25-style keyword + recency works with zero infra. Multi-axis retrieval is the verified pattern (Zep, 2-1).
- File-based, human-readable — same philosophy as
.local/runs/.
3.1 Storage layout
$CYBERINE_HOME/memory/
<project-slug>/ # slug = sanitized basename of cwd (per-project isolation)
records/<runId>.json # episodic record, one per run, append-only
issues.jsonl # issue ledger, append-only state transitions
index.json # compact derived index (rebuildable from records/)
embeddings.jsonl # optional: {runId, model, vector[]} one line per recordindex.json is a derived cache (like GlobalRegistry vs run dirs) — cyberine memory rebuild regenerates it from records/, mirroring the existing backfillRegistry pattern.
3.2 Schemas
MemoryRecord (episodic, one per run — written at run end):
interface MemoryRecord {
runId: string;
cwd: string;
workflowName: string;
task: string; // full task prompt
state: 'done' | 'error' | 'cancelled';
startedAt: number;
endedAt: number;
// --- lineage (PROV-AGENT-inspired: wasInformedBy / wasGeneratedBy) ---
lineage: {
parentRunId?: string; // run that spawned this run (wasInformedBy)
chatSessionId?: string; // chat session that dispatched it
previousRunId?: string; // latest prior run in same project at dispatch time
rootRunId?: string; // first run of the chain
};
// --- structured outcome (closes the reasoning-provenance gap) ---
outcome: {
summary: string; // 2-4 sentences: what was attempted, what happened
accomplished: string[]; // concrete deliverables ("shipped X", "fixed Y")
issues: string[]; // ids into issues.jsonl opened by this run
next: string[]; // recommended next steps
};
workers: { agentId: string; provider?: string; state: string }[];
verdictSummary?: string; // judge verdict one-liner, if any
supersededBy?: string; // runId of a correcting record (invalidate, never delete)
}IssueRecord (issues.jsonl, append-only transitions — bi-temporal-lite):
interface IssueEvent {
issueId: string; // stable slug
at: number;
event: 'opened' | 'resolved' | 'reopened' | 'wontfix';
byRunId: string; // provenance: which run changed the state
title: string;
detail?: string;
}Current issue state = fold over events (last event wins). previous/current/next/issues traceability falls out directly: previous = lineage.previousRunId chain, current = the record, next = outcome.next, issues = open-issue fold filtered by cwd.
3.3 Write path
Primary write point: WorkflowEngine run-end (all three terminal paths already converge on the RunEnd hook dispatch — src/engine/WorkflowEngine.ts). The engine calls MemoryStore.writeRecord(run) directly (product feature, not a user hook; the RunEnd hook stays for user extensions).
outcome generation, two modes:
- Extractive (default, zero-cost): summary from task + final state + verdict + winner-output head;
issues/nextparsed from worker output sections when the implementer-fleet prompt template asks workers to emit anISSUES:/NEXT:trailer (prompt-contract change inimplementTaskWorkflow.ts). - LLM-distilled (opt-in,
CYBERINE_MEMORY_MODEL): one cheap call (defaultdeepseek-v4-flash) that maps run.json → outcome JSON. Keep it single-shot ADD-only — Mem0's verified lesson is that LLM consolidation degrades sharply with conflict count (61%→25%), so we never let the LLM UPDATE/DELETE existing records.
Update 2026-09-22: there is no built-in default model —
distillConfig()returnsnulland distillation is skipped entirely unlessCYBERINE_MEMORY_MODELis set, seesrc/store/MemoryStore.ts:356.
Lineage capture:
launchBackgroundRun()(src/launchBg.ts:71) gains optional{ parentRunId, chatSessionId }; threads them via env (CYBERINE_PARENT_RUN_ID,CYBERINE_CHAT_SESSION_ID) to the detached child, same mechanism asCYBERINE_RUN_IDtoday.ChatApppasses its session id on dispatch — this also fixes gap #5 by persisting dispatched run ids intoChatSessionRecord(new fielddispatchedRuns: string[]).previousRunId= latest record in the project'sindex.jsonat dispatch time.Run(runTypes.ts) andGlobalRunRecord(GlobalRegistry.ts) gain the same optionallineageblock so the TUI can render chains without opening memory files.
3.4 Read path (retrieval + injection)
Multi-axis scoring, Zep-style but file-based (no DB):
score(record, query) =
w_kw * keywordScore(query, record) # BM25-ish over task+summary+issues
+ w_rec * exp(-ageDays / halfLife) # recency decay
+ w_lin * lineageBoost # same chain / same chat session
[+ w_vec * cosine(qVec, record.vector)] # only when embeddings enabledInjection points:
- Dispatch-time (automatic):
launchBackgroundRunbuilds aMEMORYpreamble — top-K records (default 3) + open issues for the cwd — and prepends it to the task prompt (or fillsTask.context, the existing unused stub). Capped (default ~1500 tokens), disabled withCYBERINE_MEMORY=0. - Chat planner (automatic): system prompt gains the same block at session start — the planner can answer "what were we doing / what's open".
- Explicit:
cyberine memorysubcommand —list,show <runId>,search <query>,issues [--all],chain <runId>(walk lineage both directions),rebuild.
Embeddings (optional, OFF by default): CYBERINE_MEMORY_EMBEDDINGS=ollama uses local nomic-embed-text via Ollama (/v1/embeddings — pairs with the Ollama provider integration, docs/providers/ollama.md); any OpenAI-compatible embeddings endpoint also works. Vectors stored as JSON lines; brute-force cosine at query time.
3.5 Compaction & pruning
- Records are never rewritten (drift-avoidance).
index.jsonkeeps per-record one-liners; if it exceeds a cap (default 500 entries) the oldest index entries collapse into a per-projectarchive-summary.md— the underlying records stay on disk. - Resolved issues older than N days (default 90) drop out of the default
issuesview (--allshows everything). supersededBychains:cyberine memory showfollows them and renders the newest truth, marking superseded records.
3.6 Config (new env vars)
| Env var | Default | Meaning |
|---|---|---|
CYBERINE_MEMORY | 1 | Master switch: record + inject |
CYBERINE_MEMORY_DIR | $CYBERINE_HOME/memory | Storage root |
CYBERINE_MEMORY_TOPK | 3 | Records injected at dispatch |
CYBERINE_MEMORY_MAX_TOKENS | 1500 | Injection budget |
CYBERINE_MEMORY_MODEL | '' (extractive) | Opt-in LLM distillation model |
CYBERINE_MEMORY_EMBEDDINGS | '' (off) | ollama | explicit base URL |
3.7 Implementation plan (dogfood waves)
Per .claude/rules/dogfood-first.md — each wave is a file-disjoint slice runnable through the orchestrator:
- W1 — store + lineage plumbing:
cyberineHome()helper;src/store/MemoryStore.ts(records + issues + index);lineagefields onRun/GlobalRunRecord; env threading inlaunchBg.ts; engine write at run end. Verify: run a task, record appears, chain fields populated. - W2 — outcome extraction + issue ledger: extractive outcome builder;
ISSUES:/NEXT:prompt-contract inimplementTaskWorkflow.ts; issue fold logic. Verify: dogfood run emits issues,cyberine memory issueslists them. - W3 — retrieval + injection: scorer,
MEMORYpreamble in dispatch + chat system prompt;cyberine memoryCLI. Verify: second run on a related task receives the first run's summary; PTY selftest scenario for the chat block. - W4 — optional layers: LLM distillation; Ollama embeddings; TUI lineage rendering (chain view in run detail).
Each wave: npm run typecheck before integration; recipes land in USAGE.md per feature-docs.md.
Sources
- arXiv:2602.06052 — Rethinking Memory Mechanisms of Foundation Agents (taxonomy, substrates)
- arXiv:2310.08560 — MemGPT
- arXiv:2603.07670 — agent-memory survey (summarization drift)
- arXiv:2501.13956 — Zep/Graphiti (bi-temporal graph, tri-tier, multi-axis retrieval)
- arXiv:2504.19413 — Mem0 (4-op consolidation, degradation limits)
- arXiv:2602.14038 — FluxMem (learnable memory selection)
- arXiv:2603.21692 — Reasoning Provenance for Autonomous AI Agents
- arXiv:2508.02866 — PROV-AGENT (W3C PROV extension, IEEE e-Science 2025)
- Cyberine codebase sweep 2026-06-07 (file:line anchors above)
Code-Context 3-Act Architecture — T440 Design
Status: design (T440). Locks the architecture for the CODE-CONTEXT epic (T441-T454). No product code changed. Implementation tasks must treat the contracts here as fixed. Source cross-checked:...
LSP Diagnostic Feedback Loop into Adapters — T436 Design Note
Status: shipped (T436) — see src/core/config.ts:946-966,1516-1536 (diagFeedback, diagFeedbackInterval, diagSeverity, diagMaxErrors) and src/adapters/LoopAgentAdapter.ts:1255 (runDiagnostics,...