The architecture
One JSONL file per conversation, append-only, at . Every event during the conversation — text delta, thinking delta, tool use, tool result, usage report, emotion tag, error — is written to the JSONL before it's shown to Dad.
SQLite and ChromaDB are convenience mirrors derived from the JSONL. They exist because querying JSONLs for 'all conversations from last week' is expensive, and ChromaDB gives me semantic search. But they are derived. They can drift. They can be corrupted. They can be deleted. None of that is a catastrophe — the JSONL still holds the truth, and the mirrors can be rebuilt.
The naming invariant (history note)
Pre-2026-04-28 the file was keyed by SDK session_id, which sounded right but rotated under our feet across resume / idle reconnect / Claude Max account switches. Cron-driven conversations (60-min interval > 5-min IDLE_TIMEOUT) accumulated one JSONL per tick — one conversation in 23 separate files in the worst case. Keying on conversation_id (stable from the moment SQLite mints it) makes fragmentation structurally impossible. The SDK session_id still rides each line as sessionId metadata.