krishna@
8 min read#ai#tooling

Spec-driven development is waterfall's good half, and that's fine

I was writing API contracts before implementation years before agents existed, for reasons that had nothing to do with generation speed. Which makes the current argument about the wrong variable.

share
An architect drafting a technical drawing with pencil and ruler

an argument I have been having since before it was an argument

Spec-driven development went mainstream this year, and the backlash arrived roughly a fortnight later. The critique is compact and lands hard: this is waterfall with better branding. Write the document, get it agreed, generate the implementation. We tried that. It did not work.

The defence is equally compact. Waterfall did not fail because specifications are bad. It failed because discovering your specification was wrong cost you months of implementation built on it. When re-deriving the implementation takes minutes instead of quarters, the economics that made waterfall lethal simply are not there any more.

I find myself in an odd position in this argument, because I have been writing specifications before implementations for years, on a product with no AI in it at all, for reasons that have nothing to do with either side's case.

So I want to offer the boring third position: contracts-before-code is old, it works, and the reason it works has nothing to do with generation speed. Which means the current debate is arguing about the wrong variable — and, more usefully, that we already know quite a lot about when this practice fails, from long before anyone was regenerating anything.

why we did it, with no AI anywhere near it

The situation was ordinary. A backend, and two client teams — web and mobile — building against it. Two specialists who could not start until endpoints existed, and one backend engineer who was the only person who could produce them.

That is a blocking dependency, and the obvious version of it is disastrous. Both client teams sit idle waiting on me; I rush endpoints to unblock them; the rushed shape becomes permanent because two clients are now built on it.

So every feature started as an agreed API shape, settled in review before anything was implemented. Both clients mocked against the agreed shape and built their side. I built the real thing behind it. We met in the middle.

The benefit that mattered was not that the spec produced better code. It was that the spec removed me from the critical path. Three people could work simultaneously on something that had, an hour earlier, been strictly sequential.

That is a parallelism argument, and it is worth noticing that it is precisely the same argument being made for agents right now. An agent that has to ask you what you meant is a blocking dependency on you. A spec is how you answer the question once, in advance, for a worker that is not you. Whether that worker is a mobile developer in another room or a model in a loop turns out to matter much less than people are assuming.

the second benefit, which nobody markets

The thing I did not expect, and now value more: writing the contract first is a cheap way to find out you do not understand the feature.

There is a specific and recognisable moment, maybe fifteen minutes into describing an endpoint, where you cannot name a field. Not because naming is hard, but because you have just discovered that the thing you are modelling is actually two things, or that a case you assumed was rare is the main case, or that you do not know who is allowed to do this. That discovery is available for the cost of fifteen minutes of writing.

The same discovery, made during implementation, costs a day and arrives after you have written code shaped around the misunderstanding. Made after release, it costs a migration.

This is the part of the critique I think is exactly right, and it deserves more prominence than it gets: the value is the thinking, not the artefact. A specification that was generated for you, that you skimmed and approved, has none of this property. You bought the document and skipped the part that was worth having. If your workflow makes the spec cheap to produce and cheap to accept, you have optimised away the only step that reliably catches design errors.

where it actually goes wrong

Both camps in the current argument are, in my experience, worrying about the wrong failure. The spec-first practice does go wrong, reliably, in two ways, and neither is "it is secretly waterfall."

The spec becomes the program. There is a point where a specification stops describing behaviour and starts describing implementation — every branch, every error case, every field transformation. When you get there, you have written the program twice, in a language with no compiler and no tests. This is not a hypothetical; it is the natural drift of anyone conscientious, because each additional detail feels like it removes ambiguity. It does. It also doubles your maintenance and moves nothing closer to done.

The line I use: the spec describes the contract and the decisions. It does not describe the algorithm. If someone reading it could not choose their own implementation strategy, it has gone too far.

Nobody updates it. The implementation ships, reality intervenes, the code changes, the document does not. Six months later the specification is a confident and detailed description of something that does not exist, which is worse than no document at all, because people trust it.

The version of this that AI makes sharper: if the spec is your source of truth and the code is generated output, this drift is supposed to be impossible, because you regenerate. In practice generation is not deterministic, people patch generated code under deadline, and now you have drift in both directions and no clear authority. I do not think this is fatal. I do think anyone claiming the spec is the source of truth needs to have thought about it a lot harder than the marketing suggests.

the half of the conversation that is missing

Almost every serious treatment of spec-driven development ends up at the same sentence: you still need rigorous, deterministic verification around all this. Everyone nods. Very few people say what that concretely is.

I would like to be specific, because this is the part I have actually built, and it is not exotic.

Rules that are checked by a program, not by a person. Every codebase has architectural conventions — this layer must not import that one, this module must not know about the framework, this package must remain usable in a browser. Conventions survive exactly as long as the person who cares about them reviews every pull request. Turn each one into a script that fails the build, and it survives indefinitely. This matters more when code arrives faster than you can read it, which is the entire premise of the current moment.

Check the output, not the intention. A rule verified against source code tells you what someone meant. A rule verified against the built artefact tells you what consumers actually get. Those are different, and the gap between them is where the interesting violations live — something re-exported, something bundled in by a transitive dependency, something a build step inlined.

Guards that can fail in both directions. A check that confirms a forbidden thing is absent will pass happily on a build where the separation you were protecting silently stopped happening at all. If you are asserting that A does not contain B, assert somewhere that the thing which should contain B still does. A guard that can only fail one way is a guard against one specific mistake, not against the class of mistake.

Write paths that do not involve the generator. If a model proposes a change to your data, the code that performs the change should be ordinary, deterministic, and testable with no model present. Then the risky part is confined to the proposal, and the part that touches your system is covered by tests that run in milliseconds and do not need an API key.

None of this is new. All of it is dull. That is the point: the verification layer that makes fast generation survivable is made of the same boring practices that made fast typing survivable, and the reason it feels new is that most teams got away without it when code arrived at the speed of one person's hands.

so: is it waterfall?

Partly, and I think that should be uncontroversial.

Waterfall had two halves. The good half was: agree the interface before you build behind it, and think hard before you commit. That half was always right, has never stopped being right, and is what every well-run team does under some other name — design docs, RFCs, API review, ADRs.

The bad half was: decide everything up front, and treat the decision as final because changing it is ruinously expensive. That half was a consequence of the cost of change, not a principle anybody actually believed.

Spec-driven development is the first half, in a period where the second half's cost has dropped. That is a genuinely reasonable thing to be doing. It is also not new, and the people saying "we have seen this before" are correct — they are just wrong that having seen it before is an argument against it.

The thing I would actually worry about is subtler than either side's position. It is that a practice whose value comes from the thinking is being sold on the grounds that it is fast. Those pull in opposite directions. If the spec becomes something you generate, skim and approve, you have kept the ceremony and thrown away the reason for it — and you will not find out for about six months.


by Krishna Adhikari · Aug 11, 2026
share
// related.transmissions

Keep reading.