Agent memory, measured
An agent's memory has two halves: getting a fact in, and getting it back out. We instrumented both on a live system. The expensive half had four laws and a rulebook. The cheap half had one sentence and nothing enforcing it.
A coding agent typically has file-based memory — a markdown file loaded into context at session start. It is small, it is always there, and it costs tokens on every single turn forever. That is the whole design: it arrives whether or not anyone asks for it.
A knowledge graph is the opposite. It holds every durable decision a team has ever made, scoped per project, retrieved by relevance. It costs nothing until queried. And that last property is exactly the trap: a store that costs nothing until queried is a store that gets queried only if something remembers to query it.
| File memory | Graph memory | |
|---|---|---|
| Size | ~29 lines | hundreds of facts, unbounded |
| Arrives by | injection, automatic | a call someone has to make |
| Cost per turn | paid on every turn, forever | zero until queried |
| Retrieval | all of it, always | ranked by relevance to the question |
| Handles being wrong | edit the file | facts carry validity in time |
This is what actually happened when a thread opened, traced from the hook configuration rather than from the documentation describing it. Everything blue fires on its own. Everything orange fires only if the model remembers — while competing with roughly 750 lines of other standing instructions.
Both operations were timed against the running service, not estimated. A write resolves entities, generates embeddings, and runs a temporal pass that decides which existing facts the new one supersedes — six language-model calls in total. A read is embeddings and a graph traversal. No completion at all.
Retrieval is only half of it. A store you can read from reliably is worth nothing if what goes in is noise, or if each write quietly damages what is already there. These are the rules as they are actually written in the operating instructions — every one of them earned by something going wrong first.
Never saved up for the end of the session. A session that dies mid-work must not take the decision with it, and the write is synchronous — it does not return until the fact is committed — so a crash ten seconds later still leaves it behind.
A wrap that never comes has saved nothing.
A decision and its reason, a constraint discovered the hard way, a mechanism that is not what it looks like. Not progress, not narration, not anything the code or the version history already says.
Search ranks by relevance rather than recency, so every junk fact permanently dilutes recall of the good ones. A swamp is not a neutral cost — it is the failure mode.
This is self-defence, not style. Each write runs a temporal resolver across semantically similar facts and marks superseded the ones it reads as contradicted — including facts you never touched. Say "this concerns the publish path, not the layout" in the fact itself and the resolver has no contradiction to find.
Then read every record the write returns. A freshly superseded fact that isn't yours is collateral damage, and you repair it by restating that fact scoped — not by leaving it dead.
An amendment that opened by explicitly disclaiming any change to its parent's timing or scope still marked that parent superseded. The resolver reads "amendment to X" as "X is superseded", whatever the rest of the sentence says.
One complete fact replaces one complete fact.
The corrected rule, rewritten in full, arrived already expired — the resolver had taken a timestamp inside the prose as the moment the rule stopped being true, and back-dated it. Rewritten with dates in words, the identical fact landed clean and damaged nothing.
Laws III through V were all discovered in a single afternoon, by watching the rule get destroyed twice while it was being written down.
Anyone can give an agent a memory tool and tell it to use one. The interesting part is what changes when retrieval stops being a decision the model makes and becomes something the harness does to it.
A hook runs outside the model's judgment. It cannot get distracted by a long task, outvoted by 750 lines of other instructions, or forget under load. The instruction that had been in the rulebook for weeks was skipped, in the very session that was writing memory rules. The hook has not missed once.
Anything loaded at session start is paid for on every turn of every session forever, so a file-based memory has to stay small and generic. Graph facts cost nothing until they are relevant, which means the store can hold years of decisions and still only ever spend tokens on the handful that bear on the question being asked.
Retrieval ranks by relevance, so the best possible query is the operator's own first message. The hook passes it through verbatim. No summarising step, no guessing at keywords, no extra model call to decide what to look for.
The write is synchronous — it does not return until the fact is committed. A session that crashes ten seconds later still leaves the decision behind. That is the entire argument for writing at the moment a decision is made rather than saving it for a tidy summary at the end, because the tidy summary is exactly what a crash eats.
Facts carry validity intervals. When something is superseded it is marked, not deleted, so history survives while retrieval returns only what is true now. The hook filters superseded facts before injection: feeding an agent a dead fact is worse than feeding it nothing, because it will act confidently on it.
The hook picks which project's memory to read, and that same choice governs where the session writes. Resolve it twice — machinery for reads, judgment for writes — and the two eventually disagree, filing a fact into a drawer the next session never opens. That is the original failure with extra steps.
Worth asking honestly, because a graph is not free — it is a service to run, a 50-second write, and five laws to obey. The comparison that matters is not graph versus nothing but graph versus the prose files nearly everyone already uses: a markdown memory file, a folder of handover notes, a long instructions document.
| No memory | Prose files only | Graph + automatic recall | |
|---|---|---|---|
| Where knowledge lives | the last session's transcript | a memory file, plus handover documents | facts, scoped per project |
| How a session gets it | the operator re-explains | the small file auto-loads; the documents are opt-in reading | retrieved by relevance to the first message |
| Cost per turn | none, and no benefit | every loaded line, on every turn, forever | only the handful of facts that bear on the question |
| As it grows | n/a | must be curated down by hand, or it eats the context window | grows freely; retrieval stays constant-size |
| When a fact goes stale | n/a | nothing marks it — it keeps reading as true until a human notices | superseded automatically, and filtered out before injection |
| Several projects | n/a | one shared file, or a folder nobody opens | scoped per project; the wrong project's facts never surface |
| Failure mode | repeats old mistakes confidently | acts confidently on stale prose | a swamp, if law II is ignored |
The internal documentation stated that writing a fact was "free, instant, no LLM." It was none of those — it takes 49 seconds and six model calls. That claim had sat in a prose file for weeks and propagated into other documents, because prose has no validity interval. Nothing in a markdown file can mark a sentence superseded. It simply keeps reading as true until a human happens to time it.
The same claim, held as a fact, behaves differently. When the corrected version was written, the old one was marked superseded in the same operation — and the recall hook filters superseded facts before injection, so the wrong claim can no longer reach a future session even if nobody ever gets around to deleting it. That is the difference between memory you have to garden and memory that expires on its own.
The second difference is quieter. Handover documents are read when someone chooses to read them; on this system that is explicitly opt-in, and a session that never opens one is behaving correctly. Retrieval by relevance has no such gap — the facts arrive because the question was asked, not because anyone remembered they existed.
None of which argues for putting everything in a graph. Operator preferences, house style and standing instructions belong in the file that always loads — they are relevant to every turn, so relevance ranking has nothing to contribute. The graph is for the specific, the dated, and the hard-won: the things that are decisive twice a month and invisible the rest of the time.
All three were discovered while writing the rule down, and all three contradicted the documentation that already existed.
The internal docs described a fact write as a cheap direct operation. It takes 49 seconds and six language-model calls. Nobody had timed it; the claim had simply propagated. Every performance assumption in a system deserves one stopwatch.
Writing "an amendment to rule X, which does not change X's timing or scope" caused the resolver to mark rule X superseded — the disclaimer notwithstanding. Restate a rule whole; never append a patch to it.
The corrected rule, rewritten in full, arrived already expired. The resolver had read a clock time inside the sentence as the moment the rule stopped being true and back-stamped it. Rewritten with dates in words, the identical fact landed clean.