Skip to content

MEMORY: substrate — MemoryBackend trait + SQLite-backed store (FTS5) #535

@Hmbown

Description

@Hmbown

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    Status
    Backlog

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions