← back to essays
v2026.07.16 16 Jul 2026 4 min read

Spec-Driven Development Is an Anti-Pattern

Elad Katz
Elad Katz
Strong opinions, loosely held.

Spec vs. code

Detailed specifications are becoming the default way to work with coding agents: write down the feature in enough detail, hand the document to the agent, and let it implement the plan.

I think that is backwards.

At the start of a project, you understand it less than you ever will. Everything you are about to learn is still ahead of you. Yet this is exactly when spec-driven development asks you to make the most decisions and write them down in the most detail. But understanding what you want is iterative. It develops while you work with the agent, run what it produces, and change direction based on what you learn. A document cannot front-load discoveries that have not happened yet.

There is also a deeper problem. A specification detailed enough to remove every ambiguity is effectively code written in a worse language. Anything less is an approximation, and the gaps it leaves are exactly where the coding agent has to guess.

So why write the feature twice?

My default is simpler: I describe the feature to an LLM and let it implement it. Then I keep iterating with the LLM until the behavior is right. I am not writing the implementation, and I do not begin by designing interfaces or turning the feature into a test plan. The agent writes the code; I steer the process.

Tests are how I control that process. I add them where I need guarantees about the result, not to specify the entire feature in advance. They are not a second implementation written in another language. They are the minimum force needed to constrain the agent and verify the behavior I care about. Once the feature works and the tests pass, I review the coherent implementation that emerged.

Only after the implementation has stabilized do I write—or generate—a design document, when one is actually useful. Its purpose is not to restate the code, but to explain its decisions, constraints, and trade-offs to teammates, PMs, and management. Documents should preserve the why. The implementation already contains the what. For many projects, a good README is enough.

Code as the agent's interface

Once the implementation exists, the branch diff against main becomes a remarkably effective interface for the next agent. I do not need a document reconstructing everything that happened. I give the agent a brief description of the goal and point it at the diff. The diff contains the production code, the tests, and every decision that survived contact with implementation. It is the feature in its most precise available form.

There is one important caveat: this works only when the code is easy for the agent to read. We used to care about readable, modular, well-structured code because humans had to maintain it. That still matters, but now the structure of the code is also the structure of the context we give our agents. Clear boundaries, good names, small modules, and focused diffs allow an agent to understand the feature more completely, with less context and at lower cost.

Historically, keeping code that clean was expensive. Refactoring competed with feature work and was often postponed indefinitely. With good test coverage and AI, it has become almost trivial: ask the agent to restructure the code, let the tests protect its behavior, and repeat until the result is easy to understand. Better structure then makes every subsequent agent more effective—and makes the diff itself enough context to understand the feature.

Optimize the workflow, not the token count

This workflow may consume more tokens than writing a detailed specification once and asking an agent to implement it in a single pass. That is fine. Token count is not the thing we should be optimizing. We should optimize the total cost of reaching correct, maintainable software: engineering time, review effort, defects, rework, and the amount of context humans and agents must reconstruct later.

One thing I appreciate about working at AWS is that we are encouraged to use the tools as much as necessary to improve engineering output, rather than obsessing over the cost of each model call. That is the right abstraction. Token cost is a local metric; workflow efficiency is the goal. A cheap one-shot implementation is not efficient if it takes longer to review, has to be rewritten, or leaves the code harder for the next agent to understand.

The workflow I keep returning to is simple: describe the feature, let the agent implement it, add tests where guarantees matter, iterate until the behavior is right, refactor until the diff explains itself, review the result, and generate documentation afterward when there is useful reasoning to preserve.

This is not an argument against thinking before coding. It is an argument against pretending that the hardest decisions can all be made before coding begins.

"A perfect specification is called code."

What we should optimize for is workflow efficiency. The best workflow is the one that turns intent into working code with the least friction.

engineering