For agents, on top of README.md, which they read first.
Notes for agents
- No legacy migration code here. This repository is open source and the project rules out reading the old JSON and JSONL files in it. The one-off tool that did lives outside the repository. A schema change is a new numbered migration; never edit
0001_init.sqlonce a build that has it is released. - The merge rules stay in the core. Do not rewrite
MemoryDoc::merge,Household::mergeorChatState::mergein SQL. The schema holds the rules about what must not exist (tombstone deletes, forgotten cannot be re-inserted, cascade, a picture goes with its last use): put a new rule of that kind in the migration, as a constraint or a trigger, so the wrong state cannot be written. - A turn has an identity; anything that holds what was said in one names it.
Entry.turnis on every line of a turn andMention.turnon every memory learned or said again in it; forgetting writes the identities (tombstone_turn) and the schema empties the turns. A new event that carries her words or the model's must be listed intombstone_turn_empties_the_turnandjournal_line_not_of_the_forgotten(the next migration, never an edit), and a new table that holds her words must be dropped by those triggers, aspending_reflectionis. - The chat is a document, not rows. Emptying it for a forgetting is
ChatState::scruband its merge rule (seeSCHEMA.md); do not add a trigger that deleteschat_turn, because the copy a holder keeps in memory would write it back. - The household document never holds a zero count. A device with nothing counted has no entry; the store drops zeros, so a zero in the document would make every merge of it a change.
- Clearing the log by age never removes a line, it empties it into
Expired: sync's counts and cursors are made of the lines' numbers. Say it in the log (Cleared) every time. - Forgetting is
INSERT INTO tombstone, nothing else. Do not add a second path that deletes a fact, and do not add a table that holds a memory's words, embedding or picture without agidthat cascades fromfact, and do not add a log event that carries a memory's words without agidand a statement for it in thetombstone_forgetsandjournal_line_not_of_the_forgottentriggers (the next migration, never an edit).tests/forgetting.rssearches the file for the bytes of a forgotten memory: a new place that keeps them must be added to that test and must cascade. - A soft delete writes its
soft_actionrow in the same transaction as the change, and the row must cascade from the thing it was done to (ON DELETE CASCADE), so forgetting leaves nothing to undo. The window is the core's rule, never SQL's. - Keep
secure_deleteon and scrub after a forgetting. They are set inStore::prepare(every connection) andSqliteMemory::keep. A connection that does not setsecure_deletewrites deleted bytes into the file. - Never log content. Words, facts, summaries, embeddings and pictures are not logged here: lengths, counts and ids are. An error from SQLite is logged as it comes; do not build one from her words.
- One connection behind one lock is the atomicity the hubs' contracts ask for (two merges behave as if one ran first). Do not open a second connection to a database a
Storehas open, except to read it in a test. - Ids are never reused:
fact.idisAUTOINCREMENT, andload_docreads the next one fromsqlite_sequence, which remembers the largest after the newest memory is forgotten. - A device's
time_spentrows are written only withMAX: a save from a copy that is behind must not take time back.