Spec-driven development is not a silver bullet. It is the new Scrum.

Writing the requirements down before the agent starts is a real upgrade over vibe coding. It is also a process, not a solution. The spec is prose, prose does not execute, and the wall you hit at month three just moves to month six.

Spec-Driven Development Is the New Scrum

Image: METAHEURISTIC

Alexander Myasoedov

+Alexander Myasoedov Alexander writes about the operational side of shipping production AI - agents, retrieval, evals, and the guardrails that keep them from going sideways.

It is month four. The repository has a constitution.md, a spec.md, a plan.md, and a tasks.md. All four are current, all four were reviewed, and everyone involved did the work honestly. CI is green. The burn-down on tasks.md is a clean staircase and the team shipped more surface area this quarter than in the previous two combined. Then a reconciliation job starts double-counting refunds on retry, and it takes four days to find, because the person who wrote the retry semantics into the spec is not the person who reviewed the generated code, and neither of them is on call. Nothing in the spec is wrong. Nothing in the test suite is red. The system simply does something no one intended and no artifact in the repository disagrees with it.

That gap is what I want to talk about, because spec-driven development is currently being sold as the thing that closes it. I do not think it closes it. I think it widens the runway in front of it, which is worth something and is not the same claim.

What it actually fixes

Start with the credit, because it is real. GitHub’s Spec Kit runs /specify, then /plan, then /tasks, and drops four markdown files in the repo before a line of code exists. Kiro will not generate until you have a requirements.md, a design.md, and a tasks.md. Both do something that a chat window fundamentally cannot.

Vibe coding

  • Requirements live in the prompt window
  • Review begins after the code exists
  • Intent dies with the session
  • The only artifact is the diff

Spec-driven

  • Requirements are collected and committed
  • Review begins before generation
  • Intent outlives the author
  • The artifact outlives the diff

That is a genuine upgrade and I would take it over the alternative on any team larger than one. Forcing someone to write down what the thing is supposed to do, before an agent produces nine hundred lines claiming to do it, catches a category of misunderstanding at the cheapest possible moment. It gives a team a shared conceptual map. It moves the review gate to the left. None of that is nothing.

The trouble starts when people describe that as solving the problem rather than rescheduling it.

Why Scrum is the right comparison

I do not use that comparison as an insult. Scrum was a real improvement over what came before it. Teams were shipping against Gantt charts drawn by someone who had since left the company, and Scrum replaced that with a cadence, a visible backlog, and a ritual where problems surfaced weekly instead of at the demo. If you lived through the transition, you remember it as better, because it was better.

Then came the certification bodies, the tooling vendors, the role whose actual job is maintaining the artifact rather than the system, and the metric that turned out to be easier to move than the outcome. Teams started measuring velocity points. The ceremony became the deliverable. Production incidents continued at exactly the previous rate, because nothing in the ceremony had ever been aimed at them. The improvement was real and it was bounded, and the industry spent a decade acting as though it were unbounded.

Spec-driven development has the same shape and is at an earlier point on the same curve. The tooling vendors are already here. The role is forming: somebody on your team is becoming the person who owns the spec. The ceremony is forming: spec review before generation. The metric is forming, and it is going to be tasks closed in tasks.md, because that number is right there and it moves.

The test I would apply to any process claim: Does it change what the system does under load at three in the morning, or does it change how the team talks about what the system does? Scrum was the second one. So is this.

The spec doesn’t execute

The “naturalness” with which we use our native tongues boils down to the ease with which we can use them for making statements the nonsense of which is not obvious.

Edsger W. Dijkstra, EWD667

Kiro asks for requirements in EARS notation, the Rolls-Royce syntax from 2009: WHILE <precondition>, WHEN <trigger>, the <system> SHALL <response>. It is a good format. It kills a lot of ambiguity. It is also structured English, and structured English is English. Nothing in the toolchain type-checks “the system shall break the network connection.” Two EARS requirements written by two people six weeks apart can flatly contradict each other, and the contradiction is invisible: no compiler, no test, no linter, no runtime. The only mechanism that catches it is a human reading both at once with the full system in their head, which is precisely the resource the whole exercise was meant to spend less of.

