Memory for AI Coding Agents: Storing Is the Easy Half
Last week's Harness post showed a real session briefing, and one line in it was doing more work than all the others:
- **MCP Server** [PITFALL] (claude-code/vincent, 1 day ago): every new ID argument
name must be added to idArgumentResolvers in authz.go, or the cross-org check
silently skips it.
An agent that reads that saves the afternoon the previous agent lost. Good. Now age it six months. The same sentence, in the same confident tone, served to an agent working on a file that two people rewrote in the meantime. Nothing about the line looks different. The agent has no way to tell, and neither do you.
That is the real problem with memory for coding agents, and it is not the part most tools build. Writing things down is the easy half.
Why a pile of notes cannot answer the question
The common shapes of agent memory today are a markdown file the agent appends to, and a vector store it writes into. Both store well. Both retrieve reasonably. Neither can tell you that the ground under a note moved, because neither knows what the note is about in any structural sense. A vector store knows that a note is near the words "gateway" and "headers". It does not know that ApiGateway is a container in your system, that it has a source path, and that the code at that path stopped matching the documented model three weeks ago.
An architecture model knows all three. That is the whole argument for putting memory next to one, and it is the only part of this post that another product could not copy in a sprint.
Memory is in Archyl now, on every plan. Here is what it does.
A memory is pinned to an element, not to a conversation
A memory is a fact typed as a note, a convention or a pitfall, attached to a C4 element or to the project as a whole. Agents write them through MCP; people write them in the Agent Hub or in the details panel of any element on the diagram.
▶ remember(projectId: …, element: "ApiGateway", kind: "pitfall",
content: "The gateway strips custom headers longer than 8KB — payloads must go in the body.")
There is a fourth kind, session_outcome, written automatically when a work session finishes. One session that held eight leases produces one memory attached to eight elements, not eight copies of the same paragraph. The shape matters for retrieval: an agent asking about any one of those elements gets the outcome once, rather than reading the same summary back eight times because the session happened to touch eight things.
Memory content and titles are encrypted at rest, like every other sensitive content column in the product.
Recall ranks by meaning, not by shared substrings
recall blends semantic similarity with term overlap, weighted 0.55 to 0.45. An agent asking about "rate limiting" gets back the note somebody else wrote about "throttling", the case a word-matching search misses and a colleague never would.
The vectors are best-effort by design. Without an AI provider configured (OpenAI-compatible or Ollama), there are no vectors and scoring stays purely lexical, the way it behaved before. It degrades rather than breaks, which matters if you self-host without a provider. And memories written while no provider existed do not stay second-class forever: a background worker backfills their vectors once one is configured.
Writing the same fact twice confirms it
Re-stating something the project already knows does not create a second copy. Above a cosine similarity of 0.94, the write confirms the existing memory instead, and the response says deduplicated: true. An agent re-asserting what it learned is evidence, not noise.
Between 0.82 and 0.94 sits the interesting band: close, but not the same fact. Those are stored, and the near-matches come back in similarTo, so the author can call remember(supersedes: "Old title") deliberately instead of quietly contradicting a memory that is still live and still being served.
Memory learns from use
Every recall records which memories it served to which session. When the session finishes, usedMemories names the ones it actually relied on.
Those two signals are deliberately not weighted the same. Being served a memory is circumstantial. Saying you used it is testimony. So only citations lift a memory's rank, log-scaled and capped at 1.8x so a popular memory cannot bury the newer one that corrects it. A memory served to five sessions without a single citation gets a 0.75 multiplier and is treated as noise.
Treated as, not deleted. Nothing in memory is ever removed by a heuristic. Ignored memories land in a review queue with their impression count, and a person decides. The same principle runs through the whole feature: corrected, never erased.
A memory has a lifecycle
Freshness decays on a 45-day half-life from the moment a memory was last known true, which is its creation or its most recent confirmation. confirm_memory resets that clock and bumps the confirmation count. remember(supersedes: …) replaces a fact that changed: the old version leaves retrieval but stays in history and in the graph, so you can still see what the project believed last year.
Kind weights sit on top, and they are opinionated: a pitfall scores 3.0, a convention 2.0, a plain note 1.5, a session outcome 1.0. For an agent about to change code, "this will bite you" outranks "here is what happened".
Drift is what actually invalidates a memory
Everything above is decent bookkeeping. This section is the reason memory belongs in an architecture tool.
Time is a weak proxy for truth. A convention written two years ago about how your service boundaries work is probably still right. A note written last month about a file that has since been rewritten is probably wrong. Decay treats them identically, because a clock is all it has.
What actually makes a memory suspect is the code behind its element changing. Archyl already computes that, deterministically: the drift score compares the documented model against the repository and names the elements that no longer match. Run it from the UI, from the API, or on every push with the drift-score GitHub Action. Memory is now wired to it.
When drift finds an element out of sync, every memory attached to that element is stamped with the moment it happened. A memory last confirmed before that stamp describes something that has since moved underneath it. Three things follow:
- It is demoted in ranking, by a 0.6 multiplier. Demoted, not hidden: it may be the only thing anybody ever wrote about that element, and hiding it would be worse than serving it with a warning.
- It appears in the review queue for a human.
- The agent reads a warning, in the briefing, in words rather than metadata:
- **ApiGateway** [PITFALL] [VERIFY — the element drifted since this was written]
(claude-code/sarah, 96 days ago): the gateway strips custom headers longer than 8KB.
Re-confirming the memory clears the flag, because a confirmation answers the drift question directly: somebody looked, and it still holds.
Both halves of that mechanism live in the same product. The knowledge is here, and so is the model-versus-code comparison that can undermine it. A memory layer bolted onto a chat client has the first half and no way to get the second.
Memories link to each other
Give a memory a title and it becomes addressable. Any other memory can then reference it with [[Title]] in its content, Obsidian-style. The same syntax resolves to C4 elements by name ([[ApiGateway]]) and to decisions ([[ADR-17]]), and a link to a title that does not exist yet stays pending and attaches by itself the moment somebody writes that memory.
The links are not only for reading. recall follows them: the top matches pull in their linked neighbours, marked via so you can see what brought them. A pitfall about the gateway that links to the ADR explaining why the boundary exists arrives with the reasoning attached.
The knowledge map, and the graph we threw away
The first version of the memory panel was a node-link graph. It rendered, it clustered, it looked like the sort of thing you screenshot. It answered "which memory links to which memory", and that is not a question anybody was asking.
What people need to know is which parts of their architecture the project understands and which parts nobody has written a word about. So we replaced it. The panel now shows one cell per C4 element: what is known about it, how fresh that knowledge is, how many pitfalls sit there, and, for the elements with nothing at all, a visible gap. It produces a headline no dashboard gave you before:
3 of 19 elements documented
That sentence is uncomfortable in a useful way. The graph was not.
What it doesn't do
Semantic recall needs an AI provider. No OpenAI-compatible endpoint and no Ollama means no vectors, and ranking falls back to term overlap. Everything else on this page still works.
Element matching is still lexical. Memory ranks by meaning now. The step before it, find_relevant_context choosing which elements your task is about, still scores by word overlap on names, descriptions, tags and paths. A task about "checkout" will still not surface a component called OrderProcessor. We flagged that as a limit in many agents, one architecture and it is still true.
The usefulness signal only exists if agents cite what they used. The archyl-harness skill teaches the agent to pass its sessionId to recall and to name usedMemories when it finishes. Nothing forces it. An agent connected without the skill produces impressions and no citations, which reads identically to a memory nobody found useful.
Memory is project-scoped. An organization-wide convention has to be written into each project that needs it. That is the next thing we are fixing.
And the honest overall caveat: memory has only just shipped. We have no adoption numbers, no benchmark, and no customer telling you it saved them anything. What is above is what the code does, and you can check every bit of it against your own project.
Where to start
If you already run the Harness, memory is already on. remember, recall and confirm_memory are three of the sixteen tools in the coding profile. Claude Code plugin version 0.8.0 is the piece that teaches an agent the two habits the ranking depends on: pass your sessionId to recall, and name what you used when you finish.
The first thing worth doing is not writing memories. It is opening the knowledge map and reading the coverage line. Whatever fraction it shows is the fraction of your architecture that survives the person who understands it going on holiday. Guess the number before you look, then look.
Then pick the one element with the most traffic and the least written down, and write the pitfall you would tell a new hire on their first day. That is the memory the next agent needs, and until somebody types it, no amount of retrieval will find it.
Memory is part of the Archyl Harness: work sessions, the preflight gate, the Guard hook and the Fleet console. The plugin, skills and Guard hook and the GitHub Actions are open source, and the full reference is in the Harness guide. Related reading: work sessions, many agents, one architecture, and why your agents have a rules file and not a model.