Spec-Driven Development: From Vibe Coding to Contracts Your Agents Can Ship
Vibe coding broke at review time. Spec-driven development fixes it: a four-artifact stack, one full worked example, real tooling, and metrics that prove it.
Go deeper. Build your own.
The pull request landed nine minutes after you pressed enter: 1,400 lines, green checks, a confident commit message — and the wrong feature. Not broken. Wrong. The agent resolved every ambiguity in your two-sentence prompt at machine speed, and now you get to spend ninety minutes reverse-engineering which sentence it misread.
Spec-driven development is the correction the industry converged on in 2025–26: make a written, testable specification — not the chat prompt — the thing agents implement against. This playbook covers the four-artifact stack that makes it work, one feature carried end to end through it (spec and acceptance tests shown in full), the tooling that grew up around the practice, the anti-patterns, and how to measure whether any of it is actually helping your team.
Why specs came back
“Vibe coding” — Andrej Karpathy’s early-2025 coinage for accepting generated code on feel, without reading it — was at least honest about what it was. For prototypes it remains exactly the right tool. Then agents got fast, cheap, and semi-autonomous, teams started merging their output into systems that bill customers, and the failure mode changed shape: not bad code, but confident code aimed at the wrong target.
The mechanism is worth naming precisely. Agents amplify ambiguity. A human junior who doesn’t understand the ticket asks a question; an agent commits to an interpretation and builds it, thoroughly, in minutes. Every underspecified requirement gets compiled into working software at machine speed. When generation cost collapses, the expensive artifact is no longer the implementation — it’s the intent. And intent was living in disposable chat prompts.
Review inherited the bill. With no written statement of what the code was supposed to do, every reviewer must reconstruct intent from the diff — the slowest possible way to acquire it. Multiply by parallel agents (three implementations means three interpretations) and review stops being a checkpoint and becomes the pipeline’s choke point. What is agentic coding has argued from the start that delegation only works with specs, tests, and real review; spec-driven development is that discipline promoted from good habit to named workflow.
What is spec-driven development?
Spec-driven development is a workflow where a written, testable specification — not a chat prompt — is the source of truth an AI coding agent implements against. Intent lives in versioned artifacts; acceptance tests make the spec executable; review checks the diff against the contract instead of reconstructing intent from code.
Two clarifications before anyone hears “waterfall.” First, scale: these specs are sized to tasks — a screen of markdown written in half an hour, not a quarter’s worth of documentation. Second, the consumer changed. Human implementers fill specification gaps with judgment and hallway questions; an agent fills them with the statistically most plausible guess. Specs came back because, for the first time, the implementer takes you perfectly literally.
The practical difference from prompting is lifecycle. A prompt is consumed once and scrolls away. A spec is versioned, reviewed before implementation starts, testable against, and still there next sprint when someone asks why the export behaves that way.
Treat it as a dial, not a switch. A one-line bug fix needs a reproduction and a regression test — that’s already a minimal spec. A day-sized feature earns the full per-task treatment below. An exploratory spike earns nothing, on purpose. The skill teams actually build is calibrating spec weight to task risk, and the failure modes at both extremes have names in the anti-patterns section.
The artifact stack: four layers, four failure classes
Mature spec-driven teams converge on four artifacts, each catching a different class of wrong. The layers are small; the point is that they exist separately, because they have different owners, lifespans, and jobs.
Four layers, four owners, four lifespans. Each catches a failure the layers above can’t.
| Layer | Answers | Owner | Lifespan | Typical size | Catches |
|---|---|---|---|---|---|
| PRD-lite | Why build this at all | Product owner | The feature | Half a page | Wrong problem |
| AGENTS.md / CLAUDE.md | House rules every task inherits | The team | Months | 20–60 lines | Wrong conventions |
| Per-task spec | Exact intended behavior | Ticket owner | Days | One screen | Wrong behavior |
| Acceptance tests | Proof, executable | CI | Life of the code | 5–10 tests | Wrong implementation |
PRD-lite is the why: problem, users, success measure, non-goals, half a page with a hard ceiling of one. Its job is to stop confidently-built solutions to problems nobody has.
AGENTS.md / CLAUDE.md is the constitution — build commands, conventions, boundaries — read automatically by the harness at the start of every session, so per-task specs never restate house rules. The AGENTS.md format emerged alongside OpenAI’s Codex and has spread into a cross-harness convention ; Claude Code layers the same idea through its CLAUDE.md memory hierarchy, and OpenCode reads AGENTS.md natively (OpenCode docs). Keeping this file short and current is a context engineering problem; which file your harness reads is covered in the agent harness field map.
The per-task spec is the working contract: context, exact behavior, interfaces, edge cases, explicit non-goals, and a done-means clause. It lives in the repo (a specs/ directory works fine), gets reviewed before implementation — five minutes of spec review replaces an hour of diff archaeology — and dies gracefully when the task ships, folded into docs or deleted.
Acceptance tests are the spec’s executable edge: the subset of intent precise enough to state as assertions. They’re the only layer the agent can’t argue with, they outlive the spec, and they’re the reason the whole stack has teeth. The reinvented test harness covers agent-era QA in depth; here they play a narrower role — the merge gate.
Worked example: one feature, spec to ship
Concrete beats abstract, so here is one feature carried through the whole loop: CSV export for a billing dashboard’s invoice list.
The PRD-lite (written by the PM, two minutes to read):
# PRD-lite: CSV export for the invoice list
Problem: finance pulls invoice data by copy-pasting the dashboard into
Sheets every month-end (~40 min, error-prone, no audit trail).
Users: finance ops (3 people), monthly close.
Success: October close uses the export; zero copy-paste incidents.
Non-goals: XLSX, scheduled exports, a general reporting product.
The per-task spec (written by the ticket owner in ~30 minutes, reviewed by one teammate in five):
# Spec: export filtered invoice list as CSV
## Context
Invoice list lives at /billing/invoices — server-rendered, paginated,
filterable by status and date range. House rules in AGENTS.md apply.
## Behavior
- "Export CSV" button on the invoice list, visible only to roles with
`billing.read`.
- GET /billing/invoices/export.csv accepts the same query params as the
list view and applies the same filters.
- Columns, in order: invoice_id, customer_name, issued_at (ISO 8601),
due_at, status, currency, amount_cents.
- Response streams; no full materialization in memory. Must handle
250k rows.
- UTF-8 with BOM. Header row always present, even for zero results.
- Cell values starting with = + - @ get prefixed with ' (CSV injection).
- Requests without `billing.read` return 403 with the standard error body.
## Non-goals
No new dependencies. No XLSX. No background jobs. No schema changes.
## Done means
- tests/billing/test_invoice_export.py passes.
- No changes outside billing/ and tests/.
The acceptance tests, written with the spec and committed before any implementation exists:
# tests/billing/test_invoice_export.py
def test_export_respects_active_filters(client, seeded_invoices):
rows = parse_csv(client.get("/billing/invoices/export.csv?status=overdue"))
assert {r["status"] for r in rows} == {"overdue"}
def test_column_order_is_stable(client, seeded_invoices):
header = csv_header(client.get("/billing/invoices/export.csv"))
assert header == ["invoice_id", "customer_name", "issued_at",
"due_at", "status", "currency", "amount_cents"]
def test_zero_rows_still_returns_header(client):
resp = client.get("/billing/invoices/export.csv?status=voided")
assert resp.status_code == 200 and csv_header(resp)
def test_formula_cells_are_escaped(client, make_invoice):
make_invoice(customer_name="=SUM(A1:A9)")
resp = client.get("/billing/invoices/export.csv")
assert "'=SUM(A1:A9)" in resp.text # CSV injection guard
def test_requires_billing_read(client_without_role):
assert client_without_role.get(
"/billing/invoices/export.csv").status_code == 403
def test_large_export_streams(client, make_invoices):
make_invoices(250_000)
resp = client.get("/billing/invoices/export.csv")
assert resp.headers["Transfer-Encoding"] == "chunked"
The run. With the contract fixed, implementation fans out safely: two parallel worktrees, Claude Code on one, Codex CLI on the other, both pointed at the same spec file. Forty minutes later, the CI gate runs the acceptance suite against both branches. The Codex CLI branch fails test_formula_cells_are_escaped — it built the obvious ",".join() writer, which is exactly the bug the spec existed to prevent. The Claude Code branch passes six of six, but review-against-spec catches something the tests can’t: an imported CSV helper library, which the non-goals forbid. That’s a thirty-second catch, because the reviewer is checking a diff against a contract, not divining intent.
One implementation gets trimmed and merged. The spec’s decisions move into docs/billing.md; the spec file is deleted; the tests stay forever.
The honest accounting: spec plus tests cost about 45 minutes. Review took 20 instead of the usual 90, the injection bug never existed on any branch that could merge, and the second implementation was nearly free. Writing test_formula_cells_are_escaped up front was the review — performed in advance, once, instead of per-diff.
One flow the example glosses over: what happens when the spec turns out to be wrong mid-task. It will — maybe the list view’s query params can’t express one of the filters, and an implementer discovers it at minute ten. The rule that keeps the system honest is that contradictions get resolved in the spec, not in chat. The agent (or the human) stops, the spec gets a one-line amendment with a reason, the tests update, and both branches continue against the corrected contract. Ten seconds of ceremony, and the contract never silently forks from reality.
The worked example as a loop. The gate does the mechanical review; humans do the judgment.
The tooling state, August 2026
You can run everything above with markdown files and a CI job — the method has no hard dependencies. The tooling that grew around it mostly reduces friction at specific layers.
Spec toolkits. GitHub’s open-source Spec Kit is the reference implementation of the workflow: /specify drafts the spec, /plan derives a technical plan, /tasks breaks it into implementable chunks, and the artifacts work across the major harnesses.
Spec-first IDEs. AWS’s Kiro made the spec the IDE’s primary object — requirements, design, and task files that stay synced with implementation — and defined a product class that others now imitate (kiro.dev).
Spec registries. Tessl-class platforms push furthest: the spec as the canonical, durable artifact and code as regenerable output (tessl.io). As of mid-2026 this remains the speculative end of the spectrum — philosophically clarifying, operationally early.
The layer you already have. Every major harness ships lightweight spec support: plan modes that draft an approach for approval before touching files, and the AGENTS.md/CLAUDE.md convention layer covered above. If your team isn’t ready for tooling, plan mode plus a specs/ directory is a complete starter kit.
Where you sit on this spectrum should track team size more than taste. A solo operator gets most of the value from plan mode, a specs/ directory, and acceptance tests in CI. Teams benefit from Spec Kit-style scaffolding because it standardizes the artifacts people hand each other. The registry end of the spectrum is worth watching, not yet worth betting a workflow on.
Adopt tools after the habit, not instead of it. A team that can’t keep a one-screen spec current will not be saved by a spec IDE.
Anti-patterns
Six ways spec-driven development goes wrong in practice, each with the corrective built in.
- Spec theater. Specs exist; nothing enforces them. No acceptance-test gate, no review-against-spec — the file is decoration and everyone learns to skip it. If the spec can’t block a merge, it isn’t a contract.
- Over-spec’ing exploration. Prototypes, spikes, and “what would this feel like” work are legitimately vibe-coded; a spec would slow the only thing that matters there, which is learning speed. Spec the second version — the one that ships.
- The self-graded exam. One agent session writes the spec, the tests, and the implementation. Ambiguity launders straight through: the tests encode the same misreading as the code, and green means nothing. Separate authorship — human-written or at minimum separately-reviewed spec and tests.
- Tests that mirror implementation. Acceptance tests asserting internals (function names, call counts, private state) weld the contract to one implementation and punish better ones. Assert observable behavior — status codes, output bytes, state changes.
- The lying spec. The code evolved; the spec didn’t; now the repo contains an authoritative-looking document that’s wrong, which is strictly worse than no document. Fold decisions into living docs at merge and delete the task spec — its job is done.
- PRD cosplay. The pendulum overswings into forty-page documents and sign-off meetings. Spec weight should track task risk, not org nostalgia. A screen of markdown for a day of work; anything more is process for its own sake.
Rolling it out: a four-week sequence
Adoption fails when it starts with mandates. Start where the pain is and let results argue.
Week 1 — two tickets, one author. Pick two well-understood tickets. One volunteer writes per-task specs and acceptance tests, runs their usual agents against them, and keeps rough notes on review time. No process announcements.
Week 2 — the gate. Wire acceptance suites into CI as a required check for spec’d tasks. This is the week the spec becomes a contract instead of a suggestion.
Week 3 — the constitution. Distill your AGENTS.md from the review comments the team keeps repeating; every recurring nitpick is a line that belongs in the house-rules layer, stated once, inherited by every future task.
Week 4 — measure and decide. Compare the spec’d tickets against the team’s baseline on the metrics below, run a short retro, and decide honestly: expand, adjust, or stop. Teams running several assistants in parallel usually see the effect first and loudest — fan-out without a contract is where multi-agent work gets chaotic fastest.
Skip the PRD-lite layer until the per-task habit sticks. It’s the easiest layer to theater.
Measuring whether spec-driven development helps
Specs are an intervention, and interventions get measured — the same eval discipline you’d apply to a model swap applies to a workflow swap. Take a two-week baseline before the rollout, then track:
| Metric | How to compute | Specs are working if |
|---|---|---|
| First-pass acceptance | PRs merged without a change-request round ÷ all agent PRs | It climbs within a month |
| Rework rate | Follow-up commits landing within 7 days of merge, per PR | It falls |
| Review time | Open-to-approval active review minutes (sampled is fine) | It falls, especially on large diffs |
| Revert rate | Merged agent PRs reverted or hotfixed | It falls or holds at ~zero |
| Spec-drift catches | Review findings of the form “violates spec/non-goal” | They appear — proof the contract is load-bearing |
Two cautions on reading the numbers. Small teams generate noisy weeks — judge on a month of merged PRs, not a sprint, and compare like tasks with like (a migration-heavy fortnight will wreck any metric regardless of process). And expect an honest null on exploratory work — that’s the over-spec’ing anti-pattern confirming itself, not a failure of measurement. The numbers say whether specs help; they never say why one failed in a given case. For the why, you need the trace: the metrics flag the PR, but the session transcript shows the turn where the agent decided a forbidden dependency was a good idea.
Product note: The spec says what should have happened; the transcript says what actually did. Automater Lite archives sessions from 10+ CLIs — Claude Code, Codex, OpenCode, Copilot, and friends — into one local, full-text-searchable library, so when a merged PR drifts from its spec you can search the session, find the exact turn the agent went sideways, and fix the spec line instead of just the code. Local-first and free, on automater.ai.
Close the loop the same way you’d tune any system: every spec-drift catch is either a missing spec line, a missing acceptance test, or a missing AGENTS.md rule. File it in the right layer and that class of drift is done recurring.
Specs, instruction files, acceptance suites — these are the artifacts that survive tool churn, which makes spec-driven development less a methodology than an asset strategy. Your harness will change; the harness engineering layer you built around it moves with you. The contract outlives the contractor.
FAQ: spec-driven development
What is spec-driven development?
Spec-driven development is a workflow where a written, testable specification — not a chat prompt — is the source of truth an AI coding agent implements against. Intent lives in versioned artifacts, acceptance tests make the spec executable, and review checks the diff against the contract rather than guessing at intent.
Is spec-driven development the opposite of vibe coding?
Functionally, yes. Vibe coding accepts generated code on feel without reading it; spec-driven development pins the agent to a written contract with executable acceptance criteria. Both use the same agents. Vibe coding is fine for throwaway prototypes — the spec discipline exists for code that must survive review and production.
What is AGENTS.md and how is it different from a spec?
AGENTS.md is a repo-level instruction file — house rules like build commands, conventions, and boundaries — that harnesses read automatically every session. A spec describes one task’s intended behavior and dies when the task ships. AGENTS.md is the constitution; the per-task spec is the contract.
How detailed should a spec for an AI coding agent be?
Detailed enough that two independent implementations would converge on the same observable behavior: exact interfaces, edge cases, error handling, and explicit non-goals, usually one screen of markdown. If writing it takes longer than a careful review of the wrong implementation would have, you’re over-spec’ing the task.
Do acceptance tests replace code review?
No. Acceptance tests verify the behavior the spec anticipated; review catches what it didn’t — security issues, forbidden dependencies, architectural drift, and spec violations that don’t break tests. Specs change review’s job from reconstructing intent to checking a diff against a contract, which is faster and far more reliable.
Sources
- GitHub — Spec-driven development with AI: get started with a new open source toolkit (github.blog)
- AGENTS.md — the open instruction-file format for coding agents
- OpenAI — Introducing Codex
- Claude Code memory documentation (docs.claude.com)
- OpenCode documentation
- Kiro — spec-first agentic IDE
- Tessl — spec-centric development platform
