Engineering
11 min

The Spec Is the New Source Code

When a model turns a clear description into working code in minutes, the bottleneck moves upstream — to the specification. The spec becomes the thing you actually author and maintain; the code becomes a compiled, semi-disposable output. Why intent becomes the primary artefact, and what that demands of the human.

Who's actually writing here?

There is a moment that would have sounded like science fiction three years ago and is now routine: you type a description — "an endpoint that takes an order, checks the basket against stock, rejects it with a clear error if there isn't enough, and otherwise creates a reservation" — and minutes later code is running that does exactly that. Compiled, tested, deployable.

Pause for a second and ask what you actually produced in those minutes, and you get an uncomfortable answer. Not the code — the model typed that, and you could regenerate it tomorrow from the same description. What you produced was the description. The precision with which you pinned down intent, constraints and behaviour was the real work. The code was a consequence of it.

This isn't a curiosity at the margins; it's a shift in the foundations. For decades, the source code was the thing that mattered — the thing you authored, versioned, maintained and defended. The specification, where it existed at all, was decoration. That ordering is being turned upside down. The durable artefact is moving upstream, to the spec. The code becomes something you derive from it — closer to a compiled output than to handwriting.

The reversal

The old order had an iron law, and every experienced developer knows it: the code is the only accurate documentation. Everything else lies. The design doc describes a system that was never actually built that way. The wiki is two years old. The comment above the function describes what it was once meant to do, not what it does today. Only the code never lies, because the code is what actually runs. If you wanted to know how the system behaved, you read the code — not the spec.

That truth was unpleasant but correct, and it had a clear consequence: specs rot, code lives. So you invested in code and let specs fall into neglect. Why maintain a document that's already out of date while you're still typing it?

AI inverts that logic. When the model reliably produces the right code from a precise description, suddenly the spec is what has an effect and the code is what follows. A good spec produces good code. And — here's the sharp edge — a vague spec produces plausibly wrong code. The model fills every gap you leave open with an assumption. It doesn't ask; it guesses, and it guesses with total confidence. You wanted duplicate submissions handled idempotently — but you didn't write it down? Then you get an implementation that cheerfully creates a second order on the second identical request. The code looks impeccable. It just doesn't do what you meant.

With that, the bottleneck moves. Typing the code is no longer the expensive step; the expensive step is stating precisely what the code is supposed to do. This is exactly what people now call spec-driven or intent-driven development: you don't author the solution, you author the intent so sharply that the solution follows inevitably from it. The vagueness you used to carry around in your head and resolve unconsciously while typing now has to come out — onto paper, into the repo, into words. Because the model doesn't share your head.

What a good spec contains

If the spec becomes the primary artefact, it's worth asking what it's actually made of. And the answer is illuminating, because it is almost exactly the stuff a model cannot guess. A good specification is not a novel and not a forty-page ceremony. It's a terse, dense set of commitments:

  • Intent. What problem does this solve, for whom, and how do you know it's solved? Not "a discount system" but "returning customers should see a percentage reduction in the basket, derived from their prior-year spend." Without intent, the model builds something plausible-looking that misses the point.
  • Invariants. What must always hold, no matter what? An account never goes negative. An order total is never negative. Two users never get the same reservation. These are the sentences a model can't derive, because they come from the business, not from the code.
  • Edge cases. The empty list, the concurrent access, the timezone at the day boundary, the zero amount, the expired token. Precisely the cases that never show up on the happy path — and that the model skips if you don't name them.
  • Non-goals. The most underrated part. What should this explicitly not do? "No partial refunds in version one." "We don't handle foreign currencies." A non-goal saves more code than any feature description generates, because it stops the model inventing scope nobody ordered.
  • Acceptance criteria. The checkable statements on which "done" hangs. "At zero stock, reject with HTTP 409." "Two identical requests within ten seconds create exactly one order." The lovely thing about these: they're almost already tests. An acceptance criterion, cleanly phrased, is a test case in waiting — and acceptance-criteria-as-tests close the loop, because they both steer the model and afterwards prove it hit what was meant.

You can see the pattern: every one of these is context the model cannot derive from the task alone. The syntax it can do. The structure it can do. What it doesn't have is the picture of what must be true in this business, with this data, under these rules. That's exactly what the spec writes down — and it's the only thing it has to write down.

The spec as a living artefact

That makes a consequence unavoidable that many teams haven't yet drawn: if the spec is what produces the code, then the spec belongs in the repo. Not in a Confluence nobody opens. Not in a Google Doc owned by a former colleague. Next to the code, under version control, with history, reviewed in pull requests like code.

This is the real resolution of the old "the code is the only truth" problem — not by refutation, but by reversal. Specs used to rot because they trailed the code; you changed the code and forgot the document. Now the code trails the spec: you change the spec and re-derive the code from it. The spec can no longer go stale if it's the source the current version is generated from. The rot problem reverses direction.

In practice you already see this everywhere in early form. The README or AGENTS file a coding agent reads first, before it touches a line, is a spec — one that tells the model how this project ticks. The design doc from which scaffolding, module boundaries and interfaces can be generated is a spec. The collection of acceptance criteria that also feeds the test suite is a spec. These files are no longer documentation about the system. They are the entry point into it — the thing you change when you want to change the behaviour.

