A pull request can compile, pass its tests and still leave the team in a worse position. Ask a simple question a week later: why is this abstraction here, and what else will break if we change it?
If nobody can answer without reconstructing the work from scratch, the code is not the only thing we acquired. We also acquired an understanding problem.
Agents make this easier to miss because implementation can arrive before a developer has formed a mental model of it. The answer is not to read every generated line with equal intensity. It is to decide what we must understand, then use the agent to help build and test that understanding.
The Debt Is in Our Heads
Margaret-Anne Storey describes cognitive debt as the gap created when a system evolves faster than the team’s shared understanding of how and why it works. Technical debt may make code harder to change. Cognitive debt makes it hard for people to reason about the change at all, even when the code is tidy.
Storey connects this to Peter Naur’s older idea of programming as theory building. A program is not only its source text. The people maintaining it carry a theory of its goals, structure and possible changes. Source code can be handed over intact while that theory is lost.
This is not an argument that every engineer must memorise every implementation detail. Teams have always relied on libraries, operating systems and services they do not fully understand. The useful question is whether we understand enough to make the decisions for which we are responsible.
For a small adapter that converts one JSON shape into another, tests and a clear interface may be enough. For an authorisation rule, billing calculation, data migration or core scheduling algorithm, “the agent understood it” is not an ownership model.
The risk should decide the depth of understanding:
- Behaviour: What does this component promise, including failure cases?
- Boundaries: Which systems, data and people can it affect?
- Invariants: What must remain true while it runs and after it fails?
- Intent: Why was this design chosen over the obvious alternatives?
- Change: Where would we look first if the requirement moved?
If the team cannot answer those questions for an important part of the system, more generated documentation alone will not save it. We need to rebuild the theory and check it against reality.
Start with a Linear Walkthrough
Simon Willison’s linear walkthrough pattern is a practical starting point. He used Claude Code to explain a SwiftUI application he had prompted into existence without following its implementation. The resulting document walked through the six source files and connected the code into a coherent route through the application.
“Summarise this repository” is usually too broad. The agent will list folders, praise the architecture and skip the decisions we actually need. Give the tour a path and a question. For a web request, for example:
Trace one authenticated request from the public route to the database and back.
For each step, identify the file and symbol, state the contract, and explain
what happens on timeout or invalid input. Include the tests that exercise it.
Separate observations from your interpretation. Flag gaps rather than guessing.
Willison used Showboat to construct his walkthrough. Its exec command runs shell commands and records their output in the document. Asking the agent to extract snippets with tools such as sed, grep or cat reduces the chance that it will quietly invent the code it is explaining.
Reduces is the important word. A real snippet can still be selected without its surrounding condition. Commentary can still infer the wrong intent. The document is a guide for review, not a second source of truth.
A useful walkthrough should therefore contain handles back into the system:
- file paths and symbol names;
- short, extracted snippets rather than copied approximations;
- relevant tests and commands;
- data or control-flow diagrams where relationships are otherwise hard to follow;
- explicit unknowns and competing interpretations;
- the commit identifier it describes.
That final item prevents a subtler failure. Walkthroughs age. A beautifully written explanation of last month’s code can be more dangerous than no explanation because it feels authoritative. Tie it to a revision, regenerate it when the area changes, or delete it when nobody will maintain it.
Ask for an Explanation You Can Challenge
Passive reading produces familiarity, which is not quite the same as understanding. A better test is prediction.
Before asking the agent, write down what you think happens. Then use the walkthrough to find where your model differs from the code. Ask questions that force consequences:
- If this call times out after the database write, what can the caller observe?
- If two workers process the event, which invariant prevents duplication?
- Which test fails if this validation is removed?
- What is the smallest change needed to support a second provider?
- Which assumption in this design is most likely to be false?
Then inspect the referenced code and run the relevant test. An answer that cannot point to evidence is a hypothesis, however fluent it sounds.
There is also value in reversing the direction. Explain the component back to the agent in your own words and ask it to find contradictions using the repository. This is not because the agent is a reliable examiner. It is because formulating the explanation exposes the gaps we can otherwise glide past.
For team-owned code, do the same exercise with another person. Shared understanding cannot be outsourced to a private chat transcript.
Let Tests Carry Their Share
Tests are useful comprehension artefacts because they connect a claim to executable behaviour. A well-named test can show which inputs matter, which boundary is being protected and what the author considered a failure. Unlike a prose explanation, it can object when the implementation changes underneath it.
That does not make a test suite a complete design document. Tests omit scenarios, inherit the author’s blind spots and often say little about why an outcome matters. An agent can also write a test that merely confirms its own implementation. Green is evidence, but we still have to ask what was exercised.
When building a walkthrough, ask the agent to map each important contract or invariant to the test that protects it. Treat a missing test as a visible gap, not an invitation to invent reassurance in the commentary. For critical paths, deliberately break the behaviour and confirm the expected test fails. This connects understanding to the tests-first constraints discussed earlier in the series: tests are most useful when they express intent independently, before the implementation teaches us what to expect.
There is a pleasant side effect. A test that is difficult to explain may be revealing an interface that is difficult to reason about. The comprehension pass can therefore improve both the human model and the code’s design, without pretending documentation alone will fix either.
When Prose Is the Wrong Medium
Sometimes the words are accurate and still do not help. Willison encountered that with a word-cloud implementation described as using “Archimedean spiral placement”. A linear walkthrough explained the Rust structure, but not how the placement algorithm behaved. He then asked for an interactive explanation: an HTML page that animated each placement attempt, exposed speed and step controls, and made collisions visible.
This technique is useful for algorithms, state machines, concurrency, retries and data transformations. Ask for a small executable model that exposes internal state. Pause it. Change the input. Force the awkward case. A sequence diagram may explain the happy path; a simulation can show why the third retry creates a duplicate.
Again, the demonstration is not proof. If the agent implements a simplified or incorrect model, the animation will explain the wrong thing with admirable clarity. Connect important states and transitions back to the production code, use representative inputs, and compare outputs with the real implementation.
Interactive explanations also have a cost. They are code, so they can contain bugs and need maintenance. I would not build one for every service method. Reserve them for mechanisms where a static description has failed, or where several people need the same intuition.
Preserve Intent, Not Just Structure
A walkthrough can tell us what the code does today. It cannot recover a decision that was never recorded.
Suppose an agent finds a cache around a slow service. It can trace the cache key, expiry and invalidation logic. It cannot know whether the team accepted stale data for cost reasons, added the cache as an emergency fix, or intended to remove it after a supplier migration. That is intent debt, adjacent to but different from cognitive debt.
Record consequential decisions while they are still decisions. A short architecture decision record, an issue linked from the commit, or a comment beside a surprising constraint is enough. Capture:
- the problem and constraints at the time;
- the chosen option;
- the alternatives rejected and why;
- the evidence that would justify revisiting it.
Do not ask the agent to manufacture rationale after the event. It will produce a plausible story, which is precisely the problem.
A Practical Understanding Gate
I would add a small understanding pass after implementation and before merge for any material agent-written change:
- Trace it. Generate a focused walkthrough through the changed behaviour.
- Verify it. Open the referenced files and run the tests or examples.
- Challenge it. Ask failure, boundary and change-impact questions.
- Teach it back. Explain the design without reading the agent’s summary.
- Record intent. Preserve decisions the code cannot express.
This pass spends some of the time agents saved, so budget it as part of changing software safely.
The gate should be proportional. Low-risk, conventional code may need five minutes and one focused question. Security boundaries, money movement and irreversible data changes deserve more. If nobody has enough time to understand a high-risk change, the honest answer is to delay it, narrow it or discard it. Shipping an opaque implementation does not become responsible because producing it was cheap.