Explainer 1 ยท Memory
How our AI agents remember
The short version. An AI agent forgets everything when a chat ends. So we give it a folder of short notes, one fact per note, and a one-page index it reads at the start of every session.
When it learns something worth keeping, like "this person likes short replies", it writes a note. Next session, it already knows.
The hard part is keeping the folder honest. Notes go stale, repeat each other, or turn out to be wrong. Most of this page is about that.
Going deeper
The shape of a note
Each memory is its own markdown file with a small frontmatter block. The type is one of four: user (who the person is and how they work), feedback (a correction or a thing that worked), project (state of ongoing work), reference (where something lives). The body says what the fact is, then Why and How to apply. The why matters most. A rule without its reason gets applied to the wrong cases. These examples are invented:
--- name: prefers-short-replies description: Keep replies to a few sentences unless asked type: feedback --- Keep replies short and lead with the answer. **Why:** Long answers got skimmed and the one question inside them was missed. **How to apply:** Put detail behind a link. For tone, see [[plain-words-over-polish]].
Cross-links use [[name]], so a note can point at the notes it depends on or contradicts.
The index is the part that matters
One file, MEMORY.md, loads into every session, and that loading has a hard ceiling. The agent harness we use reads only the first 200 lines or about 25 KB and drops the rest without complaint. Anything past the cut might as well not exist. So the index stays short on purpose: one line per memory, a link and a few words about why it matters, with the rules that must never be missed placed first. We aim for a little under the limit so a few additions do not push the tail off.
A second file, a full catalog, lists every note and is not loaded automatically. It exists so no note is an orphan that nothing points at.
What not to save
- Anything the code or the git history already records. If it can be read from the repository, reading it is the better source.
- A fact that is already saved. Update the existing note instead of adding a second one.
- Notes that turn out wrong. Delete or correct them. A wrong memory is worse than none, because the agent trusts it.
Recalled does not mean current
A memory is a snapshot from the day it was written. Before acting on one that names a file, a command or a state of the world, the agent checks that it is still true. A note saying "the config lives in X" is a hint to go look, not a fact to build on.
The tooling that keeps it healthy
The checker. A small command checks two failure modes: the index growing past the load limit, and notes that no index points at. It can rebuild the full catalog from the frontmatter and exits non-zero on any problem, so it can sit in a hook or a nightly job.
The consolidation pass. Over time a pile of notes rots in predictable ways. A consolidation command finds it in cheap stages:
- Parse every note.
- Find dead references: paths, commands and modules that no longer exist, checked against the real filesystem. No model involved, and it skips anything it cannot resolve rather than guessing.
- Nominate near-duplicates with a plain lexical similarity index. It only needs good recall, because the next step reads the real text.
- Nominate one-off incident notes that never generalized.
- Have a model read each nomination in full and rule on it. "Unsure" is an allowed answer and leaves the pair alone.
The command is read-only. It writes one reviewable proposal and changes no memory. A separate apply step does the work, and it is cautious by design. It dry-runs by default, only executes a human-marked merge or retire, moves retired notes into a holding folder instead of deleting them, takes a snapshot first, and can undo the whole run. The model step never runs from a scheduled job, only with a person behind it.
There is also a check before writing: it lists near-duplicates with their age and where they came from, and refuses a strong match. That is how "update instead of duplicate" gets enforced instead of just hoped for.
What went wrong before we had this
Two posts below tell the real stories. One is an index that silently lost everything past line 200. The other is a pass that found zero duplicates because the notes contradicted each other instead of repeating each other.