Spec-Driven Development: Best Practices, Techniques, and When to Use Each
AI coding agents are good at producing code and bad at guessing what you meant. Give an agent a vague prompt and it will happily fill every gap with an assumption — the wrong database, an unrequested OAuth flow, error handling that doesn't match how the rest of the system fails. Spec-driven development (SDD) is the discipline that grew out of this problem: instead of prompting and hoping, you write a precise specification first, and treat it as the source of truth that both humans and agents are held to.
This isn't a new idea — it borrows heavily from BDD, API contract testing, and old-fashioned design docs. What's new is that in an agent-driven workflow, the spec stops being a artifact you write and then ignore. It becomes the thing the agent actually builds from, and the thing tests are written against to catch drift.
Why this matters now
Unit tests catch broken functions. They don't catch an agent that quietly reintroduces a vulnerability it "fixed" last week, violates an architectural boundary between services, or reinvents a decision you already made and documented nowhere the agent could see it. Studies on LLM-generated code have found vulnerability rates ranging from roughly 10% to over 40% depending on the benchmark, and the failure mode compounds: without a spec encoding a constraint, the same gap resurfaces every time the code is regenerated. Patch the code and the next regeneration cycle reintroduces the same bug, because nothing captured why the fix mattered.
The core distinction that makes SDD different from a design doc: a design doc is read by humans, who fill gaps with judgment and context. A spec written for SDD is read by an agent and, ideally, executed — as acceptance tests, contract checks, or a CI gate that fails the build on divergence.
The three techniques, and when to use each
Most 2026 guidance on SDD converges on the same three-level ladder. They aren't competing methodologies — they're increasing levels of how much authority the spec has over the code, and you pick the level that matches how much is at stake.
1. Spec-first. The spec seeds the initial generation — you write it, the agent builds from it — but nothing enforces that the code keeps matching it afterward. Code can drift from the spec as it's edited, and nobody's alerted when it does.
Use this for AI-assisted features, prototypes, and early-stage work where requirements are still likely to change. It's the right amount of ceremony when the cost of the code quietly diverging from the spec is low, or the code is short-lived anyway.
2. Spec-anchored. Spec and code evolve together, and automated tests enforce that they stay aligned — acceptance criteria in the spec map to tests that fail the build if the implementation violates them. This is the level most 2026 sources — including the arXiv paper that's become a reference point for the field — call "the sweet spot for most production systems."
Use this when multiple people (or multiple agents) touch the same code over time, when the system is integration-heavy or has real architectural boundaries to protect, or when you need an audit trail of what was decided and verified. This is where the discipline earns its overhead.
3. Spec-as-source. Humans edit only the spec. Code is fully generated and never hand-edited — the spec is the source of truth in a literal sense, and regenerating from it is how you make changes. This eliminates drift by design, because there's no hand-edited code left to drift.
This is still mostly aspirational outside of narrow, well-bounded domains (API-first services with mature codegen, for instance). It demands generation tooling mature enough to trust without a human reviewing every line, which most teams don't have yet. Don't reach for this level because it sounds like the "most rigorous" option — reach for spec-anchored instead unless you have a specific reason spec-as-source is viable for your domain.
The practical rule: default to spec-anchored. Spec-first is fine for the exploratory 20% of your work; spec-as-source is where the hype lives, not where most teams get value today.
Writing specs an agent can't misread
The single biggest failure mode in SDD isn't a bad spec — it's an ambiguous one. EARS (Easy Approach to Requirements Syntax) is the closest thing to a standard for acceptance criteria that read the same way to a human and a model. Five patterns cover almost every case:
- Ubiquitous — "The system shall log every authentication attempt."
- Event-driven — "WHEN a user submits the login form, THE system SHALL validate credentials."
- State-driven — "WHILE a sync is running, THE system SHALL show a progress indicator."
- Unwanted behavior — "IF validation fails three times, THEN the system SHALL lock the account for 15 minutes."
- Optional — "WHERE MFA is enabled, THE system SHALL require a TOTP code."
Criteria written this way map almost one-to-one onto test cases, which is what makes a spec executable instead of advisory.
A good spec answers six questions
If a spec leaves any of these open, an agent will answer them for you — usually not the way you'd have chosen:
- What does "done" look like? Not a feature name — an outcome. "A user can sign up with email and password, gets a verification email, and stays logged in across a page refresh" beats "build auth."
- What's explicitly out of scope? Agents expand scope by default. If OAuth isn't part of this task, say so — an agent that's seen a thousand auth systems assumes it belongs.
- What constraints and assumptions already hold? Stack decisions, third-party rate limits, performance requirements — anything that isn't obvious from the codebase alone.
- What's already been decided? If you picked the schema or the encryption library, write it down. An agent that doesn't know a decision was made will make its own.
- How does the work break into tasks? Asking for too much in one shot is one of the most common ways agents go wrong. A breakdown into discrete, independently verifiable sub-tasks also lets multiple agents work in parallel when they're not touching the same files.
- How is it verified? Not "does it work," but which tests pass and which edge cases are covered. This is what a verification pass — human or agent — checks against.
The canonical workflow
Most SDD tooling in 2026 (GitHub Spec Kit, AWS Kiro, Claude Code's SDD workflows, Cursor's Plan Mode) implements roughly the same pipeline, with a human review gate between each phase:
The golden rule across every tool that implements this: never skip straight from spec to code. Review the plan before task breakdown; review the tasks before implementation. Skipping a phase is where drift starts.
A worked example
A trimmed spec for a passwordless login feature, written in EARS:
## Feature: Passwordless magic-link login
### Acceptance criteria
- WHEN a user submits a valid email
THE system SHALL send a one-time login link valid for 15 minutes.
- IF a login link is used more than once
THEN the system SHALL reject it with HTTP 410 Gone.
- WHERE the email is not associated with an account
THE system SHALL still return HTTP 202 (no account enumeration).
- THE system SHALL store link tokens hashed, never in plaintext.
### Out of scope
- Social login, SSO, password fallback.The plan that follows names the stack, the data model, and the decisions already made (token format, hashing scheme). The task list decomposes into a migration, two endpoints, contract tests for the 202 and 410 paths, and rate limiting — each task citing the spec clause it satisfies. When a test fails later, you know exactly which piece of intent broke, not just which line of code.
Best practices
- Commit a constitution before the first spec. An
AGENTS.mdor.specify/memory/constitution.mdwith project-wide rules — codify them as ubiquitous EARS statements like "the system shall use TypeScript strict mode." - One feature, one spec. Keep specs to one to three pages; split anything larger instead of letting a single spec sprawl.
- Write in domain language, not implementation detail. A spec that reads like pseudo-code has lost the point — you've written the program twice, once in prose and once in code, and now you have two things to keep in sync.
- Close the door on scope explicitly. An "out of scope" section does as much work as the "in scope" one.
- Cite specs in commits.
feat(auth): magic link, refs specs/004-magic-link/spec.md— it's what makes the audit trail actually useful later. - Treat specs as durable, code as generated. Specs outlive any particular implementation; that's the whole point of writing them.
- Consider an adversarial pattern for anything that matters. A separate verifier agent, checking an implementor's output against the spec, catches more than letting the implementing agent grade its own work — implementors are structurally optimistic about what they built.
When to skip it
Not every task needs a spec, and pretending otherwise is its own failure mode. Skip it for throwaway prototypes, solo short-lived projects, and exploratory work where you genuinely don't know the requirements yet — writing a rigorous spec for something you're still discovering just means rewriting the spec as often as the code. The practical test: if you'd be annoyed to find an agent interpreted your request differently than you meant, write the spec. If a quick follow-up prompt would fix it, skip the ceremony.
SDD also has real skeptics worth taking seriously — Thoughtworks places it in the "Assess" ring of their Technology Radar, not "Adopt," and some critics argue it's largely contract-design and waterfall thinking rebranded for the AI era. Both things can be true: the underlying discipline isn't new, and it's still worth adopting, because the value was never the novelty — it's that writing the spec is where the thinking happens, and that matters more, not less, now that an agent is the one typing the implementation.
Sources: