A coding agent can produce a working feature in twenty minutes. That speed is the problem. The feature compiles, the tests it wrote for itself pass, and the pull request looks reasonable on a first read. Then it lands in a codebase that has spent five years settling into a set of patterns, and the agent has ignored every one of them. A new data-access path. A second way to handle auth. An abstraction that duplicates one three directories over. Nothing broke today. Everything got harder to change tomorrow.
That gap between "it runs" and "it fits" is what stops most CTOs from letting AI agents near production. The fear is rational. Ungoverned AI output does not fail loudly. It drifts, and drift compounds. The fix is not a cleverer prompt or a smarter model. It is constraint. Spec-driven development is the discipline of bounding an agent with architecture rules, a written specification, and explicit acceptance criteria so that generated code matches intent instead of fighting the system it lands in. This article walks through the method behind that, where the trust comes from, and what it produced in real delivery.
What spec-driven development actually is
The inversion is the whole idea. The spec drives the code, not the other way around. Spec-driven development is a way of working where a written, reviewable specification, the architecture it must respect, and explicit acceptance criteria together drive and bound what an AI agent generates. The spec is the source of truth. The code is an output of the spec, checked against it.
That inversion is what vibe coding lacks. There the prompt is throwaway and the code becomes the only record of what the system does. Nobody can say what the software was supposed to do, only what it currently does. Spec-driven development keeps a durable artifact that states the "what" and the "why" before any code exists, so both humans and agents have something to generate against and verify against.
It is a close cousin to BDD specification practice and Specification by Example, but the target is different. Classic behavior-driven development was built so business and engineering could share one language. Here the same idea of an executable, agreed definition of done is pointed at a non-human author. The agent writes the code. The spec and the acceptance criteria are how you keep that author honest.
Why AI-generated code breaks down at scale
A single AI-generated function is rarely the problem. The trouble starts when hundreds of them pile up without a shared constraint. The failures are consistent and worth naming, because a CTO needs to recognise them before approving the approach.
- Wrong patterns. The agent reaches for the most statistically common solution from its training, not the pattern your team standardised on. It writes idiomatic code for some average codebase, not yours.
- Duplicated abstractions. Without a map of what already exists, the agent rebuilds utilities, clients, and helpers that are already present. Each duplicate is a future bug that has to be fixed in two places.
- Plausible but off logic. The code reads well and handles the happy path. The edge case the business actually cares about is silently wrong, and the agent's self-written tests assert the wrong behavior with full confidence.
- Architectural drift. A new module reaches across a boundary it should not cross. No build breaks. The layering that kept the system understandable erodes one merge at a time.
- Untestable output. Code generated without a definition of done often has no clear seam to test, so it ships under-verified and stays that way.
The root cause under all five is the same. The agent has no durable source of truth for what the system should do and how new code should fit. Absent that, the existing codebase becomes the de facto specification, and a large codebase is a contradictory one. It contains the good pattern and the three deprecated ones, the boundary and the two places that already violate it. Ask an agent to infer intent from that, and it will infer something plausible and partly wrong, fast, at scale.
What good looks like is the opposite. The agent is handed the patterns it must use, the boundaries it must respect, and the behavior it must produce, before it writes a line. That is the job of the three constraints.
The three constraints that make AI code trustworthy
Most writing on this topic stops at "write a spec." A spec alone cannot govern an agent working in a mature system. Three constraints have to operate together, and each one closes a failure mode the other two cannot.
Architecture as a constraint
Architecture is the constraint competitors skip, and it is the one that matters most at scale. It is the encoded set of patterns, boundaries, and non-negotiables the agent must respect: which layer talks to which, the approved way to access data, the error-handling contract, the modules that are off-limits, the naming and structure conventions that keep the system legible.
In practice this lives as machine-readable context the agent loads on every task, not as tribal knowledge in a senior engineer's head. When architecture is a first-class input, the agent stops inventing a new data-access pattern because the constraint already tells it which one exists. This is what keeps generated code matching the system instead of fighting it. The specification says what to build. Architecture says how it is allowed to fit.
The specification
A prompt lives in a chat window and disappears. A spec is a versioned artifact a human reviews, edits, and approves, and that an agent reads as the binding description of the work. The specification is the reviewable statement of the "what" and the "why," written and agreed before code.
A useful spec names the behavior, the inputs and outputs, the constraints, the explicit non-goals, and the edge cases that matter. The discipline of writing it forces the ambiguity out before it becomes wrong code. Half the value lands before the agent runs at all, because a human caught the under-specified requirement while it was still a paragraph instead of a pull request.
Acceptance criteria
Acceptance criteria are the executable definition of done. They state, in checkable terms, what must be true for the work to count as complete, and the agent's output is verified against them rather than against its own self-graded tests. This is the gate. Code that does not satisfy the criteria does not merge, no matter how confident the agent is.
This is where the connection to BDD spec thinking gets concrete. Acceptance criteria expressed as Given-When-Then scenarios are readable by the team and runnable against the output. The agent did not write the test to match its own code. A human wrote the criterion to match the intent, and the code has to clear it.
A short way to hold the three together:
| Constraint | What it is | What it prevents | Who owns it |
|---|---|---|---|
| Architecture | Encoded patterns, boundaries, non-negotiables | Wrong patterns, duplication, drift | Tech lead / architect |
| Specification | Reviewable "what" and "why" before code | Ambiguity, scope creep, wrong-direction work | Product + engineering |
| Acceptance criteria | Executable definition of done | Plausible-but-off logic, under-verification | Engineering / QA |
The method in practice: how the loop runs
The constraints are the inputs. The method is how they run together as a loop, and it stays the same whatever tool you use. That is the point. This is a way of working, not a product.
The loop has four moves. Specify the behavior and the why, and review the spec as a human artifact before anything generates. Plan by breaking the spec into tasks small enough that each maps to a checkable outcome, with the relevant architecture constraints attached to each. Generate the code with the agent, bounded by those architecture rules and aimed at one task at a time. Verify the output against the acceptance criteria, automatically where possible. Anything that fails the criteria goes back into the loop with the failure as the next instruction, not forward into review.
Two examples show the loop catching drift before it costs anything.
In the first, an agent is asked to add a reporting endpoint. Left alone it writes a fresh database query inside the controller, the fast and common pattern. The architecture constraint states that all data access goes through the repository layer. The generation is rejected at verification against that rule, the agent is re-run with the constraint surfaced, and the second attempt uses the existing repository. The wrong pattern never reaches a human reviewer.
In the second, a spec for a discount calculation lists an acceptance criterion that stacked promotions must never produce a negative price. The agent's first pass handles the common cases and misses the stacking edge. The acceptance test fails on exactly that scenario. Because the criterion was written from intent and not by the agent, it catches what the agent's own tests would have passed without complaint. The fix happens inside the loop, before merge.
Neither example needs a specific vendor. It needs the spec, the architecture rules, and the criteria to exist and to gate the output. The tooling around that is a detail you can change.
The proof: what constrained AI delivery actually produced
Every competitor article asserts benefits and quantifies none. Here is the part that matters to a gatekeeper deciding whether this is real.
On a recent production engagement, our team delivered the full scope using this method in under 1,000 hours, at roughly 40% lower cost than the conventional baseline for comparable work, with technical documentation produced as a by-product of the spec-first flow rather than as a separate phase. These are observed results from our own delivery, not a universal benchmark or a guarantee, and the baseline is our estimate of the same scope built the conventional way.
What earns trust is not the numbers themselves but why the method produces them. The cost falls because the expensive failure in software delivery is wrong-direction work, and constraint removes most of it. The agent does not spend hours generating code against the wrong pattern, because the architecture constraint rejects it early. The team does not discover a misunderstood requirement in QA, because writing the spec surfaced it before generation. Less rework, fewer wrong-direction sprints, fewer late surprises. The speed is a consequence of catching errors when they are cheap.
The documentation is the cleanest example of the compounding effect. The specs and acceptance criteria are written as part of the work, in reviewable form, so the record of what the system does and why exists the moment the work is done. The artifact that usually gets cut under deadline pressure is the same artifact that drove the build. You do not document after the fact. The method already did it.
If your team is weighing whether to run this method in-house or bring in people who already work this way, that is the decision the next step is built for.
Next step
Constraining an agent well is a practice, not a switch you flip. The architecture has to be encoded, the spec discipline has to be real, and the acceptance gate has to hold under deadline pressure. That is the difference between AI output you can ship and AI output you have to clean up.
Fractional Agentic Team is the embedded team that already works this way, with architecture rules, specifications, and acceptance criteria built into how they deliver, so the method is running from day one instead of after a year of trial and error.
Talk to us about a Fractional Agentic Team
If you would rather start by mapping where this fits in your stack, an AI Transformation Discovery session is the lower-commitment entry point.
Where spec-driven development does not pay off
Trust runs on honesty about limits, so here is where this method is the wrong tool. Throwaway prototypes and exploratory spikes do not earn the overhead. If the goal is to learn whether an idea is worth pursuing, writing a full spec first slows the learning you are trying to do. Generate freely, throw it away, then specify what survives.
Specs written too thin fail in the other direction. Garbage in, garbage out applies to agents with force. A vague spec produces vague code with the same confidence as a precise one, and now you have spent the effort of writing a document without getting the constraint. The spec has to be specific enough to gate against.
Specs written too rigid have a cost too. Over-specifying early, before you understand the problem, freezes decisions that should stay open and slows iteration to a crawl. The skill is specifying the parts that are settled and leaving room where the answer is still genuinely unknown.
And there is an agent-capability ceiling. On novel problems with no established pattern to constrain toward, the agent has less to lean on and the method gives less leverage. Constraint helps most where there is a right way to fit. It helps least where nobody has worked out the right way yet.
How this relates to BDD and Specification by Example
If acceptance criteria as the definition of done sounds familiar, it should. It is the same instinct behind behavior-driven development and Specification by Example: agree on concrete, checkable examples of correct behavior before building, in language the whole team can read.
The bridge is direct. A BDD specification written as Given-When-Then is already an acceptance criterion in executable form. "Given a cart with two stackable discounts, when the total is calculated, then the price is never negative" reads as plain English and runs as a test. What spec-driven development changes is the author and the scale. The criteria now also govern a non-human author working fast across many tasks, and they carry more weight because that author will confidently pass its own self-written tests if you let it. BDD gave us the shared, executable example. Constrained AI codegen makes that example the thing standing between a plausible-looking generation and your main branch. You do not need to adopt Gherkin or any specific framework to get the benefit. You need the idea: agreed examples of done, written by humans, enforced against the output.
Key takeaways
- AI-generated code fails at scale through drift, not loud breakage: wrong patterns, duplicated abstractions, and slow erosion of architecture, because the agent has no durable source of truth for intent.
- The fix is constraint, not a better prompt. Three constraints operate together: architecture (how code must fit), specification (the reviewable what and why), and acceptance criteria (the executable definition of done).
- Architecture is the constraint most teams skip and the one that matters most at scale. Encode patterns and boundaries as machine-readable context, not tribal knowledge.
- The method is a loop: specify, plan, generate under constraint, verify against criteria. Failures go back into the loop before they reach human review.
- In our own delivery the method produced a production scope in under 1,000 hours at roughly 40% lower cost with documentation as a by-product, because constraint removes wrong-direction work. These are observed results, not a universal benchmark.
- Skip it for throwaway prototypes and novel problems with no pattern to constrain toward, and remember that a thin or over-rigid spec gives back exactly what you put in.