Spec-driven development means agreeing in writing what a piece of software should do before the code is written, then building from that spec. With a coding agent, the spec has to be something the agent can check its own work against. A spec the agent can run tells it when the work is finished. If it can only read the spec, that judgement is left to the agent.

This article uses a form older than coding agents. Acceptance criteria are the conditions a piece of work has to meet before anyone calls it finished. Here they’re written as Given, When and Then scenarios for a tool such as Cucumber to run. The sections below compare spec-driven development with test-driven and behaviour-driven development. Then they follow it through a framework migration and one small feature, from acceptance criteria to scenarios to code.

What spec-driven development means for AI agents

As a name, spec-driven development is older than the agents too. Wikipedia’s Specification-driven development article (2026) doesn’t say who coined it. Its oldest reference is a 2004 paper, “Agile Specification-Driven Development” by Jonathan Ostroff, David Makalsky and Richard Paige. That paper, the article says, combined test-driven development with design by contract. Its own lead now describes the approach as following the steps of behaviour-driven development, with different artefacts that AI agents help to create.

Birgitta Böckeler’s Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl (martinfowler.com, 2025) starts from a short definition. “Spec-driven development means writing a ‘spec’ before writing code with AI (‘documentation first’).” She separates three levels of it:

  • Spec-first, where the spec is written for the task in hand and used while the agent works on it.
  • Spec-anchored, where the spec is kept after the task and used when the feature changes later.
  • Spec-as-source, where people edit only the spec and never touch the code.

Every level depends on the agent doing what the spec says. AI4 Verifiable Specs puts the condition in its tagline: “If the agent can’t grade itself, it isn’t a spec.” A requirements document still has to be interpreted, however well it’s written. The scenarios further down are one way to give the agent something it can run.

Spec-driven vs test-driven vs behaviour-driven development

Test-driven, behaviour-driven and spec-driven development differ in what’s written first and in what checks the code once it exists.

Practice Written first What checks the code Where it comes from
Test-driven development (TDD) A failing unit test Unit tests, run after every change Kent Beck, credited by Wikipedia with having developed or “rediscovered” it
Behaviour-driven development (BDD) Scenarios written as Given, When and Then steps Scenarios, run by a tool such as Cucumber Dan North’s Introducing BDD, 2006
Spec-driven development (SDD) A spec, often with a design and a task list Whatever the spec includes, which may be nothing a machine runs A 2004 paper, then AI coding tools such as Kiro and Spec Kit

North explains the change of word in his article: “people’s misunderstandings about TDD almost always came back to the word ‘test’.” His scenario template, given some initial context, when an event occurs, then ensure some outcomes, gives its shape to Gherkin, the plain-text format Cucumber reads.

These three nest inside each other, with a tenet in the canon at each size. P2 Spec-First Execution covers a piece of work: “AC at the start, AC at the end”, where AC means acceptance criteria. TS2 Specification by Example covers the scenarios that carry those criteria, in “Domain-language Gherkin.” P1 Test-First Development covers each unit underneath: “The test is the brief. Write it first.” An agent needs every line of the spec to have a check behind it, and with all three in place, every line does.

A migration with the tests as the spec

In July 2026 Prickles moved from Next.js to Astro, with the site’s end-to-end suite as the acceptance bar. The suite is written in Gherkin. Its feature files describe what a visitor does and sees, and Playwright runs most scenarios twice, once in desktop Chrome and once at a phone’s size.

For the move, the suite grew from 182 tests to 354. Four new feature files covered the home page, the tenet pages, the implementation page and search. A fifth covered feedback, the editorial pages and SEO. None of the existing feature files changed. The commit that completed the move records all 354 passing, alongside 1,460 unit tests.

Throughout, the work followed a rule set down for Claude Code in the repository:

The e2e suite is the acceptance bar and it is frozen: the site moves to the tests, never the tests to the site. A change that fails a scenario is wrong until proven otherwise.

— Prickles, .claude/rules/astro.md

Claude Code did the porting, with the suite as the bar it had to clear. The scenarios could judge a new framework because none of them mentioned the old one. A search scenario reads When I type "simplicity" into the header search, and its step definition finds the search box and types into it. Nothing in the feature files names Next.js or a CSS selector, so they described the Astro site as well as they had described the Next.js one.

Since then the suite has grown to 450 tests, counted on the main branch on 12 October 2026. The same rule still applies to every change.

A spec-driven development example

A single feature goes through the same loop. This example takes a meeting room that mustn’t be double-booked from acceptance criteria to scenarios, step definitions and code. Before it reports the work as done, the agent runs the scenarios.

Acceptance criteria agreed with people, in words Scenarios room-booking.feature Step definitions turn each step into a call Code room-diary.ts Agent runs the scenarios npx cucumber-js a step fails: fix the code every step passes Done with the run as evidence
People agree the criteria and the scenarios. The agent writes the step definitions and the code, and runs the scenarios until every step passes.

