feat(memory): a coding turn from two weeks ago can still be found, after the conversation forgot it - #504
Merged
brcampidelli merged 1 commit intoSep 17, 2026
Conversation
…ter the conversation forgot it Every finished Code-screen turn joins an append-only SQLite/FTS5 history index (`chimera/memory/history.py`, `<home>/history.db`): the message, the answer, the files the turn read or edited (off the same fold the replay endpoint shows), the tools, when, and whether it ran tainted. The session file trims at a user boundary and this never does. Written by the turn's finishing code through `redact`, never by the model; the LIKE degradation the memory store documents; the same tokenizer and function-word list; scoped by the same project key. `recall_history` (read-only, in the run-together set) prints dated excerpts with the files each turn edited or read, scoped to the current project unless asked for every project, with a window in days; a tainted turn is labelled in-line the way a tainted memory is on recall. Deleting a conversation or a project deletes its rows. An index that will not write logs and the turn goes on. Ten dictionaries for the Capabilities screen; the usage guide says which store is which in ten languages, translations restamped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
brcampidelli
deleted the
feat/a-turn-from-two-weeks-ago-can-still-be-found
branch
September 17, 2026 06:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A coding turn from two weeks ago can still be found after the conversation itself forgot it: every finished Code-screen turn joins an append-only SQLite/FTS5 history index, and a
recall_historytool searches it. Item 5 of the list audited on 2026-09-16 ("SQLite long-term memory — remembers the login function from two weeks ago").Why
A code session keeps the model's own message list and trims it at a
userboundary (DEFAULT_MAX_MESSAGES= 200, about thirty tool-using turns). That is the right bound for a transcript re-sent to a provider every turn, and the wrong bound for "what did we decide about the login function two weeks ago?" — the turn that decided it is the first one trimming drops. The memory store is the wrong place to look: a memory is a fact the agent chose to keep, not a record of what was asked. Before this PR there was no SQLite index of conversation history at all; the marketing sentence described a store that did not exist.How
chimera/memory/history.py—HistoryIndexat<home>/history.db: one row per completed turn (turn_id,session_id,project,asked_at,tainted,asked,answered,files,edited,tools). FTS5 virtual table,LIKEfallback when FTS5 is not compiled in (the degradationsqlite_store.pyalready documents), the same tokenizer and function-word list the memory store uses, scoped by the sameproject_key. Terms are OR-ed and ranked (bm25), quoted sologin_userreacheslogin_user. Written throughredact.recordreplaces on the sameturn_id;forget_session/forget_sessions; one index per home per process (history_for, likejobs_for), one connection under a lock because the turn's thread writes and the agent's thread reads.chimera/tools/history.py—RecallHistoryTool(query,days,k≤ 10,everywhere). Dated excerpts centred on the first matching term (one excerpt rule for both search paths),edited:andread:told apart, the project named only when the search left it. A tainted turn carries[this turn read untrusted content — weigh its answer accordingly]in-line, the way a tainted memory is labelled on recall. Says "nothing recorded yet" vs "no match" vs "no match in the last N days" distinctly.default_registrymounts the tool (so it passesrestrict_registryand the governance wrappers like everything else);PARALLEL_READ_TOOLSgains it;_verify_and_finishincode_api.pyrecords the turn after the transcript is saved, reading the files off the same fold the replay endpoint shows (exchanges_from_messages→files_of_exchange), witheditedfrom the turn'son_editandtaintedfrom the receipt. Both the native and the external-provider (ACP) paths go through it. A record that will not write is logged and the turn goes on.DELETE /api/code/sessions/{id}andDELETE /api/code/projectsforget the rows (ids collected fromlist_metabefore the files go, because the index is keyed by session id). The screen says the conversation is gone; an index that still answered about it would make that a lie.tools.desc.recall_historydictionaries;docs/usage.mdsays which store is which, translated in nine languages and restamped.Not done, on purpose
CHIMERA_MEMORY_BACKEND=json). The audit note said to measure JSON vs SQLite recall before flipping it; that measurement is a separate, pre-registered bench and the next PR, not a line in this one.chimera chatsessions (prose pairs inSessionStore) are not indexed — this covers the Code screen, which is the conversation the item describes.Tests
tests/test_a_turn_from_two_weeks_ago_can_still_be_found.py(13): found by message, answer and file; project scoping andeverywhere; a window in days; function-word query finds nothing; sameturn_idtwice is one row; forgotten session is gone; a pastedsk-…key is not in the.dbbytes; tainted label present/absent; the LIKE fallback answers the same questions on the same data;files_of_exchange; the tool's output shape and its three "nothing" sentences;recall_history ∈ PARALLEL_READ_TOOLS; through the app: a turn is indexed with the files its tool calls named, the tool the app's own registry mounts finds it, the rows survive the session file being unlinked; deleting one conversation and then the whole project forgets exactly those rows; an index whoserecordraises does not fail the turn.Sabotage:
history.record(replaced by a no-op → the app test fails;forget_sessionremoved → the delete test fails; both restored.test_document_args_match_the_toolsclassifiesdays/k/everywhere;test_tool_descriptions_reach_the_userpins the ten dictionaries; the translation-provenance guard passes on the restampedusage.md.Full gate on a clean copy in WSL: ruff clean, mypy clean (360 files), 6529 passed, 18 skipped, 10 xfailed in 183 s. OpenAPI unchanged (no new endpoints).
🤖 Generated with Claude Code