And because that's what they are, they deserve the treatment once reserved for code: reviews where someone challenges the intent before a line is generated. Diffs where you can see how an invariant changed. Blame that shows who decided, and when, that foreign currencies are out of scope. This is the point at which "we wrote something down once" becomes a maintained, owned artefact.

Regenerate, don't patch

Here the shift gets genuinely radical, because it changes how you deal with faults. In the old world, code was precious because a human had written it line by line. Throwing it away and rewriting felt like burning money — so you patched. You patched the bug, added a special case, glued a workaround over the crooked assumption. Over years, a geology of patches grew in which nobody could find the original intent any more.

When code comes out of a spec in minutes, the sums change. On a genuine fault, the first question is no longer "where do I patch?" but "is the fault in the code or in the spec?" And often it's in the spec: you never named the edge case, never captured the invariant, never drew the non-goal. Then the cheapest route isn't to operate on the generated code but to sharpen the spec and re-derive the affected part. You fix the intent, not the output.

This is more than a convenience; it's a different cost model. A workaround that costs a developer half a day — call it a few hundred £, if you're honest about it — produces code that someone will have to understand again next time. A sharpened spec costs the same half-hour of thinking, produces clean code and improves the artefact from which all the future is generated. The patch pays once; the better spec pays every time.

This doesn't hold absolutely — more on that shortly. An isolated one-line fix is an isolated one-line fix, and putting it through a spec round would be ceremony. But as a default stance it's right: when the code is semi-disposable because it's cheaply regenerated, you stop repairing the copy and start repairing the original.

The discipline the human has to bring

Now the catch, and it's a big one. This whole reversal demands of the human exactly what humans least like doing: thinking clearly up front.

Typing code was, for a long time, also a way of dodging the thinking. You started, felt while writing where it snagged, discovered the edge case because the compiler complained, understood the problem through building it. Typing was a thinking tool — slow, but forgiving. You were allowed to start unclear and become clear along the way.

Spec-driven development takes that forgiveness away. Writing a spec means resolving the vagueness at the front, before a line exists. You have to know the intent before you type it. You have to name the invariants before anything can violate them. You have to draw the non-goals before you're tempted to invent scope. That's harder than programming, not easier, because it demands the one job no tool takes off you: knowing what you actually want, and saying it precisely.

That's exactly where bad specs fail, and they fail in the most expensive way. Because — and this is the honest footnote — a spec can be as wrong as code. A precisely phrased, cleanly reviewed, repo-versioned specification built on a false assumption about the business reliably produces the wrong thing, at scale. Precision is not correctness. You can commit, with full care, in the wrong direction. The spec moves the thinking to the front; it doesn't abolish it.

Not everything deserves a spec

Which brings us to the honest counter-side, because anyone who takes this lesson too far replaces one kind of waste with another. Not everything deserves a formal specification, and spec-first as dogma becomes exactly the bureaucracy good engineers are rightly allergic to.

The throwaway script that shovels a CSV into a different format once needs no statement of intent with invariants and non-goals. You want it in five minutes and then never to see it again. Prefacing it with a spec would be as absurd as writing a requirements document for a shopping list.

The second case is more serious: exploratory work. Sometimes you simply don't yet know what you want — and the most honest way to find out is not to brood over an empty spec but to let the agent build a rough, half-baked version and react to it. You see the thing running, feel what's wrong, and then the intent takes shape. In this mode the spec emerges from the prototype, not the other way round. Insisting up front on a clarity you can't yet have isn't discipline; it's self-deception.

The resolution is the same as with every good engineering principle: match the ceremony to the blast radius. What happens, worst case, if this is wrong? If it touches money, customer data, authentication, anything irreversible, anything that lives and grows for a long time — then the intent deserves a real, versioned, reviewed spec, and the effort pays back many times over. If it's an afternoon experiment or a script that dies after a single run — then spec-first is ballast. Spec-driven development is a tool, not a religion. The mistake isn't building without a spec; the mistake is building the expensive, long-lived, dangerous thing without one and the cheap, dead script with one.

Conclusion

For decades, the source code was the thing that mattered — the durable artefact you read the truth of a system off, because everything else lied. That era doesn't end dramatically; it ends quietly: the moment a model reliably produces code from a precise description, the thing you actually author and maintain moves up a level — to the specification. The code becomes the output. The intent becomes the source.

This is no relief; it's a relocation of the effort. The hard work doesn't disappear, it moves forward: think clearly before you build; name intent, invariants, edges and non-goals so sharply that a model has nothing wrong to guess; and then maintain the artefact from which the code is generated, again and again. Dodge that work and you get plausibly wrong code at scale — faster than ever before.

That's exactly why, at NH Labs, we treat the spec as the primary artefact for everything that matters. Not for the throwaway script, and that's deliberate. But for the system that moves money, holds customer data and grows over years, we author and version the intent itself — in the repo, reviewed, owned — not just the code that follows from it. So in the end it isn't merely traceable what the system does, but evidenced and owned what it was supposed to do. The code can be regenerated. The intent has to stay.