This is why the notation we already have for instructing machines is a programming language. That is not a preference, it is the entire reason formal languages exist. A program cannot be self-contradictory without something failing. A spec can be self-contradictory for a year and the only symptom is that two engineers implement two different systems and both believe they complied. The specification is a conceptual map for humans, and it is genuinely useful as one. A map is not a mechanism.

What a spec is, precisely: A specification you cannot execute is not a contract. It is a memo with strong opinions, and its accuracy is enforced entirely by whoever happens to read it next.

I have argued elsewhere that when you rewrite a legacy system you should anchor the agent on the spec and not the old code, and I still believe that. The two claims sit together fine. Anchoring on the spec is the best choice among the available anchors. It is not a guarantee that the implementation matches, and nothing about drawing a better map makes the territory start verifying itself.

The anatomy of a perfect spec

Let me steelman it as hard as I can. Forget the thin spec written under deadline. Imagine the complete one: inputs with their types, expected outputs, preconditions, functional requirements, non-functional requirements, acceptance criteria, bounded error cases, and the invariants that must hold across all of it. Every field populated, by someone who knows the domain, reviewed by someone who also knows it. This is the artifact the methodology is actually promising, and it is a good artifact.

Now change the question. Instead of asking whether each field is filled in, ask what enforces each field when nobody is looking.

Figure - A complete spec and an enforceable spec are different documents
Lens:
Every field of a maximal spec, filled in by someone who knows the domain. Under as written the document scores a clean eight out of eight. Switch to machine-checkable and ask what enforces each row without a human. Three survive, and only because they stopped being prose. Three more are promises that something else will be built later. Watch which rows go dark: they are the ones people argue about after the incident.

Inputs and outputs survive, on one condition: you wrote them as a schema, and a validator can compare a payload against it. That is real enforcement, and notice what it cost. The enforceable part of your specification is the part that stopped being prose. Invariants survive on the same condition, once someone expresses them as property tests.

Acceptance criteria are the interesting case, because they feel enforceable and are not. A criterion becomes enforcement only when somebody translates it into an executable test, and the spec does not perform that translation. A human or an agent does, by hand, and the translation step is exactly where intent leaks out. The same is true of non-functional requirements: “the system shall respond within 200ms at p99” enforces nothing until somebody builds the load test and wires it into CI. Writing the sentence does not produce the load test. It produces the expectation that someone else will.

Bounded error cases are worse, and the word doing the damage is “bounded.” The set is bounded by what somebody thought of on the day they wrote it. Production is not bounded by that. Preconditions and functional requirements do not even pretend: they are prose, they stay prose, and they get satisfied to whatever degree the implementer’s reading of them happened to match the author’s writing of them.

So the complete spec and the enforceable spec are two different documents. The gap between them is closed by hand, every single time, by exactly the humans the process was meant to relieve. Both documents have a score, only one of them gets looked at in review, and it is not the one that predicts your incident rate.

The spec is a prompt with version control

Strip the methodology away and look at the mechanism. What does spec-driven development actually do to the system? It assembles a large, structured, persistent block of context and places it in front of the model on every invocation. That is context engineering with a governance story attached to it, and I want to be clear that context engineering works. Conditioning the sampler on a well-organized description of the target narrows the output distribution and centers it better. Measurably. This is not a trick.

But name it accurately, because the name determines what you should expect. You are not specifying a system. You are conditioning a generator. The output gets closer to what you described, on average, across samples. It does not become a function of the input.

Which means it inherits every failure mode of context. It competes for window space with the code the agent also has to read. It goes stale in the exact way any other duplicated description goes stale. Past a certain length, attention over it is uneven, so a sentence in section 7 does not carry the same weight as a sentence in section 1. And compliance with any given line is probabilistic rather than enforced.

