The second memory
I gave a real project a second kind of memory, a code graph from Graphify that maps what actually connects to what, sitting under the context system I write by hand. The one I write holds the why. The one the code writes back holds the what is. The gap between them is where the bugs live.
Part of my methodology series. If you have not read the overview, start there. This is a case study: what happened when I put a second, derived memory underneath the curated one.
A one second query caught a feature that was marked done and never built.
On this project, a ride sharing platform I have been building, Claude, the agent I build with, was wiring up subscription pricing. My own progress notes said the rider screens for it were finished. Before Claude touched them, it asked a code graph one question: which parts of the app import the subscriptions API. The graph, built by a tool called Graphify, answered: none. Zero importers.
The screens existed, they rendered, they looked done, and they were wired to nothing. They had been running on sample data seeded early and never replaced. My notes said the feature was finished. The code said it had never been connected.
Grep would not have caught that, because the screens work. My curated docs could not have caught it, because they record what I intended, not what got wired. A test would have caught it only if someone had thought to test that exact screen against a real server, which is the one thing you do not think to do for a feature you believe is done. An absence is invisible to almost everything. It is not invisible to a graph.
Two memories
The reason the disagreement surfaced at all is that the project now has two kinds of memory, and they were telling different stories.
The first is the one I write by hand. I have described it before: a context system, a set of plain files that hold the project’s decisions and the reasoning behind them. It is the authority. When the code and the foundation disagree, the foundation wins. It is small, opinionated, and it holds the why. I write it, and it is mine.
The second is new, and nobody writes it by hand. It is a code graph that Graphify builds by reading the whole codebase and mapping what actually connects to what: which function calls which, which file imports which, which module exports what. It is comprehensive, mechanical, and it holds the what is.
The two are easy to confuse, and they should not be. The one I write is intent. The one Graphify derives is reality. The part this whole piece is about is that the gap between them is where the bugs live.
The before
I put this to a real test, because I did not want a hopeful story. The project is a monorepo with seven workspaces and around 390 source files. A shared types package feeds all five apps, so almost every change crosses a backend, a shared contract, and at least one app. Understanding a change before making it is a genuine cost.
First I had Claude build two real features the old way, with grep and read and my curated docs, no graph, and I logged what the explore step actually cost.
The curated docs earned their keep immediately. They told Claude where things lived, which rule it was implementing, and which invariant it must not break. The why took no exploration at all. But the what is took constant grepping, and grepping missed things. On the first feature, Claude designed a change to a dispatch function before checking who else called it; a last minute grep, one it almost skipped, found a second caller a naive change would have broken. On the second feature, a review pass caught two more: a module that exported nothing, so another module literally could not use the service it needed, and a seeder that quietly ignored a field the feature depended on.
Every one of those was the same kind of question: who depends on this thing. Nothing in my curated docs enumerates that, and it should not try; hand maintaining a list of every caller of every function is a fool’s errand. It is exactly the question a graph answers for free.
The after
So I pointed Graphify at the codebase, and it built the graph. It indexed 466 files into a few thousand nodes and edges in under seven seconds, at zero token cost, because it reads code with a parser, not a model. Then Claude built two more features, and this time it asked the graph before it grepped.

It changed the shape of Claude’s work. Instead of sweeping directories, guessing at file names, and reading whole files to build a map it could only hope was complete, it asked the graph for the map. One query on a function returned its complete set of callers, with file and line. One query on an API returned every screen that consumed it, including one Claude would have missed by guessing, a booking screen that also happened to create the thing being changed. The expensive, error prone part of the old way was sweeping for structure and hoping the map was complete. That is the part the graph deleted.
Then it proved the absence, the subscription screens with no importers, and that was the moment the tool paid for itself. Finding what is there is useful. Proving what is not there, no importer, no export, no filter, is something only a graph does cheaply. That finding rewrote the feature Claude was about to build.
What it did not do
It is not magic, and a piece that pretended otherwise would be worthless. A few honest edges.
It is a resolver, not a search engine. Ask it a vague question and it hands back noise; it wants a real symbol name to anchor on, which means Claude still needed one grep or one guess to get started. The node names took a few failed tries to learn. It does not carry the things that live inside a file: a wrong field name still cost Claude a failed test, because the graph knows a function exists but not the exact shape of its input. It does not carry judgment: it pointed at some duplicated math, but knowing that the duplication was a business rule and not a coincidence took reasoning, the kind the graph does not do. And it does not carry environment truth: at one point Claude was verifying against a stale process serving old code, and no graph would have caught that. Only running the real thing did.
The most honest line in the log is that twice Claude had the graph sitting right there and forgot to use it, and answered a dependency question from its own memory of the code instead. Tool habits lag tool availability, and it turns out that is true for a machine too. A better memory does not automatically make anyone, or anything, reach for it.
The rule I hold kept it safe: the graph is strictly advisory. When it and my foundation doc disagree, the foundation wins. In practice they never disagreed. The graph never argued with my decisions. It argued with my progress tracker’s optimism, and with my docs’ claims about the code. My foundation stated what I meant to build; the graph stated what was actually there; and the delta between the two was the work list.
The second memory
That is the whole idea. A project wants two memories. The one you write holds the why, and it stays the authority, because intent is a human decision and no graph can derive it. The one the code writes back holds the what is, and its job is to keep the first one honest against reality.
They are not rivals. They are altitudes. The curated memory even made the derived one more useful, because it told Claude which questions were worth asking; the graph was a sharper answer to questions my own docs had already framed. And it earned its place the first time it proved an absence, a feature that would have shipped as done, that had never been built.
If you already keep a memory outside the chat, and you should, this is the second one. Let the code write it, and let your agent query it. Just never let it outrank the one you wrote yourself.