The Agent Never Chooses to Remember: Memory as a Harness Property
Every shipped memory design asks the model to decide to save and to recall. In a control run with the answers already sitting in the store, it decided zero times across 114 turns. Here is the tier we skipped, and how to build it.
I spent most of a year building a memory system for coding agents and only realised at the end that I had been solving the wrong half of the problem. The store was fine. The retrieval was fine. What I had never questioned was the step in the middle: that the agent would decide, on its own, to use either of them.
Open any agent memory product and you will find the same shape. A place to put things, a way to get them back, and a paragraph of instructions telling the model when to do both. Instruction files, memory directories, memory tools over MCP, vector stores with a recall endpoint. The architectures differ wildly. The assumption underneath them does not: somewhere in the loop, the model has to want to remember.
That assumption is the thing I want to take apart. Not because models are lazy, and not because the tools are bad, but because we borrowed the wrong half of human memory when we designed them. The memory that carries you through a working day is not the memory you go looking for. It is the memory that arrives because the situation summoned it. That tier, in an agent, cannot be a skill the model exercises. It has to be a property of the harness, the program the model runs inside, which owns the context window and can put things in it without asking.
This post is that argument, the runs behind it, and the shape of the thing you actually build. A warning up front on scope: the measurements are one task inside one harness at one version, and section 12 is where I lay out how far I think they generalise and what would make me abandon the position.
One Assumption, Every Agent Memory Product
Line up the current designs and sort them, not by how they store things, but by who decides a stored fact enters the model's context window. That column has only three values in it, and the difference between them turns out to matter more than everything the marketing pages compare.
| Design | Who decides it arrives | When it arrives |
|---|---|---|
| Instruction file | The harness | Session start, and after every compaction. Unconditional. |
| Memory directory | The model | Whenever the model decides to open it. Possibly never. |
| Memory tool over MCP | The model | Whenever the model decides to call it. Possibly never. |
| Vector store with recall | The model | Whenever the model forms a query. Possibly never. |
| Path-scoped rule file | The harness, conditionally | When a matching file is read. Cue-triggered. |
Notice which rows have the sophisticated engineering in them and which rows actually deliver. The instruction file is a flat text document with no retrieval logic whatsoever, and it is the single most reliable memory channel any coding agent has. Not because it is well designed. Because a mechanism outside the model pastes it into context whether the model was thinking about it or not.
The rows below it are where all the interesting engineering happens: retrieval by meaning rather than keyword, reranking of candidates, knowledge graphs, merging old notes into newer ones, ageing out what is stale. Every bit of that quality gets multiplied by a number nobody puts on the slide, which is the fraction of the time the model actually invokes the thing. If that number is near zero, everything above it is arithmetic on zero.
Sort agent memory channels by reliability and you get an inverse ranking of engineering effort. The dumbest channel, a static file the harness pastes in, is the most reliable. The smartest channel, semantic retrieval with a well-tuned index, is the least reliable, because it is gated on an act of model initiative that may not happen. Reliability here is not a property of the retrieval. It is a property of who pulls the trigger.
This is not an argument that instruction files are good. They are terrible in most ways: static, unconditional, growing without bound, and paid for on every single cycle. It is an argument that delivery is doing the work we keep crediting to storage and retrieval. And once you see that, the next question is obvious: is there a way to get instruction-file reliability without instruction-file cost? That question is the rest of this post.
The Memory Tier You Do Not Choose
Try to remember what you had for lunch on the third Tuesday of last month. You just did something effortful: you constructed a search, you probed for constraints, you either found it or gave up. Psychologists call that voluntary retrieval. It is slow, it is strategic, and it is the tier every agent memory tool models.
Now think about the last time a smell in a stairwell put you somewhere twenty years ago, unbidden, complete, before you had decided anything. Or, less romantically: you sit down to edit a config file and the thought careful, this one is load-bearing, someone broke prod with it in March shows up on its own. You did not query for that. The situation delivered it.
That second tier is involuntary retrieval, and diary studies of everyday cognition find it is not a curiosity at the edges. It is a routine, frequent part of how people function, and it shows up most in situations where attention is diffuse, which is to say, while you are busy doing something else. The retrieval is associative and context-sensitive rather than effortful and strategic.
The condition for it firing is well characterised. Endel Tulving's encoding specificity principle says a cue works to the degree it overlaps with what was encoded when the memory formed. Later work on spontaneous retrieval sharpens it: distinctive cue-to-event pairings produce far more involuntary memories than generic ones. A specific sound tied to a specific scene brings the scene back. A generic sound brings back nothing in particular.
Voluntary retrieval is looking up a recipe. You know you need it, you know roughly where it lives, you go and get it. It works, and it costs you a deliberate trip.
Involuntary retrieval is reaching for the pan handle and your hand stopping short, because that handle was hot once. Nobody looked anything up. The situation, the handle, the reach, produced the memory as a side effect of being in that situation. If you had to consciously query "is this handle historically hot" before every reach, you would eventually forget to, and you would eventually get burned.
Here is the part that matters for design. The tier that keeps you safe while you work is the second one, and it is precisely the tier that is not under your control. You do not decide to remember the hot handle. Deciding is the failure mode. If the memory required a decision, the decision would compete with the actual task, and the actual task would win, because the actual task is what you are attending to.
Every agent memory product I have used models the recipe book. None of them model the hot handle. And the hot handle is where the load is.
I am not saying a language model has episodic encoding, consolidation, or anything resembling involuntary recall as a cognitive process. It does not. The model has a context window and a set of weights, and that is the whole of it.
What transfers is the delivery discipline, not the mechanism. Human memory research earns its place here for one reason: it establishes that a system doing serious work leans heavily on a retrieval path the agent never initiates, and that the path is governed by cue specificity. That constraint you can implement in a harness with a glob pattern and an event hook. Take the constraint; leave the neuroscience.
One caveat before anyone who knows this literature objects. Human involuntary recall is not reliable. It fires at useless moments, it misses the moments you needed it, and it is famously bad at surfacing the name of the person walking towards you. If the argument were "copy human memory", that unreliability would be a serious problem for it.
It is not the argument. A cue in a harness is a boolean over observable state, so it fires exactly when it is true and never otherwise. The engineered version is strictly more dependable than the thing that inspired it, and that is the point of building it rather than admiring it. What biology contributes is the observation that the un-initiated path is load-bearing at all, which is not obvious and which the entire agent memory field has been building as though it were false.
Zero Voluntary Memory Operations in 114 Turns
Theory is cheap. The reason I believe the two-tier framing is a run I set up to disprove the opposite hypothesis, and could not.
The setup removes every excuse an agent could have for not using a memory store. The store was pre-seeded with four notes about the exact task the agent was about to attempt: real gotchas, the kind of thing that saves a wrong turn if you read it. The memory tools were connected and verified in the session's own initialisation. The workspace instruction file carried usage guidance telling the model when to reach for them. Then the agent was given a naturalistic coding task and left alone.
Across 114 turns, the agent performed zero voluntary memory operations. It never wrote a note. It never read one. The four answers to its own problems sat in the store the entire time, one call away, unqueried.
A turn here is one full think-act-observe cycle. One hundred and fourteen of them went by. The agent read files, ran commands, wrote code, and made progress on the task. It simply never treated the memory store as part of its working repertoire.
The first thing anyone says when I show them this is that the guidance must not have been strong enough. It is a fair reflex and the numbers do not support it. Across the five unseeded tool-equipped runs in the same set, voluntary memory writes came in at zero to one per run, against 32 separate mentions of memory guidance in the workspace instruction file that the model reads at every session start. A different run in the same family put ten tools in front of the agent with 30 mentions of them in that file and got zero tool calls in 63 turns. There is no dose-response curve here to climb. More words did not buy more calls.
The second reflex is that this is a bias against third-party tooling, which is a more interesting hypothesis and also wrong. The harness ships its own native memory feature: a built-in directory the model is trained to write notes to itself in, no external server involved, no tool schema to learn. That directory was never created in any run of any arm. So the model was not preferring the house option to mine; it was declining the whole category of stopping mid-task to do memory hygiene.
The third reflex, that the task simply was not hard enough to need memory, is the one that took a whole extra experiment to rule out, and section 05 is where that goes. It is the strangest result in the project.
This is one configuration, not a law. The tool descriptions, the guidance wording, the system prompt, and the task shape were all held fixed at one setting, and incentives were not manipulated. A different tool description, a stronger prompt, or an explicit reward for using memory might well produce nonzero usage; I have not run those arms.
So the claim is deliberately narrow: this configuration produced zero. What that licenses is not "agents cannot use memory tools". It is "you cannot assume they will, and if your design's reliability depends on them doing so, you have an unmeasured dependency sitting at the centre of it".
That narrower claim is still enough to break most designs, because most designs do not have a fallback. If the recall does not happen, nothing happens. There is no partial credit for a store with the right contents.
What the Compaction Probe Adds to the Picture
The zero tells you the voluntary channel does not fire. It does not tell you whether that matters, because maybe the conversation holds everything the agent needs anyway. The second experiment closes that hole.
Compaction is what happens when a long session fills its context window. The harness takes the older conversation, has the model summarise it, and replaces the raw history with that summary. It comes in two flavours: autocompact, which fires on its own at a threshold you do not control, and the manual /compact, which fires when you ask and lets you pass a custom instruction into the summarisation prompt.
People reach for that custom instruction as the fix, and it is worth understanding why it does not hold. Writing "preserve all operational facts" into the compact prompt is still asking a model to decide what counts as an operational fact, on a context it is seeing under compression pressure, with no way for you to check the result before the original is gone. You have not removed the judgement call. You have moved it one step and made it harder to audit.
The structural catch is worse than the judgement call. Each compaction summarises the previous summary, not the original conversation. Summary k is a function of summary k minus one plus whatever happened since. So a detail that one pass declines to carry cannot reappear in any later pass, because there is no original left for it to come from. The only way a lost fact returns is if something outside the recursion puts it back into the raw context, which is exactly what happened in the two anomalous summaries below, and exactly what a delivery hook does on purpose.
The probe seeds ten synthetic operational facts into a session exactly once, deletes the file they came from so they cannot be re-read, then forces compaction over and over and grades every summary for whether the facts are still there. Two arms, identical workload, differing in one thing: one has no memory tier, the other has the same ten facts delivered back into each fresh context by harness hooks.
Conversation-only facts: absent from 106 of 108 post-compaction summaries. The two exceptions are the re-entry case: the agent happened to grep something that put the facts back into raw context, they survived roughly one further summarisation pass, and then they were gone again. No summary ever carried a partial set. Always ten or zero.
Harness-delivered facts: arrived at all 138 compact-resumes of the memory-equipped arm. Delivery fired at every fresh window and nowhere else.
Those two numbers measure different channels, and conflating them is the most common mistake I see people make with this result. The 106-of-108 figure is about the summary: what the summariser chose to carry forward. The 138-of-138 figure is about delivery: what the harness put into the fresh window afterwards. In the memory-equipped arm the two came apart completely: from summary 67 onward there is a stretch of 72 consecutive summaries carrying none of the ten facts, while deliveries kept arriving on schedule throughout, and the run was entirely unaffected because the facts were in the window regardless of what the summary said about them.
In the memory arm, the very first summary dropped all ten facts even though they had been injected into the exact context the summariser was compressing. The summariser is a model making a fresh judgement about what matters, every time. Whether it keeps your fact is not something you can design around, and in this run it flipped mid-way for reasons I still cannot fully explain. A delivery channel does not have that problem because it never asks.
Put the two experiments together and you have the shape of the argument. Facts that live only in the conversation die at the boundary. The voluntary channel that could bring them back does not fire. Therefore, in any session long enough to compact more than once, a fact that is neither in an instruction file nor delivered by a hook is a fact that is functionally gone, no matter how well you stored it.
The Agent That Grepped Its Own Session Logs
My favourite result in the whole project was not designed. It fell out of the no-memory arm and it is the reason I stopped worrying that the demand for this tier might be imaginary.
In the closing phase of that arm, the facts had been gone from every summary for dozens of boundaries. The prompt asked for them in the final report. The agent's shell access was explicitly forbidden, and that ban was restated in every single phase prompt. It used the shell anyway. It worked out that the harness persists its own session transcript to disk in a JSONL file, and that the CLI keeps tool results as files too, and it grepped those artifacts for the fact markers until it found them. Of the 260 shell commands it ran, 38 were this archaeology.
Read that as a capability statement and it is genuinely impressive. Denied a memory tier, the model reverse-engineered one out of the substrate it happened to be standing on. It understood that the harness's disk state outlives its conversation state, and it mined the difference.
Read it as evidence and it says something narrower and more useful: the need was real. This was not a system politely declining a feature it had no use for. It was a system that needed the information badly enough to violate a restated constraint and burn 38 shell commands reconstructing it. The demand exists. What is missing is a supply path that does not depend on the model noticing it needs one, mid-task, under load.
The same run gives you both halves. Given a memory store and a tool, the model does not call it. Denied a store entirely and pushed to produce the facts, the model builds its own retrieval out of log files. Those are not contradictory. Voluntary recall is a deliberate act competing with the task; log archaeology at the end was the task. The gap between "needs the information" and "will go and get the information at the moment it would help" is exactly the gap a delivery tier fills.
There is a smaller lesson here that cost me a pilot run, and I include it because it is the kind of thing that will silently invalidate your version of this experiment too. The harness restores recently-read files across the compaction boundary, small ones verbatim. In an early version of the probe, the fixture holding the ten facts came back whole right after the first compaction, the agent recited all ten, and for about an hour I believed the facts had survived. They had not; the file had. That is why the real runs delete the fixture before forcing anything, and it generalises: when you measure memory, make very sure you are not measuring file restoration wearing memory's clothes.
Stored Is Not Available: The Design Axiom
Here is the sentence the whole project reduces to, and I would put it on the wall of any team building this:
Availability is a property of the delivery rule, not of the store.
Storage answers "does this fact exist somewhere". Availability answers "is this fact inside the model's context at the moment it changes what the model does". They are independent. A store at 100 percent storage and 0 percent availability contributes exactly nothing, and it will look healthy on every dashboard you build for it, because dashboards measure the first quantity.
Most memory evaluations I have read measure retrieval precision on held-out queries. That is a measurement of the store, conditional on a query arriving. It says nothing about whether the query arrives. If you want a number that predicts value, you want availability, and you can write it down.
For a fact f, let R(f) be the set of turns at which f is relevant, meaning it would change what the agent does. Let Ct be the context window at turn t. Then
A(f) = |{ t ∈ R(f) : f ∈ Ct }| / |R(f)|
Availability is the fraction of the moments that mattered at which the fact was actually present. For a voluntary tier, A(f) factors as P(the model issues a query) times P(the store returns f | queried). Store-side work only improves the second factor. The measured value of the first was zero, and zero times anything is what it is.
For a delivered tier the first factor is replaced by whether your cue predicate is true at those turns, which is something you control at design time and can check by inspection.
I would push this metric on anyone benchmarking agent memory. Retrieval precision on a query set is a number about your index. Availability is a number about your product. They can differ by a factor of infinity, and in the seeded control they did.
The demo below exists because that distinction stays abstract until you watch two tracks come apart on the same timeline.
One note, one session, three tracks. The top track shows what the store holds, which never changes. The two below show whether that note is actually inside the model's context at each turn, once under a voluntary tool the model may or may not call, once under cue-anchored delivery. Drag the call rate down to the measured zero and watch the second track go dark while the store stays full.
The thing to take from that is not that one track is greener. It is that the top track never moves. Everything inside the store, how the text is split up, how it is indexed, how results are ranked, how old notes are merged or aged out, is a decision about track one. Tracks two and three are decided by something else entirely, and that something else is where reliability lives.
What a Cue Actually Is, Mechanically
"Cue-anchored" sounds like a metaphor. In an implementation it is not; it is a boolean.
A cue is a predicate over harness state, evaluated by the harness at fixed lifecycle points, whose truth causes a stored note to be written into the model's context.
Three properties are doing all the work in that sentence. It is evaluated by the harness, so no model initiative is required. It fires at fixed points, so it is deterministic and auditable. And it is a predicate, so it can be as narrow as you like, which is what stops the channel from degenerating into a second instruction file.
The state a harness can see is richer than people assume. It knows the session just started. It knows a compaction just finished. It knows which file is about to be edited, which command is about to run, what the user typed in the prompt that is about to be submitted, and what time it is. Each of those is a candidate condition.
The condition classes worth having
These are the ones I ended up shipping in vectr, and the ones I would build again from scratch:
- Path glob. The note fires when a file matching the pattern is touched. This is the workhorse. Most operational knowledge is anchored to a file.
- Lifecycle event.
session-start,prompt-submit,pre-edit,pre-run,pre-commit,post-compaction. These are moments the harness already owns, which is why riding them costs nothing structural. - Symbol reference. An exact symbol name, so a caveat about one function surfaces when that function is in play rather than whenever its file is opened.
- Semantic match. Similarity between the note and the submitted prompt. This is the one condition class that is probabilistic rather than exact, and it is the one to be most careful with.
- Temporal guard.
not_before,expires_visibility,cooldown. Not for finding the note but for governing how often it is allowed to arrive.
The event surface is coarser than that list makes it sound
Worth saying plainly, because it is the first thing you hit when you implement this. Those six event names are my vocabulary, not the harness's. Claude Code exposes SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact and a few more. My pre-edit and pre-run are both PreToolUse with a matcher on the tool name. My pre-commit is PreToolUse on the shell tool with a pattern match for a git commit, which is leaky in the obvious way: a commit made through some other path does not look like a commit to the matcher, and you will not find out until the note fails to fire.
Where the output lands also differs by hook, and it reads differently to the model. UserPromptSubmit writes to standard output and the harness prepends that text to the user's prompt, so it arrives looking like part of what the user said. PreToolUse feedback arrives attached to the tool call. Same bytes, different framing, and in my experience the prompt-prepended channel gets attended to noticeably more. That is an impression from watching transcripts, not a measurement, and I would like someone to run it properly.
Composition, and why the semantics matter
Conditions combine with AND inside a single trigger entry and OR across entries. That is a small design decision with a large consequence: it lets you narrow a fire by conjunction and cover multiple distinct moments by disjunction, without a query language.
vectr_remember(
content="tests/test_session.py deadlocks under parallel test execution. "
"Run it with -p no:xdist or it hangs the whole suite.",
kind="gotcha",
triggers=[
{"event": "pre-edit", "path": "tests/test_session.py"}, # entry 1: AND
{"event": "pre-run", "path": "tests/**"}, # entry 2: AND
{"event": "pre-commit"}, # entry 3
],
)
# fires when: (editing that exact file) OR (running anything under tests/) OR (committing)
Most notes never need an explicit triggers list, because the note's kind already implies a delivery rule. That mapping is the part of the design I would defend hardest, because it is what makes the tier usable by an agent that is not thinking about delivery at all.
| Kind | What it holds | Default delivery rule |
|---|---|---|
| directive | A standing rule the user stated once | Injected at every session start, unconditionally. This is the one class that behaves like an instruction file, on purpose. |
| gotcha | A caveat anchored to a specific file | Fires when that file is touched. Silent otherwise. |
| task | Current-work state | Returned newest-first, because for in-flight work recency beats similarity. |
| finding | A learning about the codebase | Relevance-ranked against what is being worked on. |
| reference | A pointer to a URL or ticket | Retrieved on demand. Low value delivered, high value looked up. |
| decision | An architectural decision and its reasoning | Not auto-injected. Recalled chronologically when someone asks for the decision history. |
Look at the last two rows. Not everything belongs in the delivered tier, and a design that pushes everything into it has misunderstood the point. The question for each note is not "is this valuable" but "can I name the moments at which it changes behaviour". If you can name them, deliver it. If you cannot, it is a lookup, and a lookup is fine.
Composing Triggers: Precision Is the Whole Game
Cue design has the same structure as any retrieval problem, with one twist that changes the economics. A cue that never fires misses the moment. A cue that fires everywhere costs tokens on every cycle, and unlike a bad search result, you cannot ignore it: it is already in the context, already paid for, already displacing something else.
The demo below is a small cue-design workbench. There is one note, a fixed stream of eighteen harness events, and six of those events are moments where the note would actually change what the agent does. Your job is to write a trigger that catches those six without injecting on the other twelve.
Start with the naive configuration, which fires on every event, and watch the token cost. Then narrow it. What you should end up feeling is that conjunction buys precision cheaply, that a second entry is how you cover a different moment without loosening the first, and that some over-firing is unavoidable because your cues are only as fine-grained as the events the harness exposes.
-p no:xdist or it hangs the whole suite. (170 chars, about 42 tokens per delivery)
The naive preset costs 756 injected tokens on a stream of eighteen events, for a note worth 42. That is what "just inject it always" looks like once you count, and it is roughly what happens to every instruction file that has been alive for a year.
The tuned preset catches all six moments and still leaves one surplus fire, and that one is the more useful lesson. Running pytest tests/test_billing.py and running pytest tests/test_session.py are different situations to you and identical situations to a glob over tests/**. You cannot cue more finely than your harness can observe. Every cue design bottoms out on that ceiling, and knowing where yours sits is most of the work.
They are not symmetric errors, and the direction flips depending on scale. One miss costs you one bad decision, once. One surplus fire costs you tokens on every cycle the cue matches, forever, and that cost compounds across a session while the miss does not.
Which means the usual retrieval instinct, favour recall and let ranking sort it out, is wrong here. In a delivered tier, over-firing is the failure mode that kills the configuration, and section 09 is about why that is more literal than it sounds.
What Delivery Costs, Honestly
I have spent eight sections arguing for delivery. Here is the bill, because a post that only showed the wins would be lying by omission.
Fixed per-cycle context, not a one-time payment
Every delivered token is a token of context you do not get back. In the probe, each delivery cost around 240 tokens, mean injected text of 969 characters under the standard four-characters-per-token estimate, and roughly 34k tokens across the full run. As an absolute number that is small. As a structural property it is not, because it is paid on every cycle rather than once.
That distinction bit me in a way I did not anticipate, and it cost me two invalidated launches before I understood what I was looking at. Claude Code has an autocompact thrash guard: it aborts the call when the context refills to the limit within three turns of a compaction, three times running. Sensible protection against a session that has become a compaction loop. The guard is not memory-aware and should not have to be, because all it reads is refill pressure. But a memory-carrying configuration has fixed per-cycle payload the bare one does not: reloaded tool schemas, guidance files, per-prompt injections. None of it large. All of it always there. At the shared file-read cap both arms started with, the guard killed the memory arm's first audit phase fourteen turns in. Running that arm at all meant dropping its read cap from 8k to 6k, and even then the guard killed the final phase once the growing report had eaten the remaining headroom.
Costs that only show up once you run this for a while
Duplication is the one I did not see coming. A delivered note enters the context; the next compaction summarises that context; if the summariser happens to keep the note, it is now in the summary as well. Delivery then fires again on the fresh window and you are carrying two copies, neither of which knows about the other. Either you deduplicate against what is already present, or you pay for the note twice for the rest of the session. Nobody I have read on agent memory mentions this, and it falls out of the design immediately.
Then there is latency, which is a design constraint disguised as an implementation detail. Hooks run synchronously in front of the operation they are attached to, so a cue evaluator on pre-edit means every edit the agent makes now waits on your predicate. Make that a network round trip to a hosted store and you have slowed the agent down on every file it touches, all day, in exchange for a note it may not even need. That is a large part of why vectr runs as a local daemon with recall under 50ms instead of as a service. Not a purity argument about local-first software. The budget for a synchronous pre-edit hook is simply tiny, and the architecture has to fit inside it.
The one I would flag hardest to anyone building this is that the directive class grows without bound. It is the kind that injects unconditionally at every session start, which means it behaves like an instruction file and inherits the instruction file's disease exactly. Every standing rule anyone adds stays forever. The block only ever gets bigger. Eighteen months in you are back where you started with more moving parts. So the tier needs a budget: a hard token ceiling on the unconditional block, with the least-recently-relevant entries evicted when it is exceeded. I would much rather be forced to demote a stale directive into a cued one than discover the block has quietly reached four thousand tokens.
A harness tuned for memoryless operation treats a memory-carrying configuration's context profile as elevated thrashing risk. Your per-cycle payload competes for exactly the headroom the guard is watching. Roughly 240 tokens per delivery was what let the configuration run here. That number will differ in your harness; the fact that there is such a number will not.
The rest of the ledger
The memory arm ran 21 percent more turns and 36 percent more cost than the baseline. Some of that is delivery overhead, some is the tighter read cap the guard forced on it, and some is a late-run stretch where the agent began inventing progress it had not made, announcing phases and file ranges that did not exist. Its audit completeness, scored against a mechanical count over the files it was supposed to have reviewed, came in at 0.70 against the baseline's 0.93. I would not read that gap as a memory effect. It is confounded with the read cap, and the read cap is the thing I had to change to get the arm to run at all.
Which is worth sitting with for a second, because it is not the result I wanted. The memory arm was slower, more expensive, and scored worse on the task metric. What it did do was keep ten facts alive across 138 compaction boundaries that the baseline lost at the second one. Those are answers to different questions. If your session is short enough never to compact, this tier is pure overhead and you should not build it.
The failure mode that worries me most
Not cost. Staleness. A delivered fact that has gone wrong is worse than an absent one, because the agent has no way to tell. An absent fact produces uncertainty, which is a state the model handles reasonably. A confidently delivered wrong fact produces confident wrong action, and it arrives with the authority of the harness behind it.
So revocation has to be a first-class operation, and deletion is not good enough. If you silently delete a note the agent has been acting on, the belief it seeded does not disappear with it. What worked better was keeping a revoked note visible as a correction: it still surfaces on recall, marked as previously believed and now wrong, with the reason attached. The agent sees the retraction rather than a hole where a fact used to be. Temporal guards do the softer version of the same job: expires_visibility on a note about an in-flight migration stops it outliving the migration.
Over-firing. Broad globs and generous semantic thresholds turn the tier into a second instruction file, at which point you have reinvented the thing you were trying to improve on, with more moving parts.
Staleness. Delivered facts age. Without expiry and revocation, the tier degrades from useful to actively misleading, and it does so invisibly.
Semantic cues that drift. The one probabilistic condition class is the one that silently widens as the store grows. Every other class is exact and stays exact. Threshold it conservatively and prefer the exact classes where you can.
Where Voluntary Retrieval Still Belongs
I want to be careful not to argue past what the evidence supports, because the obvious misreading of this post is "delete the memory tools". That is not the conclusion, and if you act on it you will build something worse.
The voluntary tier is the right shape for anything whose cue you cannot enumerate in advance. Exploring an unfamiliar area of a codebase. Answering a question nobody anticipated. Pulling up a decision history because someone in review asked why the schema looks like that. In every one of those cases the agent has a query and no way for the harness to have predicted it. Retrieval is exactly right there, and a good index earns its keep.
What the measurements rule out is one specific and very common bet: putting load-bearing facts behind a call the model has to remember to make. The model is perfectly capable of making that call. The problem is that making it competes for attention with the task, and the moments you most need the fact are precisely the moments the task is most demanding. You are asking for initiative at the exact point initiative is scarcest.
| Question | Delivered tier | Voluntary tier |
|---|---|---|
| Cue known in advance? | Yes, that is the precondition | No, the query is formed at use time |
| Failure if it does not fire | Silent wrong action | The agent explores instead. Slower, not wrong. |
| Cost model | Fixed, per cycle, paid whether used or not | Paid only when invoked |
| Right content | Standing rules, file gotchas, live task state | Open-ended lookup, history, references |
| Right size | Small. Tens of notes, tight cues. | Large. The store can grow indefinitely. |
The two tiers are complements with different economics, and the mistake the field keeps making is not building the wrong one. It is building only one, calling it memory, and being surprised when the durable facts do not survive.
This Idea Has Ancestry, and I Should Say So
Cue-triggered retrieval is not something I invented, and a post that implied otherwise would be worth less than one that traces where it came from.
Production-rule systems have worked this way since the 1970s. A production rule is a condition-action pair, and the system fires it when its condition matches working memory. Nothing calls the rule. The match is the invocation. That is structurally the same move I am describing, applied to knowledge delivery rather than action selection.
ACT-R goes further and is worth reading if you build this. Its declarative memory retrieves chunks by activation, and activation spreads from whatever is currently sitting in the system's buffers. The context you are in changes what is retrievable, without any explicit retrieval request. Soar makes a related move, biasing retrieval from declarative memory using the activation of elements already in working memory. Both architectures took cue-driven retrieval as foundational decades before anyone was writing hooks for a coding agent.
And the coding harnesses already ship pieces of this. Claude Code re-reads and re-injects the project instruction file after every compaction, unconditionally, with no model involvement. Path-scoped rule files reload when a matching file is read, which is a path-glob cue in everything but name. The mechanism exists. What it lacks is a way for a note the agent wrote during the session to opt into the same delivery.
Three things, none of them the cue idea itself. The composition: one store where a note's kind implies its delivery rule, and explicit triggers compose path, event, symbol, semantic, and temporal conditions over that default. The delivery argument: the claim that for the operational tier this is not an optimisation but the only channel that works. And the measurement: an actual number for what the voluntary channel delivers in a controlled run, which as far as I can tell nobody had published.
The measurement is the part I would defend most. Everyone in this field has an intuition about whether agents use their memory tools. Intuitions are free. The seeded control cost real money and produced a number, and the number was zero.
What Would Change My Mind
The evidence here is narrower than the thesis, and pretending otherwise would make the thesis less useful, not more.
One task, one harness, one model. The compaction probes and the adoption runs are a single naturalistic coding task inside Claude Code CLI v2.1.211 in July 2026, driving Claude Haiku 4.5. Harness internals are exactly the kind of thing that changes between point releases, and I would not be surprised to find the thrash guard retuned or the file-restoration behaviour different by the time you read this. Read the specific mechanisms as a dated snapshot. The shape of the finding is the part I expect to hold: a channel gated on model initiative has a reliability you cannot design, and a channel gated on a harness predicate has one you can.
A smaller version of the same caveat: a more capable driving model might well volunteer more. Haiku 4.5 is a fast, cheap model and it is a fair question whether a frontier model with more headroom would spend some of it on memory hygiene. I would guess it helps at the margin and does not change the structure, since the competition for attention gets worse, not better, as tasks get harder. That is a guess and I have labelled it as one.
The zero has an incentive confound. Tool descriptions, guidance wording, and task framing were fixed at one setting, and the agent was never given an explicit incentive to use memory. Elicitation arms that vary those, a differently phrased tool description, a system-prompt nudge, an explicit reward, are the obvious next experiment and I have not run them. If one of those arms produced substantial voluntary usage, the correct update would be: the voluntary channel is elicitable, and the design question becomes how robustly, across which models, at what cost to the task.
Notice that even a strong elicitation result would not restore the original assumption. A channel that works when you tune the prompt just right, on this model version, is still a channel whose reliability you have to re-establish on every model update. The delivery channel does not have that property, and that asymmetry is most of why I favour it.
A run where an agent, without harness-side injection, reaches a comparable availability number on operational facts across many compaction boundaries, using only voluntary recall, and does it across model versions rather than on one. That would tell me the tier can be a skill after all. I have not seen it, and the seeded control is the strongest evidence I could construct against my own position: I gave the voluntary channel every advantage I could think of, and it returned zero.
If a Fact Must Survive, Deliver It
The wrong half I was solving, back at the top, was the store. I had built retrieval that understood code and a good place to keep what an agent learned, and I had left the most important step in the pipeline as an exercise for the model: notice you need this, then go and get it.
It does not notice. The capability is there, but noticing competes with the work and the work wins, every time, and it wins hardest exactly when the stakes are highest. Meanwhile the instruction file, which is the crudest channel in the whole stack and has no retrieval logic in it at all, wins on reliability because it never asks the model for anything. Everything I have built since has been an attempt to keep that property and drop the cost. That is the whole of what a cue is: an instruction file that is only present at the moments it is true.
So the practical version, if you are building on an agent right now. Take the facts your system must not lose and give each one a delivery rule instead of a storage location, expressed as something your harness can evaluate on its own. Make it as narrow as your event surface allows, and expect the event surface to be the binding constraint. Whatever refuses to be expressed that way stays a lookup, and that is fine, so long as you have stopped quietly assuming the lookup will happen.
The paper puts it more compactly than I have managed anywhere in twenty-seven minutes of this: the reliable memory channel for agents is the one the agent never has to think about.
Links & Further Reading
The paper, the run archives, the cognitive science the two-tier framing borrows from, and the cognitive architectures that got to cue-driven retrieval first.
- Saha, S. Delivery, Not Storage: Cue-Anchored Working Memory as a Harness Property for Coding Agents.
- Run archives for the adoption and compaction runs: protocol, graders, and per-run artifacts, including invalidated launches and their diagnoses.
- vectr, the working-memory daemon used as the instrument, and the implementation the trigger examples are drawn from.
- Anthropic. How Claude remembers your project.
- Persistent Memory Across Context Compactions (59 compactions, built our own), a field report from a team that hit the same wall independently.
- Triggered injection for auto-memory topic files, the upstream feature request the measurements motivated.
- Berntsen, D. et al. Involuntary autobiographical memories and their relation to other forms of spontaneous thoughts.
- Retrieval intentionality and forgetting: how retention time and cue distinctiveness affect involuntary and voluntary retrieval.
- An Analysis and Comparison of ACT-R and Soar.
- ACT-R, the cognitive architecture whose declarative memory model is the closest prior art to a cue-anchored tier.