Say it in the terms that predict behavior: A spec does not constrain the model. It conditions it. Constraints fail loudly and always; conditioning fails quietly and sometimes, which is much harder to notice.

The wall moves, it does not lift

Figure - Spec rigor buys runway, not altitude
Spec rigor: 0%
Unverified complexity accumulates as the team ships. The dashed line is the verification ceiling: how much of the system the team can actually hold, review and predict. Drag spec rigor from 0 to 100 and watch the crossing point slide from month three toward month six. Watch the ceiling too. It never moves.

Here is the number I would bet on, offered as a field estimate rather than data. A codebase built from unreviewed agent output with no shared requirements starts collapsing under its own weight in about three months: nobody can predict the blast radius of a change, every fix reveals two more, and the team’s real velocity goes negative while the dashboard still looks fine. Put the same team on a maintained spec and I would move that to six. Six months of runway is worth paying for. I want to be precise about what you bought, though, because it is runway and not altitude.

The ceiling is untouched. Context windows still cannot hold the system, and the spec is now competing for the part of the window the code needs. Requirement complexity still grows faster than any document describing it, because a document grows linearly with features and interactions grow quadratically. Side effects still cross module boundaries that no spec section mentions, because the spec describes intent and side effects are what the implementation does on the way to satisfying intent. Latent bugs still accumulate at whatever rate your verification catches them, which the spec did not change.

Month six arrives and it looks exactly like month three did, with better documentation of how you got there.

Complexity is conserved, not removed

You now maintain two artifacts that describe one system, and they can disagree with each other silently and indefinitely. That is not a reduction in complexity. It is complexity spread thinner across more surfaces, which reads as simplification because no single file looks hard anymore.

Watch what happens when the author changes. A new product manager or architect arrives and rewrites the spec, in good faith, correctly. Now regenerate. The implementation does not land on a clean slate: it lands on a codebase that grew along the previous spec’s path. Every module boundary that exists because of a sentence that no longer exists is still in the tree, still imported, still being read by the agent as prior art about how this system is built. The new spec’s implementation inherits the old spec’s shape and nobody involved can point at the file where that happened.

The same mechanism produces abstractions that only make sense on the page. A seam gets introduced because it made a paragraph read cleanly, not because the code needed a seam there. A human writing that module would have felt the wrongness in their hands. Prose does not transmit that feeling, so the leak ships and gets built on.

It also inflates the code itself. Every sentence in a requirements document tends to become a function, and every “the system should also handle” becomes a branch. Nothing in the document has any notion of “this case is rare enough that a code path for it costs more than it saves.” That judgment is the thing experienced engineers spend half their effort on, it deletes enormous amounts of code, and it never appears in a specification because specifications are written in a mode where completeness looks like virtue.

The velocity illusion

What gets counted

  • Lines generated per day
  • Tasks closed in tasks.md
  • Features per sprint
  • The staircase looks great

What gets paid

  • Discovery and dead ends
  • Reviewing code nobody wrote
  • Patch, then re-sync the spec
  • Regenerate, then prove nothing else moved
  • Then the hours after it breaks

The perception of leverage comes from the left column, which is easy to measure and moves immediately. The right column is the actual bill and most of it arrives later, attributed to something else. Generation was never the expensive part of software. The expensive parts are discovery, review, the patch cycle, keeping two artifacts in sync, and the compounding cost of the defects that got through, and verification is now the bottleneck in every one of them.

There is a step in that right column people consistently forget to price: proving that the regeneration did not move something critical somewhere else. When you patch by hand, the blast radius is the patch. When you regenerate a module from an updated spec, the blast radius is the module, and confirming that the other seventeen call sites still behave is work that scales with the size of the system rather than the size of the change. A spec makes generation faster and slightly improves review. It does not touch the rest of the ledger, and it adds a line to it.

The API contract thought experiment

If a precise text artifact is what makes agentic generation work, then I do not need a spec framework to test the idea. I can write an OpenAPI document for a public REST surface, hand it to an agent, and say: implement the database layer behind this. Same method, same promise, and the contract is strictly better than a spec, because it is machine-readable and a validator can genuinely check a response against it.

Now run that for a year. The contract holds the entire time. Underneath it: a migration that took a lock on a hot table, a UUID primary key that destroyed index locality, a query that was fine at ten thousand rows and is a sequential scan at ten million, a cache that nothing invalidates on the write path, a schema whose growth pattern nobody chose. Every one of those is invisible from the contract. Every one of them is what actually takes the service down. The contract stays green all the way to the incident.

That is the honest version of what spec-driven development offers, and it is worth sitting with, because the contract case is the strongest possible version of the argument. It is machine-checkable, it is unambiguous, it is versioned, and it still tells you nothing about the layer where systems die. A spec written in English has the same geometry with less rigor. It constrains the surface and goes quiet exactly where the danger is.

Test inversion

The second failure compounds more quietly, and I see it in nearly every generated repository: the tests come out bloated and tautological. Mocks for everything the code touches, assertions on the logger’s exact string, snapshots of debug output. A test that mocks the repository, calls add_balance(account, 50), and asserts the mock received 50 has established that the function you wrote is the function you wrote. It will stay green through a schema change, a lost write, and a double-applied retry.

This happens because a specification describes operations, and an agent asked to test an operation will assert the operation. The test mirrors the implementation, the suite grows enormous, the coverage number climbs, and the information content per test is close to zero. Worse, the suite now actively resists change: rewrite the internals correctly and four hundred mirror tests go red for no reason connected to correctness.

The fix is to invert. Assert what must be true regardless of how the thing was built.

Tautology
Mirror test
Mocks the repo, calls add_balance(acct, 50), asserts the mock saw 50. Green because the function exists.
Invariant
Conservation
Across any sequence of operations, total balances plus pending transfers is unchanged. Green only if the arithmetic is real.
Invariant
Replay
Rebuilding from the ledger reproduces the stored balance. Exercises the write path the mock never touched.

Add idempotency to that set: applying the same transfer id twice leaves the balance where one application left it, which is exactly the refund bug from the opening. Add a bound: no sequence of operations produces a negative balance on a non-credit account. Notice that none of these assertions name add_balance. That is the point. An implementation cannot satisfy them by construction, so a passing run carries information. Tautological tests go green because the code exists; inverted tests go green because the code is correct.

Here is the uncomfortable part, and it is the hinge of this whole piece: you cannot reliably get inverted tests from the context that wrote the implementation. It knows what the code does, so that is what it asserts. The mirror is not a prompting failure you can fix with a better instruction in spec.md. It is a structural consequence of one context holding both jobs.

What actually grounds a system

If the spec is not what makes agentic coding pay off, something has to be. In every team I have seen get real return out of this, the same four things were in place, and none of them is a document.

The first is evals that actually run: a graded suite executed per change, not a vibe check at the end of the week, and built so that a pass means this run caused the outcome rather than the environment already containing it. The second is observability that answers questions nobody pre-registered. A spec makes a claim about retries; a trace shows you the retry, its p99, and the idempotency key it actually used. When those two disagree, only one of them is evidence.

The third is the inverted test suite from the previous section, because that is where intent gets encoded in a form that fails loudly. And the fourth is the one that gets left off every list: at least one human who can explain what the process does at three in the morning without opening the repository. Generated code plus a maintained specification is not comprehension. Comprehension is a person who can predict the blast radius of a change before making it, and it is the single scarcest input in this entire discipline.

The reason return on agentic coding disappoints is not that the requirements were vague. It is that generation scaled by an order of magnitude and those four functions did not scale at all.

Where the spec collides with your setup

