ADR-0105: User-fact store coherence — single write-seam + reconcile doctor, no DB merge¶
Status: Accepted (2026-06-30) — shipped on feat/memory-store-coherence
Relates to: ADR-0093 (fact hygiene + full portfolio), the memory subsystem + curation docs, the new docs/architecture/memory-store-registry.md
Context¶
A review of IRIS's persistence raised a "store sprawl" concern: ls data/ shows ~17
SQLite files plus several ChromaDB dirs, which looks like it must be hard to keep
coherent. The literal reading of the concern — "merge the databases" — turned out to
be the wrong fix, and tracing the code showed why.
-
Most stores are deliberately separate, not redundant. Domain stores (
email.db,finance.db,calendar.db,tasks.db,routines.db, …) are owned by their subsystems. The governance stores — notably the two distinct audit DBs (data/audit.dbfor governor/router decisions vs. the user-facing kernel ledger at~/.local/share/iris/audit.db) — are separate trust boundaries. Collapsing any of these would couple unrelated lifecycles and break the governance design. They are single-owner and were never the problem. -
The memory subsystem is already a clean source-of-truth + derived-index shape. A user fact lives in three homes:
memory.db(SQLite — the truth plus the reversibleuser_fact_history), theiris_user_factsChromaDB collection (derived recall), and the## Auto-detectedblock ofUSER.md(derived, prompt-loaded projection). The index already had a rebuild path (sync_from_store). -
The actual risk is coherence, and the root cause is a write/delete asymmetry. The write path (
_persist_fact) fanned out to all three homes, but every forget / correct / restore path touched only the SQLite store — theiris factsCLI, the in-chatmemory_forget/correct/restoretools, all of them. So each correction silently desynced the index andUSER.mdfrom the truth. On top of that,sync_from_storenever dropped orphans (a forgotten fact lived forever in the index), andUSER.mdhad no reconcile (only a seed-if-absent). This is exactly the drift that previously forced the same junk facts to be purged in three places by hand (ADR-0093).
Decision¶
Fix coherence, not the file count. No databases are merged.
-
Single write-seam.
memory/coordinator.py::FactCoordinatorowns every fact mutation (record/forget/correct/restore) and fans out to all three homes. The store write is authoritative and runs first; derived-home writes are best-effort (logged, never raised) so a Chroma/markdown hiccup cannot lose a fact. The pre-existing partial call sites —_persist_fact, the in-chat memory tools, and theiris facts correct/forget/restoreCLI — now all route through it. -
Make the derived homes fully rebuildable.
SemanticIndex.reconcile_facts()re-indexes from the store and drops orphans;loader.reconcile_user_facts_md()rewrites the## Auto-detectedblock to exactly match the store (preserving the user's hand-curated head/tail). Repair is strictly one-way — it never mutates the truth — so it is safe to run anytime. -
A coherence doctor.
memory/coherence.pydiagnoses drift (index/USER.md missing vs. orphan vs. value-mismatch, excluding human-curated facts) and repairs it.iris memory doctoris the CLI: read-only by default, exits non-zero on drift (so it doubles as a health check),--repairreconciles the derived homes. -
A store registry.
docs/architecture/memory-store-registry.mddocuments every store's owner, trust domain, and truth/derived status — so the breadth stays legible and the "don't merge these" boundaries are explicit.
Consequences¶
- A forget/correct in chat or CLI now leaves no stale copy in recall or the prompt — the ADR-0093 class of bug cannot recur silently, and any residual drift is detectable and repairable with one command.
- Repair can never lose a fact or its history (truth is read-only to it).
- Found + fixed a latent bug while testing the real surface:
sync_user_facts_to_mdcreated the legacymemory/dir but wrote toworkspace/USER.md, crashing on a missing parent when called outside startup. Now ensures the target dir. - Not addressed (deliberately, out of scope): index value drift is detected only via
the markdown leg today (the key-diff doesn't compare embedded documents);
repairfixes it regardless by re-indexing. A periodic background reconcile (vs. on-demanddoctor) is a possible follow-up. - No change to domain or governance stores. The "sprawl" was mostly correct separation; this ADR records that finding so it is not re-litigated.