Agent memory, measured

The read half

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.

49.2sto write one fact — six LLM calls
1.4sto recall a project's memory — zero LLM calls
0hooks that performed a read, before this

Two memories, one of which shows up

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 memoryGraph memory
Size~29 lineshundreds of facts, unbounded
Arrives byinjection, automatica call someone has to make
Cost per turnpaid on every turn, foreverzero until queried
Retrievalall of it, alwaysranked by relevance to the question
Handles being wrongedit the filefacts carry validity in time

Where the machinery stopped

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.

before after A thread opensnothing has been asked yet Standing instructions load~750 lines, every session File memory injects itselfno call required The first messagethe one that sets the topic A prompt hook firesand reads no memory at all machinery ends — the rest is judgment Query the graphonly if the model remembers to Work beginswith the context, or without it A thread opensnothing has been asked yet Standing instructions load~750 lines, every session File memory injects itselfno call required The first messagenow also the retrieval query The recall hook firesresolves the project, queries it Live facts injected1.4s, no model judgment involved Work beginsalways with the context fires on its own fires only if remembered
The only structural change is one hook. Everything else in the sequence was already automatic — which is what made the gap so easy to miss.

The asymmetry that made it obvious

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.

seconds per operation write a fact 49.2s recall a project 1.4s 0 50
A read is roughly 35× cheaper than a write. The unenforced half was the cheap one — which is the wrong way round for any system you want people to use.

The five laws of writing a fact

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.

I

Write at the moment of decision

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.

II

Only what changes what a future session does

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.

III

Every fact names its own subsystem, inside its own text

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.

IV

Restate a rule whole; never append an amendment

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.

V

Never write a clock time into the text of a 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.

Why this belongs inside the agent, not beside it

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.

01

Determinism beats good intentions

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.

02

Context budget is finite; the graph is not

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.

03

The question is the query

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.

04

Sessions die; decisions shouldn't

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.

05

Time is a first-class field

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.

06

One resolution, both directions

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.

What the same work looks like with no graph at all

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 staleness column is the one that actually bit us

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.

Three things we only learned by measuring

All three were discovered while writing the rule down, and all three contradicted the documentation that already existed.

"Free, instant, no LLM" was none of those

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.

An amendment supersedes its own parent

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.

A timestamp in prose is a boundary

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.