refactor(memory): append-only writes + retrieval-driven consolidation (fixes #34)#41
refactor(memory): append-only writes + retrieval-driven consolidation (fixes #34)#41DeerGoat wants to merge 4 commits into
Conversation
…onsolidation architecture This is a planning commit for discussion. No code changes yet. Closes #34
…tecture-append-only
…tecture-append-only
…fixes #34) Replace the cosine-threshold dedup that silently dropped factual updates with an append-only write path plus an autonomous "dream" consolidation agent that tidies the daily logs into a notebook wiki. Phase 1 - append-only writes (the #34 fix): - Remove _deduplicate_and_store_facts and the 0.85 similarity threshold; facts are appended to the daily log and indexed per-fact, with no write-time dedup. - Platform guard: forked/system turns (dream, sub-agents) never feed memory extraction or transcript indexing. Phase 2 - always-on notebook vault + watermark-aware indexing: - Bootstrap the vault unconditionally at CONFIG.notebook_dir (zones + nav files). - CoreMemoryFileIndexer is the sole LanceDB writer: indexes notebook pages, drops consolidated archives by a log.md watermark, skips tombstoned facts, constant importance. Delete the broken MarkdownIndexer. - Deletion = edit file + reindex / tombstone (never a direct LanceDB mutation). Phase 3 - DreamRunner (autonomous wiki keeper): - Gated background agent (time+volume / catch-up + retry-skip + proof-of-work) that consolidates daily logs into the vault, owns the watermark, and regenerates MEMORY.md. POST /memory/consolidate triggers it on demand. Design + audit trail: docs/03-developing/memory-consolidation-plan.md Closes #34 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implementation walkthrough (review guide)This documents the exact runtime behavior of the change so reviewers can follow the code. The conceptual design is in TL;DR data flowLanceDB is a disposable projection — every row is (re)written only by 1. Where things live on disk
2. Write path (per turn) —
|
| Knob | Default | Meaning |
|---|---|---|
notebook_dir |
~/.suzent/notebook |
vault location |
memory_consolidation_enabled |
True |
master switch for the dream |
memory_consolidation_min_hours |
24.0 |
steady-state cadence (per attempt) |
memory_consolidation_min_facts |
20 |
steady-state volume gate |
memory_consolidation_interval_seconds |
1800 |
how often the gate is checked |
memory_consolidation_timeout_seconds |
600 |
per-run agent timeout |
memory_consolidation_max_days |
14 |
batch size; backlog beyond this triggers catch-up |
memory_consolidation_max_retries |
3 |
no-op batches before skip |
memory_consolidation_memory_max_lines |
200 |
MEMORY.md cap |
memory_consolidation_model |
None |
optional model override for the dream |
memory_dream_tools |
file tools + MemorySearchTool |
dream whitelist |
9. File-by-file review map
| File | What to look at |
|---|---|
memory/manager.py |
append-only process_conversation_turn_for_memories; deletions; promote_memory_md; _log_recalls; refresh_core_memory_facts retained |
memory/indexer.py |
MarkdownIndexer removed; lock + reindex_file_now + clear_and_full_reindex; per-fact _parse_archive_facts; watermark + tombstone logic in _check_and_update_impl/_reindex_file |
memory/markdown_store.py |
vault/page helpers, read_watermark/write_watermark_entry, recall + tombstone helpers |
memory/wiki_manager.py |
always-on bootstrap + zones |
memory/lifecycle.py |
always-create vault; shared indexer; core_watcher_gate; start/stop DreamRunner |
core/dream_runner.py |
new — gate, catch-up, retry-skip, proof-of-work, forked agent |
core/chat_processor.py |
_is_system_chat guard on B1+B2 |
memory/lancedb_store.py |
get_memory(id) helper |
routes/{memory,session}_routes.py |
/memory/consolidate; file-edit delete; reindex delegates to the indexer |
memory/models.py |
MemoryExtractionResult slimmed (no memories_created/updated) |
config/__init__.py |
knobs + /mnt/notebook default mount |
10. Known limitations / things to weigh
- Per-turn re-embed cost (§2 above) — the main perf trade; delta-indexing is the documented follow-up.
- Transient duplicate window — a fresh fact lives in both the raw log and (after consolidation) a page until the watermark advances; strictly better than the old silent drop.
- Notebook-page deletion is best-effort (§6).
recall_log.jsonlisn't truncated yet — it grows until a future cleanup (open question in the plan).- Single-user — one vault under
CONFIG.user_id(pre-existing assumption). - Phase 3 is runtime-exercised only — imports, DB/agent signatures, and the chat-reset are verified; the full forked-agent run happens at runtime (gated, or via
POST /memory/consolidate).
11. How to exercise manually
- Deploy, then
POST /memory/reindex {"clear_existing": true}(rebuild index from files). - Chat a few turns → check
archive/<today>.mdgrows andmemory_searchreturns the new facts (no drops). POST /memory/consolidate→ check the dream writes pages under~/.suzent/notebook/, appends awatermark=line tolog.md, and refreshesMEMORY.md.- Async memory tests need
pytest-asyncio(uv pip install pytest-asyncio);tests/memoryis green.
Summary
Closes #34. Supersedes #36.
Replaces the cosine-similarity dedup (
_deduplicate_and_store_facts, fixed0.85threshold) that silently dropped factual updates with an append-only write path plus an autonomous "dream" consolidation agent that tidies the daily logs into a notebook wiki. Cosine measures topical proximity, not factual identity — so no threshold can fix it; the fix removes the write-time dedup entirely and moves consolidation off the critical path.Full design + the multi-pass audit trail:
docs/03-developing/memory-consolidation-plan.md.What changed (3 phases)
Phase 1 — Append-only writes (the #34 fix)
_deduplicate_and_store_facts+ the0.85threshold (and the legacyprocess_message_for_memories/MarkdownIndexer). Facts are appended to the daily log and indexed per fact, with no write-time dedup — updates can no longer be dropped.chat_processor: forked/system turns (platform="dream", sub-agents) never feed memory extraction or transcript indexing (fixes a latent sub-agent recursion too).Phase 2 — Always-on notebook vault + watermark-aware indexing
CONFIG.notebook_dir(zones +schema/index/log); the/mnt/notebookmount becomes an optional redirect.CoreMemoryFileIndexeris now the sole LanceDB writer: indexes notebook pages, drops consolidated archives by alog.mdwatermark=token, skips tombstoned facts, constant importance (ranking = relevance + recency).Phase 3 — DreamRunner (autonomous wiki keeper)
BaseBrainbackground agent (forked, file-tool-whitelisted) consolidates daily logs into the vault, resolving correction / state-change (keeps history) / duplicate / genuine-conflict (escalates) cases. It owns the watermark (runner-written, proof-of-work-gated), runs catch-up sprints for backlogs with retry-then-skip, regeneratesMEMORY.md, and pauses the file watcher while it works.POST /memory/consolidatetriggers it on demand;POST /memory/reindex {clear_existing:true}rebuilds the index from files.Verification
suzent.serverroute wiring imports.MemoryExtractionResulttest updated for the slimmed model.src/.Migration
After deploy, run
POST /memory/reindex {"clear_existing": true}once to rebuild the index from files (memory + notebook). Raw daily logs are the immutable source of truth, so nothing is at risk.Notes for review
pytest-asynciois required to run the async memory tests locally (already a project dev need).🤖 Generated with Claude Code