Thesis
Phase 1/2 (#489) borrowed Claude Code's memory integration shape — hierarchy, /memory, @import, quick-add. The substrate is still flat markdown (~/.deepseek/memory.md + anchors.md + JSONL cycle archive). Phase 3 graduates the substrate: typed memory classes, salience-weighted recall, graph edges between related memories, first-class contradiction tracking, and a hygiene loop that keeps the store from becoming stagnant.
Built natively in Rust under crates/tui/src/memory/. No new runtime dependencies. SQLite (already in the workspace via rusqlite in deepseek-state) with FTS5 for the substrate; vectors deferred until proven necessary.
Why now
Phase 1/2 needs to settle in production first. Users need to be living with /memory and @import before we know what the typed model should actually carve at, and what the auto-extraction signal looks like in the wild. This EPIC is soft-blocked on #491, #492, #493, #494, #495, #496, #497 shipping and getting at least one release of soak time.
Why not an external dependency
Considered wiring an external memory service (own MCP server). Rejected: adds a Node runtime requirement, splits the storage story across two systems, and the sophisticated parts of those designs (typed records, multi-signal recall, graph topology, contradiction tracking) are concepts we can implement natively in Rust without taking on a runtime dependency.
Why SQLite + FTS5
- OLTP shape matches the access pattern (frequent small reads/writes, point lookups by id, status updates).
- FTS5 ships built into SQLite — text search without a separate index.
- Recursive CTEs cover graph traversal until proven insufficient.
sqlite-vec exists if/when vector recall becomes necessary.
- Single-file backup, debuggable from any machine with
sqlite3.
DuckDB rejected: analytical/OLAP shape, wrong fit for frequent small writes. LanceDB rejected for now: vector-first, columnar, versioning-on-every-write would create write amplification on status updates; revisit specifically if embedding-based recall becomes core. SurrealDB rejected: too young, recursive CTEs cover the graph case.
Sub-issues (all v0.9.0)
Out of scope
- Replacing
session_manager.rs / runtime_threads.rs / task_manager.rs — those persist execution state, not memory. They keep their own SQLite stores.
- Vector embeddings (deferred; revisit only if FTS5 + signals proves insufficient).
- Multimodal memory blobs.
- Team / shared memory across users.
Acceptance signals
- User-facing surface (
/memory editor, # add, @import) unchanged from Phase 1/2.
- Existing
~/.deepseek/memory.md and anchors.md migrate cleanly on first run; originals preserved as .bak.
- Recall is typed and ranked, not BM25-over-flat-files.
- Contradictions surfaced to the user, never silently auto-resolved.
- Memory store gets less annoying over time, not more — measured by user-initiated drops trending down.
Thesis
Phase 1/2 (#489) borrowed Claude Code's memory integration shape — hierarchy,
/memory,@import, quick-add. The substrate is still flat markdown (~/.deepseek/memory.md+anchors.md+ JSONL cycle archive). Phase 3 graduates the substrate: typed memory classes, salience-weighted recall, graph edges between related memories, first-class contradiction tracking, and a hygiene loop that keeps the store from becoming stagnant.Built natively in Rust under
crates/tui/src/memory/. No new runtime dependencies. SQLite (already in the workspace viarusqliteindeepseek-state) with FTS5 for the substrate; vectors deferred until proven necessary.Why now
Phase 1/2 needs to settle in production first. Users need to be living with
/memoryand@importbefore we know what the typed model should actually carve at, and what the auto-extraction signal looks like in the wild. This EPIC is soft-blocked on #491, #492, #493, #494, #495, #496, #497 shipping and getting at least one release of soak time.Why not an external dependency
Considered wiring an external memory service (own MCP server). Rejected: adds a Node runtime requirement, splits the storage story across two systems, and the sophisticated parts of those designs (typed records, multi-signal recall, graph topology, contradiction tracking) are concepts we can implement natively in Rust without taking on a runtime dependency.
Why SQLite + FTS5
sqlite-vecexists if/when vector recall becomes necessary.sqlite3.DuckDB rejected: analytical/OLAP shape, wrong fit for frequent small writes. LanceDB rejected for now: vector-first, columnar, versioning-on-every-write would create write amplification on status updates; revisit specifically if embedding-based recall becomes core. SurrealDB rejected: too young, recursive CTEs cover the graph case.
Sub-issues (all v0.9.0)
MemoryBackendtrait + SQLite-backed store (FTS5)Out of scope
session_manager.rs/runtime_threads.rs/task_manager.rs— those persist execution state, not memory. They keep their own SQLite stores.Acceptance signals
/memoryeditor,# add,@import) unchanged from Phase 1/2.~/.deepseek/memory.mdandanchors.mdmigrate cleanly on first run; originals preserved as.bak.