My Memory System Almost Forgot Why It Existed

My Memory System Almost Forgot Why It Existed

I built a memory system so I would stop paying for the same mistake twice. Most projects turn up a trap eventually, some detail that costs real time or real money once and has to survive past the moment I learned it. The problem was never capturing them in a way that if a similar situation should occur months later, it's readily available to assist when I actually need it.

So I built a git-backed store: markdown notes, a hot layer every session reads first, and a local model that runs at 3am, folds the day's notes into the permanent record, and proposes the result on a review branch before any of it touches main. I trusted the review step to catch anything the model got wrong. I did not expect the thing that needed catching to be the system itself.

Summarization Optimizes For the Wrong Signal

Reviewing the branch on September 10, I found the note on warden, my systemd-timer watcher, had been quietly rewritten on two separate nights, September 7 and September 9. Both times, the consolidator dropped the line that says: strip 'ANTHROPIC_API_KEY' from the subprocess environment, or Claude Code prefers the metered key over the subscription login and bills credits anyway.

That is not a hypothetical trap. Two days before that review, I had run warden as a bare process with that key still exported, and it burned through 9.45M input tokens on Opus before I noticed the bill. The one sentence that would have stopped me from doing it again was gone, twice, from the record I built specifically to stop me (and Mia) from doing it again.

Digging in, I learned that the model did exactly what a summarization prompt asks for: read the note, keep the meaning, make it shorter. A trap like that is one specific, low-repetition sentence sitting inside a longer paragraph describing what warden does and how it is built, and specific sentences compress worse than they look like they should, because nothing else in the note reinforces them. The model kept the parts that read like a description. It cut the parts that read like a footnote, which is exactly backward, since the footnote was the only sentence in the note anyone would ever need again.

I would flag the same failure in a squashed commit history. Squash ten commits into one and the diff survives; the commit message that said "revert: this broke prod, do not re-enable without X" does not. The code ends up identical either way. What disappeared is the one line telling the next person why the state is what it is, and that line was never filler, it was the entire reason to keep history instead of just the latest file.

A consolidation prompt has the same blind spot aimed at prose instead of code. It optimizes for a cleaner document, and a warning read without the scar behind it looks exactly like the kind of line a cleaner document does not need.

A Minimum-Retention Floor Catches What Judgment Can't

Telling the model to please keep the traps did not survive a second rewrite. So I stopped asking it to know which lines mattered and made the pipeline measure instead:

def retained_fraction(old_text, new_text):
    old_body, new_body = body_of(old_text).strip(), body_of(new_text).strip()
    kept = sum(b.size for b in difflib.SequenceMatcher(
        None, old_body, new_body, autojunk=False).get_matching_blocks())
    return kept / len(old_body)

Every proposed update to an existing note runs through this before it reaches the review branch. Below MIN_RETAINED, 0.7 (70%), the operation is rejected outright, with the reason logged to Telegram: "update keeps only X% of the existing note; append to it instead of rewriting." The rule going forward is simple. The model can add to a note. It cannot rewrite one it has not been shown in full.

The fix is not a model that remembers to protect a trap next time. The fix is a function that counts how many characters of the old note survive in the new one, computed the same way `git diff` counts a line change, and refuses the update outright if the count falls under 70%. I do not need the model to recognize a scar when it sees one. I need the pipeline to notice when one is likely to go missing, and stop.

Subscribe to ClearText

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe