Spec-Driven Development with Agents
Open in the interactive app: practice, quiz, mock interview →In one minute
- With coding agents, the spec becomes the main artifact the agent works from. A good spec states what must be true, not how to type it. Treating the spec as the only source and regenerating code from it is the least mature end of the practice.
- The loop is: specify, plan, break into tasks, implement, verify. Humans own the first two and the last one; agents do most of the middle.
- Write requirements as numbered, testable statements (EARS-style "when X, the system shall Y", or Given/When/Then), each with an ID that tests and code can point back to.
- Traceability keeps the agent honest: every requirement has at least one test, every test names a requirement, and the agent's work is done when the mapped tests pass.
- Specs are a living record. When behaviour changes, change the spec first, then let code follow; otherwise the spec rots into fiction within weeks.
How it works
Why prompts are not enough
A prompt is a conversation; a spec is a contract. Prompts drift, get lost when the session ends, and mix what you want with how you said it. When five agents and three humans touch a feature over a month, the only thing that keeps them consistent is a document that says what the system must do, which everyone, human or agent, reads first. That is why tools such as GitHub's Spec Kit, OpenSpec and AWS Kiro all center on a spec file that lives in the repository next to the code. Spec Kit's phases are specify, plan, tasks, implement (GitHub blog, 2025); Kiro's feature specs produce requirements.md, design.md and tasks.md (Kiro docs).
The tools differ in how long the spec lives. Martin Fowler's comparison names three levels: spec-first (written before the code, then possibly discarded), spec-anchored (kept and evolved with the feature) and spec-as-source (humans edit only the spec). Most tools today are spec-first; spec-as-source showed non-deterministic output between runs, and he warns it could inherit both the rigidity of model-driven development and the randomness of LLMs (Fowler).
The loop
- Specify. Write the problem, users, requirements and non-goals. Requirements are numbered and testable.
- Plan. Decide the design: components, data model, problem shapes and mechanisms. For high-stakes work this is the whiteboard step.
- Tasks. Break the plan into small tasks, each naming the requirement IDs it satisfies and the tests that prove it.
- Implement. The agent works task by task, writing tests first where possible.
- Verify. Tests run, traceability is checked, a human reviews the diff against the spec.
Writing testable requirements
Vague requirements produce plausible code. "The system should be fast and handle errors well" gives an agent nothing to verify. Two formats help:
- EARS (Easy Approach to Requirements Syntax) came from Rolls-Royce engineers analysing jet-engine control requirements and was published in 2009. It has five templates: always-true, "While" for states, "When" for events, "Where" for optional features, and "If ... then" for unwanted behaviour (Mavin). Examples: "When
<trigger>, the<system>shall<response>" and "While<state>, the<system>shall<response>". For example: "REQ-3: When a refund request repeats an idempotency key with the same amount, the refund service shall return the original result without issuing a second refund." - Given/When/Then describes a scenario: given a starting state, when an action happens, then an outcome follows. It maps directly onto a test.
Use RFC 2119 words consistently: MUST for hard rules, SHOULD for strong defaults (valid reasons to deviate must be understood and weighed), MAY for options (RFC 2119). Only the capitalised forms carry that meaning (RFC 8174), so tell agents and authors to capitalise on purpose. Write numbers, not adjectives: "p99 under 200 ms at 500 requests per second", not "fast".
Non-goals and invariants
The most useful lines in a spec are often what the system must not do and what must always hold. "Non-goal: multi-currency in v1" stops an eager agent from building it. "Invariant: an account balance never goes negative" becomes a property test that runs on every change. Agents expand scope by default, because adding features looks helpful; non-goals are how you stop that.
Keeping specs alive
A spec written once and never updated becomes misleading, which is worse than no spec. Treat changes like code: propose a spec change, review it, apply it, then change the code. OpenSpec models exactly this cycle: a change folder is proposed, applied, then archived and merged into the live specs/ folder (OpenSpec). The rule for agents is simple: if the code and the spec disagree, stop and ask which one is right.
Down the stack
Layer 1The contract
A spec-driven workflow promises that every merged behaviour traces to a requirement, every requirement has at least one passing test, and a change in behaviour starts as a change in the spec. Reviewers can check it mechanically: a script lists requirements without tests and tests without requirements, and CI fails if either list is non-empty.
Layer 2The mechanism
In practice the spec is Markdown in the repository, for example specs/refunds/spec.md with requirements tagged REQ-1 to REQ-n, plus plan.md and tasks.md. Tests reference IDs in their names or with markers, such as test_req3_repeat_key_returns_original or a pytest marker @pytest.mark.req("REQ-3"). A traceability script greps the spec for requirement IDs and the test suite for references, then reports gaps. Agent instructions in CLAUDE.md or AGENTS.md say: read the spec first, implement one task at a time, write the test named after the requirement before the code, and never edit the spec without asking.
Layer 3The machine
Specs help agents for a concrete reason: an agent's context window is finite and every token competes for attention (Anthropic). A 2,000-token spec with numbered requirements gives the model a dense, stable description of the goal that survives across sessions, while a long chat history is lossy: earlier turns get summarised or dropped as the context fills. Tests then provide the ground truth that text cannot: a test run is a process with an exit code, 0 or not, which the agent's runtime reads directly. The spec narrows what the model generates; the tests check what it generated.
Why it's built this way
- Agents generate code quickly but have no stable memory of intent across sessions.
- Intent must therefore live in a durable document both humans and agents read.
- Documents drift from reality unless each claim is checked.
- Tests check claims, but only if each requirement is written precisely enough to test.
- So: numbered, testable requirements, linked to tests, enforced in CI.
Diagram
flowchart LR
S["Spec: REQ-1..n, non-goals, invariants"] --> P["Plan: design and shapes"]
P --> T["Tasks: each lists REQ ids"]
T --> I["Agent implements, tests first"]
I --> V{"Traceability and tests pass?"}
V -->|no| I
V -->|yes| R["Human review against spec"]
R -->|behaviour change| SWorked example
A team adds refunds to a support agent. The spec has six requirements. REQ-1: a refund MUST NOT exceed the captured amount of the order. REQ-2: refunds over 200 dollars MUST require human approval. REQ-3: repeating an idempotency key with the same parameters MUST return the original result. REQ-4: repeating a key with different parameters MUST be rejected. REQ-5: every refund MUST write an audit record with actor, amount and reason. REQ-6: p99 latency SHOULD be under 2 seconds excluding the payment provider. Non-goals: partial refunds of bundled items; refunds in a different currency.
The agent produces 14 tests and the implementation in about 40 minutes. The traceability script reports REQ-5 has no test: the agent wrote the audit record but never tested it. It also reports a test named test_refund_currency_conversion, which maps to no requirement: the agent started building a non-goal. Both are caught by a 30-line script before a human reads a single line of code. The reviewer then spends their time on the part machines cannot judge: whether 200 dollars is the right approval threshold.
In the wild
- Knight Capital, 2012: an undocumented flag meaning. New code reused a flag that once activated the retired "Power Peg" function, and that old code was still callable on the one server that missed the deployment. Knight lost over $460 million in about 45 minutes. The SEC found Knight had no written procedures requiring a second technician to review deployments (SEC order). A written, tested statement of what the flag may mean is the kind of requirement this lesson argues for.
- Air Canada, 2024: two sources of truth. The airline's website said bereavement fares could not be applied retroactively, while its chatbot said they could. The tribunal held the airline responsible for both and ordered it to pay the fare difference of CA$812 plus costs and interest (Wikipedia summary). One owned, versioned policy that the chatbot is tested against would have exposed the conflict.
Trade-offs
| Approach | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Full SDD (spec, plan, tasks) | Durable intent, traceable, reviewable | Process overhead | Features that last, teams of several people or agents |
| Light spec plus tests | Quick, still testable | Less design context | Small features, clear scope |
| Prompt only | Fastest start | Drift, scope creep, no record | Throwaway prototypes |
| Spec as source, code regenerated | Code stays aligned with intent | Immature tooling, nondeterministic output | Experiments, generated adapters |
Pitfalls in production
- Requirements written as adjectives ("robust", "fast"). Fix: numbers and observable outcomes only.
- Specs that never change after the first merge. Fix: spec change first, reviewed like code, for every behaviour change.
- Agents silently editing the spec to match their code. Fix: protect spec files with code owners and tell agents to propose spec changes, not make them.
- Too much process for small changes. Fix: scale the spec to the zone; automate-zone tasks need a sentence, not a document. Fowler reports a tool turning a small bug into four user stories with 16 acceptance criteria (Fowler).
- Believing the spec controls the agent. Fix: agents still ignore instructions or regenerate existing classes as duplicates (same Fowler article), so keep tests and the traceability check as the enforcement.
- Tests that mention a requirement but do not test it. Fix: review a sample of tests against their requirements, and use property tests for invariants.
Leadership lens
- Agree a spec template with numbered requirements, non-goals and invariants, and put it in the repository.
- Add the traceability check to CI; it is cheap and catches scope creep and missing tests automatically.
- Review specs more carefully than code. A wrong spec produces correct-looking wrong code at agent speed.
- Keep humans as the owners of specs and plans; agents may propose changes, but a person approves them.
Check yourself
Why is "the system shall handle errors gracefully" a bad requirement for an agent?
Show answer
It is not testable, so the agent cannot verify it and will interpret it however is most convenient; a good requirement names a trigger and an observable response.
What do non-goals add to a spec when an agent implements it?
Show answer
They stop scope creep: agents tend to add plausible features, and an explicit non-goal lets both the agent and the traceability check flag work that was not asked for.
A test references no requirement. What could that mean?
Show answer
Either a missing requirement that should be added to the spec, or work outside the spec, such as a non-goal the agent built; both need a human decision.
Why should behaviour changes start in the spec rather than the code?
Show answer
Otherwise the spec drifts from reality, and the next agent or engineer who reads it builds on false information.
Sources and further study
Primary
- RFC 8174: Ambiguity of uppercase vs lowercase in RFC 2119 key words: why capitalisation matters in a spec.
- Mavin, EARS: Easy Approach to Requirements Syntax: origin and the five templates.
- SEC order against Knight Capital Americas (2013): what missing written procedures cost.
Systems docs
- OpenSpec: a propose, apply, archive workflow with live specs and in-flight changes.
- Claude Code best practices: let Claude interview you: write a self-contained spec, then execute in a fresh session.
Books
- Software Engineering at Google (2020), Ch 11: Testing Overview (read free): why tests are the executable form of expectations.
- Building Secure and Reliable Systems (2020), Ch 4: Design Tradeoffs, on turning design objectives and requirements into decisions.
Free resources
- Spec-driven development with AI (GitHub blog): introduces Spec Kit's specify, plan, tasks loop.
- Understanding spec-driven development: Kiro, spec-kit and Tessl (Martin Fowler's site): a critical comparison of three SDD tools.
- Kiro: specs: requirements, design and task files in practice.
- RFC 2119: what MUST, SHOULD and MAY mean.
- Given When Then (Martin Fowler): scenarios that map directly to tests.
- Hypothesis: property-based testing for invariants in Python.