Part of #534 (Phase 3 EPIC). Soft-blocked on Phase 1/2 (#491–#497) shipping first.
Thesis
Today, memory storage is scattered across crates/tui/src/memory.rs, crates/tui/src/anchors.rs, crates/tui/src/cycle_manager.rs, and crates/tui/src/tools/recall_archive.rs. Swapping in any structured backend later requires touching all four. Consolidate behind a MemoryBackend trait, then replace the default impl's flat-file storage with SQLite (under ~/.deepseek/memory.db). The user-facing surface (/memory editor, # add quick-capture, @import) stays identical; only the substrate changes.
Current behavior
crates/tui/src/memory.rs — flat ~/.deepseek/memory.md, ad-hoc append.
crates/tui/src/anchors.rs — separator-split markdown at ~/.deepseek/anchors.md.
crates/tui/src/tools/recall_archive.rs — BM25 over JSONL cycle archives.
- No common interface; each module touches its own files directly.
Proposed change
Define trait MemoryBackend in crates/tui/src/memory/mod.rs with the following surface (sketch):
trait MemoryBackend: Send + Sync {
fn load_user_memory(&self) -> Result<String>;
fn append_user_memory(&self, entry: &str) -> Result<()>;
fn read_anchors(&self) -> Result<Vec<String>>;
fn write_anchors(&self, anchors: &[String]) -> Result<()>;
fn recall(&self, query: &str, scope: RecallScope) -> Result<Vec<MemoryHit>>;
fn remember(&self, item: NewMemory) -> Result<MemoryId>;
}
Default impl: SqliteBackend backed by ~/.deepseek/memory.db with FTS5 enabled. Schema versioned per the existing schema_version pattern in runtime_threads.rs / task_manager.rs. Reuse the workspace's existing rusqlite dependency from deepseek-state.
Migration: on first launch with the new backend, import existing ~/.deepseek/memory.md and anchors.md content into the SQLite store, preserve originals as .bak.<timestamp>.
Backend instance lives on ToolContext so tools can call it. Hand-edit path preserved: /memory round-trips SQLite ↔ markdown so power users can still edit in $EDITOR.
Open questions
- Round-trip semantics for
/memory editing — when the user hand-edits the markdown view, do we diff against the SQLite state or treat the edit as authoritative?
- Should the SQLite file live at
~/.deepseek/memory.db (alongside other deepseek state) or under state/ to consolidate?
Acceptance signals
- Default config behavior identical to Phase 1/2 (snapshot tests pass).
- Existing memory files migrated, originals preserved.
MemoryBackend is the only place that touches the underlying memory storage.
recall_archive tool reads through the trait.
- No new runtime dependencies (rusqlite already present).
Part of #534 (Phase 3 EPIC). Soft-blocked on Phase 1/2 (#491–#497) shipping first.
Thesis
Today, memory storage is scattered across
crates/tui/src/memory.rs,crates/tui/src/anchors.rs,crates/tui/src/cycle_manager.rs, andcrates/tui/src/tools/recall_archive.rs. Swapping in any structured backend later requires touching all four. Consolidate behind aMemoryBackendtrait, then replace the default impl's flat-file storage with SQLite (under~/.deepseek/memory.db). The user-facing surface (/memoryeditor,# addquick-capture,@import) stays identical; only the substrate changes.Current behavior
crates/tui/src/memory.rs— flat~/.deepseek/memory.md, ad-hoc append.crates/tui/src/anchors.rs— separator-split markdown at~/.deepseek/anchors.md.crates/tui/src/tools/recall_archive.rs— BM25 over JSONL cycle archives.Proposed change
Define
trait MemoryBackendincrates/tui/src/memory/mod.rswith the following surface (sketch):Default impl:
SqliteBackendbacked by~/.deepseek/memory.dbwith FTS5 enabled. Schema versioned per the existingschema_versionpattern inruntime_threads.rs/task_manager.rs. Reuse the workspace's existingrusqlitedependency fromdeepseek-state.Migration: on first launch with the new backend, import existing
~/.deepseek/memory.mdandanchors.mdcontent into the SQLite store, preserve originals as.bak.<timestamp>.Backend instance lives on
ToolContextso tools can call it. Hand-edit path preserved:/memoryround-trips SQLite ↔ markdown so power users can still edit in$EDITOR.Open questions
/memoryediting — when the user hand-edits the markdown view, do we diff against the SQLite state or treat the edit as authoritative?~/.deepseek/memory.db(alongside other deepseek state) or understate/to consolidate?Acceptance signals
MemoryBackendis the only place that touches the underlying memory storage.recall_archivetool reads through the trait.