A smaller and dumber problem will annoy you before any of the above does. The spec is not the only file telling the agent what to do. There is an AGENTS.md, a CLAUDE.md, whatever rules file the next person’s tool of choice reads, a house style document, and a local dev setup that quietly contradicts the spec’s assumptions about how the service is even run. When they disagree, nothing errors. The agent picks one, per invocation, and the pick can change between runs. Multiply by a team where every person runs a different policy file and the spec is authoritative in the repository and advisory in practice.

Where it actually works

I want to be useful rather than merely negative, so here is the scope where I think this earns its keep. A small service with a bounded surface, where the whole spec fits in one person’s head and one context window at the same time. Greenfield work, where there is no existing baseline for a regenerated implementation to inherit shape from. Handoffs, where the realistic alternative is a chat log and somebody’s memory. And teams that have already built the evals, the observability and the inverted tests, and want a requirements discipline sitting on top of a foundation that can catch it when it is wrong.

Where it goes badly: a large system in year three, a team with turnover in whoever owns the spec, and any situation where the specification is the primary verification story rather than an input to one.

The rule I would put on the wall: Spec-driven development is a good input discipline and a bad output guarantee. Use it to decide what to build. Never use it to decide whether what got built is correct.

What actually moves the number

Not a better document. A second agent that is paid to disagree.

Figure - An adversary rejects until it runs out of attacks
Verifier:
Five real defects are hiding in the patch. With the verifier off, v1 merges on the first step and all five reach production behind a green build. Turn the verifier on and watch it reject with a reproduction each round until it runs out of attacks. Note both numbers at the end: four defects caught, and roughly seven times the tokens. One still escapes, because fuzzing never triggered it.

Split the loop. The implementer agent produces the patch. The verifier agent never sees the implementer’s reasoning, only the spec and the running system. It builds the thing, runs it, fuzzes the inputs, calls the retry path twice, and hunts for daylight between what the spec promised and what the process actually does. When it finds daylight it rejects with a reproduction, not an opinion. The implementer patches. The loop runs again. Nothing merges until the adversary runs out of attacks.

That is TDD with the roles separated, and the separation is the whole trick. It is the direct structural answer to the mirror-test problem: generated tests come out tautological because one context wrote both the code and its tests, and an agent with a different objective function does not have that failure mode. This is the same reason code review is the skill that now separates engineers. The verifier does need to be built carefully. It has to assert the delta rather than the state, or it will happily pass runs where the environment already contained the answer, and it needs its own view of the running system rather than a summary of what the implementer believes it did.

Notice what the spec becomes in that arrangement, because this is the part I actually like. It stops being the guarantee and becomes the adversary’s target definition. It is no longer pretending to enforce anything. It is telling a fuzzer where to aim. That is a job prose is genuinely good at, and it is a much smaller and more honest job than the one the methodology currently advertises.

It is expensive. You are running the system under fuzz on every iteration and paying for an agent whose entire output is rejections, most of which get thrown away. That is what makes it look wasteful and it is also what makes it work.

But look at what kind of expensive it is. It is a compute problem, and compute problems have a curve: cost per verified change falls every year without anyone needing a better idea. Conceptual problems have no such curve. “Write the requirements more carefully” has no exponent on it. It costs the same in 2030 that it cost in 1998, when it was called a functional specification, and it fails for the same reason it failed then.

The trade worth making: Given the choice, prefer failure modes that get cheaper. A verification loop is a bill that shrinks; a documentation discipline is a bill that does not.

So keep the spec. It makes the team think before it generates, it survives turnover better than a chat log, and it is the right anchor for a rewrite. Just be honest about what you purchased: a longer path to the same wall, and a process your organization can hold meetings about. Scrum was a real improvement too.

The bugs were never in the map. Go build the adversary.

Work with us

Production AI, with guardrails.

Start with a fixed-scope AI Workflow Audit. We map the opportunity and quote a build.

Start a Discovery Sprint →