What I learned about making design documents the driver of agentic development.
I recently moved the development of CALM into the open. Until then, it had evolved privately, alongside lessons I had accumulated from earlier closed-source projects about working with coding agents.
The approach may feel slightly contrarian at a time when agentic development is often framed as progressively removing constraints and increasing autonomy. My experience has pushed me in another direction: as implementation becomes easier to generate, it becomes more important to make the system’s architectural intent explicit.
The lesson I kept returning to was not primarily about prompts or models. It was about how a codebase preserves its design.
The document existed. It just wasn’t in charge.
The first project where this became visible was an object store I was building from scratch for my company. We had an anchoring design document describing the product, its architecture, and many of its important decisions.
But the agent had not been told that the document was the driver.
The effect was subtle at first. The agent would ask questions that the design had already answered. More consequentially, it would sometimes produce an implementation that looked locally reasonable, only for the review pass—manual and agent-assisted—to reveal that it had ignored part of the system’s original setup.
These were not missing corner cases.
The implementation had quietly discarded assumptions on which the architecture had been built. The answer had been available all along. What it lacked was authority.
Over time, the project arrived at a different arrangement. The design document stopped being a corroborative artifact and became the source from which implementation decisions flowed. The agent guide explicitly established that relationship: read the design first, reason from its invariants, and treat disagreement between design and implementation as something to investigate.
That experience led to two practices that now shape how I develop with agents.
The first is invariant-driven development: identify the architectural properties that feature work cannot reinterpret locally. Think of the kind of rule like *an audit trail is append-only*: a feature that “just cleans up old entries” is not an optimization; it is a different product.
The second is to make the design document the driver. If a feature genuinely requires the architecture to evolve, discuss the change and update the design first. Do not leave the document behind as a historical artifact while the code quietly becomes a different system.
Code cannot explain itself as a whole
The business logic of a real system is rarely written in one place.
A single contract may begin in an API definition, pass through validation and middleware, appear again in a service, become a database constraint, affect a cache key, and finally be asserted in tests. Security boundaries and lifecycle rules are distributed in the same way.
Each piece may be understandable on its own. But as the surface grows, reconstructing the system-level behavior from all of them becomes less reliable. One part of the codebase may continue enforcing an older assumption while another implements a newer one. Both can appear locally correct. The contradiction only becomes visible when the system is considered as a whole.
A design document solves a different problem from code comments or API documentation. Its purpose is not to repeat the implementation in prose. It keeps the ideas that the code necessarily distributes in one place: what the product is and is not, which boundaries define it, which invariants must survive every implementation path, and which trade-offs are deliberate.
The code spreads these ideas across executable pieces. The design document keeps them conceptually whole.
The usual objection is that design documents rot. They do — when they describe the implementation. A document that mirrors code is stale the day after it is written, and everyone learns to distrust it. The documents that survive carry only what the code cannot: the invariants, the boundaries, the deliberate trade-offs. At that altitude, the document changes when the *design* changes, not when the code does — and a disagreement between the two becomes a flagged, temporary state to reconcile rather than silent drift.
The design drives, but it also evolves
Files such as AGENTS.md or CLAUDE.md should not become alternative design documents. Duplicating the product’s design into agent instructions creates two descriptions that can drift apart.
The agent guide instead establishes the operating discipline:
Read the design document first. Treat it as the driver. If the design is silent or the implementation appears to disagree, surface the gap rather than inventing a local answer.
A small instruction can change the agent’s relationship with the entire codebase.
But making the design authoritative does not make it immutable. Experience can travel back upstream and improve the design itself.
I am also building an agentic coding harness as part of my work. Developing that harness alongside CALM has created a useful feedback loop between the environment through which an agent works and the codebase being worked on. One change to CALM’s design document captured this particularly well.
An earlier version explained why CALM could look like retrieval-augmented generation while not being a RAG system. At the time, “not RAG” was largely an architectural direction: I had approached CALM as a systems-engineering project rather than another retrieval system.
Working closer to the coding harness changed the question.
It was no longer, “Why isn’t CALM RAG?”
It became, “Why can CALM not be RAG?”
The distinction had become an invariant. RAG retrieves knowledge to help produce an answer. CALM governs the working context produced while an agent acts. Making CALM RAG-shaped would not merely alter its implementation; it would change the role the system was meant to play.
The conclusion remained the same, but the design had become more precise. The document was not preserving an old answer. It was being updated as the reasoning behind that answer improved.
An old discipline, amplified
None of this is fundamentally new. Engineers used design documents long before coding agents, and strong teams already understood that important system behavior should not exist only in source files — or in the minds of a few senior engineers. Agentic development amplifies the value of that discipline.
A human engineer develops a mental model of a codebase through repeated exposure, conversations, incidents, and implementation work. A new agent session begins without that history. It can inspect the repository, but it must reconstruct the system again from whatever material is available.
A crisp design document changes that starting point. If it is focused enough to remain in context, every session can begin with the same preamble, boundaries, and invariants. The agent still needs to read the relevant code, but it no longer needs to infer the project’s entire philosophy from hundreds of files before changing one of them.
The implementation capacity of agents makes ambiguity more expensive. An unclear assumption can be propagated across a codebase very quickly. The same capacity also makes a clear design unusually powerful.
Humans remain in the review loop
The design document does not only help an agent enter a codebase. It also helps a human re-enter one.
An agent can produce a substantial change faster than a reviewer can reconstruct every relevant contract from the files in the diff. Without an anchor, review begins by trying to infer what the architecture was supposed to be.
With the important calls and invariants written down, the reviewer already knows the frame against which the implementation should be evaluated. Does the change preserve the invariants we agreed on? Is it implementing an existing decision, or quietly introducing a new one? Has a local convenience changed a product boundary?
A human can choose where to participate: in the architectural decisions, in the implementation details, or in both. The design document makes each level more accessible.
Human judgment defines the system. Agents help carry that judgment through the implementation. The design document is the shared reference between them.
What the project chooses to remember
Not every artifact produced during development belongs in the project’s durable memory.
Plans, research, and work-item breakdowns may be valuable to the current task without being meaningful six months later. Conversations and local resources can hold temporary thought. Issues and pull requests can hold coordination and history. Committed design documents hold durable product decisions. Code and tests hold executable behavior and evidence.
Writing a file is not the same as deciding that the project should remember it. The commit surface is a promotion boundary.
In practice, the boundary is not local versus public. A work item may stay on a developer’s machine or live in a public GitHub issue; in either case, it coordinates the work. What matters is whether the durable decision that emerges from it is promoted into the committed design or decision log. Visibility is not authority. Choosing what gets promoted is itself an act of engineering judgment—and probably a piece worth writing on its own.
Agents should be allowed to think expansively. Projects should remember conservatively.
CALM is the first project where I am applying this philosophy in the open. It is not proof of a universal methodology. It is simply a place where the relationship between design, agent instructions, implementation, and review can be inspected as the system evolves.
As agents make implementation increasingly abundant, engineering judgment becomes more visibly scarce. Deciding what the system is, which constraints matter, and what deserves to become durable project knowledge remains human work.
The code will remain distributed.
The design does not have to be.
