AdvantageWorks Team 13 min read

Spec-Driven Development: AI Code That Matches Your Architecture

Modular steel framing being seated into place on a concrete structure beside a weighted architectural blueprint

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.

Scaffolding, snapped layout lines, and an inspection checklist on a construction site representing three constraints

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.

A construction sequence from blueprint to framing to fitting to an inspector checking the work with a level

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.

Frequently asked questions

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 specification is the source of truth, and the code is an output checked against it rather than the record of what the system does.

The difference from prompting is the durable artifact. A prompt is ephemeral and lives in a chat window, so when you only prompt, the code becomes the de facto spec and nobody can say what the software was supposed to do, only what it currently does. This is what the term vibe coding describes. Spec-driven development keeps a versioned statement of the what and the why that a human reviews and approves before code exists, so both people and agents have something to generate against and verify against.

Spec-driven development uses the same instinct as behavior-driven development and Specification by Example, agreeing on concrete, checkable examples of correct behavior before building, but it points that instinct at a non-human author working fast across many tasks. A bdd specification written as Given-When-Then is already an acceptance criterion in executable form, so the bridge is direct.

Two things change. The author changes, because the agent now writes the code while the human-written acceptance criteria keep it honest, and the scale changes, because those criteria carry more weight when the author will confidently pass its own self-written tests if you let it. Spec-driven development also adds architecture as a first-class constraint, which classic BDD does not cover. You do not need Gherkin or any specific framework to get the benefit. You need agreed examples of done, written by humans and enforced against the output.

Humans write and own the spec and the acceptance criteria, and the AI agent generates code against them. The specification is a reviewable statement of the what and the why that a human edits and approves, and the acceptance criteria are the human-authored definition of done that the agent's output is verified against rather than against its own self-graded tests.

Ownership is shared across roles. Product and engineering own the specification, engineering or QA own the acceptance criteria, and the tech lead or architect owns the encoded architecture rules the agent must respect. An agent can help draft a first pass, but the point is that a human caught the under-specified requirement while it was still a paragraph instead of a pull request. The acceptance criterion has to be written from intent, not by the agent, or it will happily assert the wrong behavior with full confidence.

It can save both, because the expensive failure in software delivery is wrong-direction work, and constraint removes most of it. 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 our estimated conventional baseline for comparable work, with technical documentation produced as a by-product of the spec-first flow. These are observed results from our own delivery, not a universal benchmark or a guarantee.

The mechanism matters more than the numbers. Cost falls because the agent does not spend hours generating code against the wrong pattern, since the architecture constraint rejects it early, and because writing the spec surfaces a misunderstood requirement before generation rather than in QA. The documentation is a by-product because the specs and acceptance criteria that drove the build are themselves the reviewable record of what the system does. The overhead is real, but it is paid up front in cheap edits instead of late in expensive rework.

Spec-driven development does not pay off for throwaway prototypes, exploratory spikes, and novel problems with no established pattern to constrain toward. 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, so generate freely, throw it away, then specify what survives.

It also fails when the spec is wrong-sized. A spec written too thin produces vague code with the same confidence as a precise one, so garbage in, garbage out applies to agents with force, and a spec written too rigid freezes decisions that should stay open and slows iteration to a crawl. There is also an agent-capability ceiling: on novel problems where nobody has worked out the right way to fit, constraint gives less leverage because the agent has less to lean on. The method helps most where there is a settled right way for new code to fit.