Start from the acceptance criteria

A first draft has one criterion:

  • A booking that overlaps an existing booking is refused with a reason that names the booking it clashes with.

It reads as complete until someone tries an example. If the room is booked from 10:00 to 11:00, can the next booking start at 11:00? The sentence doesn’t say, so an agent has to choose, and nothing makes it tell you what it chose. Writing examples brings that question up before the code does, as TS2 argues. After the conversation there are two criteria:

  • A booking that overlaps an existing booking is refused with a reason that names the booking it clashes with.
  • A booking can start at the minute another one ends.

Write the scenarios

Each criterion becomes at least one scenario, written in the words of someone booking the room:

Feature: Booking the meeting room

  Scenario: A booking that overlaps another is refused
    Given the room is booked from 10:00 to 11:00
    When I book the room from 10:30 to 11:30
    Then my booking is refused because it clashes with the 10:00 to 11:00 booking

  Scenario: A booking can start as another ends
    Given the room is booked from 10:00 to 11:00
    When I book the room from 11:00 to 12:00
    Then my booking is accepted

Times are 24-hour, with two digits for the hour, so 09:30 sorts before 10:00 as plain text. That keeps the comparison in the code to a single line.

Write the step definitions

A step definition pairs a pattern with the code to run when a step’s text matches it. That’s how the room is booked from 10:00 to 11:00 becomes a call, so the scenario can stay in the room’s own words. The samples use cucumber-js for TypeScript and JavaScript, behave for Python, Cucumber-JVM for Java and Behat for PHP. In cucumber-js, the steps share state through a World, the object this points to, and its World docs say each scenario gets a new one.

Viewing: TypeScript.
import assert from "node:assert/strict";
import { Given, Then, When, World, setWorldConstructor } from "@cucumber/cucumber";
import { findClash, type Booking } from "../../src/room-diary";

class RoomWorld extends World {
  diary: readonly Booking[] = [];
  clash: Booking | undefined;
}

setWorldConstructor(RoomWorld);

Given(
  "the room is booked from {word} to {word}",
  function (this: RoomWorld, start: string, end: string): void {
    this.diary = [{ start, end }];
  }
);

When(
  "I book the room from {word} to {word}",
  function (this: RoomWorld, start: string, end: string): void {
    this.clash = findClash(this.diary, { start, end });
  }
);

Then(
  "my booking is refused because it clashes with the {word} to {word} booking",
  function (this: RoomWorld, start: string, end: string): void {
    assert.deepEqual(this.clash, { start, end });
  }
);

Then("my booking is accepted", function (this: RoomWorld): void {
  assert.equal(this.clash, undefined);
});

Behat’s :name placeholders match letters, digits, underscores, full stops and commas, but not a colon, as its pattern source shows. So the PHP version captures times such as 10:00 with regular expressions. Behat accepts those in the same attributes.

Write the code and run the scenarios

Here the code does no more than make both scenarios pass. Two bookings overlap when each one starts before the other ends.

Viewing: TypeScript.
export type Booking = Readonly<{ start: string; end: string }>;

const areOverlapping = (first: Booking, second: Booking): boolean =>
  first.start < second.end && second.start < first.end;

export const findClash = (diary: readonly Booking[], wanted: Booking): Booking | undefined =>
  diary.find((booking) => areOverlapping(booking, wanted));

Suppose the agent writes <= in place of <, so bookings that only touch count as overlapping. The first scenario still passes, and to a reader the code still looks reasonable. The second scenario fails, with the reason in the run’s output:

1) Scenario: A booking can start as another ends
   √ Given the room is booked from 10:00 to 11:00
   √ When I book the room from 11:00 to 12:00
   × Then my booking is accepted
       AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
       + actual - expected

       + {
       +   end: '11:00',
       +   start: '10:00'
       + }
       - undefined

2 scenarios (1 failed, 1 passed)
6 steps (1 failed, 5 passed)

With < back in place, the run ends with 2 scenarios (2 passed) and 6 steps (6 passed). Pasted into the agent’s report, those two lines are its evidence that the work is done. The output above comes from running the TypeScript and JavaScript samples with cucumber-js 12.9 on 12 October 2026.

Spec-driven development with Claude Code

Anthropic’s Best practices for Claude Code (2026), read on 12 October 2026, opens its advice with verification.

Claude stops when the work looks done.

— Anthropic, Best practices for Claude Code, 2026

Without a check to run, the page goes on, Claude has only “looks done” to judge by. The page lists four ways to make a check decide when the work ends:

  • In one prompt, by asking Claude to run the check and keep going until it passes.
  • Across a session, by setting the check as a /goal condition for a separate evaluator to re-check after every turn.
  • As a fixed gate, with a Stop hook that runs the check as a script and won’t let the turn end until it passes.
  • With a second opinion, from a verification subagent that tries to refute the result, so the agent that made the change isn’t the one grading it.

For larger features, the same page suggests having Claude interview you and write a SPEC.md. It says the most useful specs “end with an end-to-end verification step that proves the feature works”, and that Claude should “show evidence rather than asserting success”. Its workflow of explore, plan, implement and commit uses plan mode to keep reading the code apart from changing it. That’s the order AI1 The Intern Pattern sets: plan, approve, execute, review.

For the meeting room, the setup is a few lines in CLAUDE.md, or in AGENTS.md if other agents work in the repository too. CLAUDE.md vs AGENTS.md covers which agent reads which.

## Acceptance criteria

- Every acceptance criterion has a scenario in `features/`.
- Run `npx cucumber-js` before you say a change is done, and paste the summary lines.
- Never edit a `.feature` file to make a scenario pass. Stop and ask.

That last line applies the migration’s rule to a single feature. Prickles hasn’t tested a Stop hook or /goal for this, so they’re described here only as Anthropic documents them. Agentic coding covers how an agent works through its loop of reading, running and editing.

Spec-driven tools such as Kiro and Spec Kit

Several tools now write the spec with you. Prickles hasn’t tested them, so this section describes what their own pages said on 12 October 2026.

Tool What the spec is What the agent can run
Kiro requirements.md or bugfix.md, design.md and tasks.md Property-based tests, optional and in the IDE only
Spec Kit A spec, a plan and tasks, written through agent skills Tests, when you ask for them
Gherkin with Cucumber .feature files Every scenario, as written

Kiro’s Specs page says every spec generates those three files. Its Feature Specs page writes requirements in EARS, the Easy Approach to Requirements Syntax, as WHEN [condition/event] THE SYSTEM SHALL [expected behavior]. Kiro can turn those requirements into property-based tests. A property-based test tries many random inputs against a stated rule. Its Correctness page is plain about the limits: “PBTs are optional by default so you can focus on your core implementation first”, and “It is not formal verification”.

GitHub’s Spec Kit gives a coding agent a set of skills. A constitution of project principles comes first, then specify, plan, tasks, implement and converge for each feature. Its Agentic SDD reference says converge “Assesses the codebase against the feature’s spec, plan, and tasks to confirm nothing was missed.” That’s an agent reading its work against Markdown. Tests “are generated within a user story’s phase when requested”.

For whichever tool writes the spec, AI4 asks whether the agent can run that spec once it’s written. Where it can’t, the spec needs scenarios or tests beside it.

Where spec-driven development goes wrong

Specs the agent can’t check

“The room should be easy to book” is a fair thing to want and impossible to run. Writing about the tools’ own specs, Böckeler reports that she “frequently saw the agent ultimately not follow all the instructions.” When a line of the spec has no check, nothing shows that it was skipped. Turn it into something measurable, such as a number of steps, or move it to the review, where a person reads the change.

Scenarios that test the screen

This scenario tests the page:

Scenario: Double booking
  Given I am on "/rooms/garden"
  When I fill in "#start" with "10:30"
  And I press "#book-button"
  Then I should see ".toast--error"

It passes whenever an error message appears, for any reason, and fails the day someone renames the button. It never says which booking clashed, though the criterion asks for that. Cucumber’s Writing better Gherkin states the rule: “Your scenarios should describe the intended behaviour of the system, not the implementation.” Step definitions can still drive a browser, as the ones in the Prickles suite do. The scenario’s words stay with what the person booking wants. TS6 Behaviour Testing draws that line for every test.

Specs that change to fit the code

An agent that can’t make a scenario pass can edit the scenario. The diff looks like progress, and every check turns green. That’s why the CLAUDE.md lines above forbid it, and why the migration’s rule is written down. A change to a .feature file is a change to the spec, so a person approves it.

More spec than the change needs

When Böckeler gave Kiro a small bug, its requirements document turned it into “4 ‘user stories’ with a total of 16 acceptance criteria”. She found the workflow “like using a sledgehammer to crack a nut.” Anthropic’s best-practices page draws a similar line for planning: “If you could describe the diff in one sentence, skip the plan.” A one-line fix needs one scenario, or an existing one that now fails.

How to start spec-driven development with an agent

  1. Write the acceptance criteria before the prompt, in the ticket or a SPEC.md, as lines someone can mark met or unmet.
  2. Write a scenario for each criterion in the words of the people who use the feature, and add one for each edge case the examples turn up.
  3. Run the scenarios and watch them fail, so you know they can.
  4. Ask the agent for a plan that names the scenarios and the command that runs them, and approve it before any file changes.
  5. Have the agent keep working until the command passes, and ask for the run’s output as evidence.
  6. Review the diff, and treat any change to a .feature file as a change to the spec.

For a bug, step 2 is one scenario that reproduces it, and step 3 shows it failing for the reason in the bug report.