Working from a spec in small increments
In this lesson we add a feature to a small program with a coding agent, and the agent works from a written specification instead of from a chat message. The spec comes first: one page with the commands, the limits and a numbered list of success criteria. Then the agent splits the work into increments that each leave the program working. The increment we’re least sure about goes first, and we land it. While the agent works, the spec stays the document it reads and the one we check the result against. When code and spec disagree, we decide which one changes.
Where a bug fix has one assertion that says what “fixed” means, a feature has decisions in it: a new command, a new field in the data file, a new line in the output. The spec holds those decisions in one place, where the agent can read them and you can check against them. The playbook Anthropic publishes for engineering teams describes the same order of work, the intent and the requirements written down before the build starts [1].
The fixture
Section titled “The fixture”The fixture is the to-do program from the previous lessons, after its
clear bug was fixed. It has three modules, todo.py with the commands,
store.py for the JSON file, and render.py for the output, and its
tests pass. It is in the course repository under
site/examples/coding-with-agents/spec-driven-increments/fixture-repo/.
Copy that directory to a place of its own and change into it. A copy has
no git history, so the reset is to delete the copy and copy again.
Run the program once before you change anything, so you know what the output looks like today.
Show the list
Section titled “Show the list”Run this, and compare what you see with the output below.
python3 todo.py list1. [ ] Buy milk 2. [x] Call the plumber 3. [x] Renew the passport 4. [ ] Water the plants 2 open, 2 done
Output verified in CI from site/examples/coding-with-agents/spec-driven-increments/list.py.
Run the tests
Section titled “Run the tests”Run the tests and keep only the last line of the output.
python3 -m unittest -q 2>&1 | tail -1OK
Output verified in CI from site/examples/coding-with-agents/spec-driven-increments/tests.py.
The examples in this lesson aren’t graded. Each one is a command you run in the fixture, with the output the course itself checks on every build. The checkpoints ask about the spec and the increments, because writing those is what this lesson teaches.
Write the spec
Section titled “Write the spec”The feature is due dates. An item gets an optional date, list shows it,
and a new overdue command lists the items whose date has passed. In
Deciding what to build you wrote success criteria for a smaller version
of this feature, with the date given on add and “any notion of overdue”
out of scope. This spec takes that feature up again and changes two
decisions on purpose. The date is set by its own due command, so an
existing item can get one. overdue is back in, because it is the reason
to store a date at all. Changing the decision in the spec, before
any code, is what the spec is for. Before the agent sees any of that,
write it down. A spec for a change this size fits
on one page: a goal in one or two sentences, the commands with what each
one prints, the limits, and success criteria you can check one by one.
The limits say what must keep working and what the change may not do. Save it as
SPEC.md in the fixture. The course keeps its copy next to the fixture
scripts as SPEC.md, and this is the whole of it.
# Due dates
## Goal
An item can carry a due date, and a new command lists the items whosedate has passed.
## Commands
- `due N YYYY-MM-DD` sets the date on item N and prints `due #N: <text> by YYYY-MM-DD`.- `list` shows the date after the text: `1. [ ] Buy milk (due 2026-10-01)`. An item without a date is shown as it is today.- `overdue` prints the open items whose date is before today, numbered as in `list`, or `nothing overdue`. `TODO_TODAY=YYYY-MM-DD` overrides today, so a test can pick the day.
## Limits
- `add`, `list`, `done` and `clear` keep working as they do.- An item in `todos.json` without a `due` field stays valid. No migration.- A malformed date is refused with `bad date: <input>` and changes nothing.- Standard library only.
## Success criteria
1. `due 1 2026-10-01` then `list` shows `1. [ ] Buy milk (due 2026-10-01)`, and the other lines are unchanged.2. The committed `todos.json` lists as before, with no date shown.3. With `TODO_TODAY=2026-10-02`, `overdue` prints item 1 and nothing else. With `TODO_TODAY=2026-09-30` it prints `nothing overdue`.4. `due 1 tomorrow` prints `bad date: tomorrow`, exits with status 2, and the file is unchanged.5. `python3 -m unittest -q` passes after every increment.Every line under success criteria is something you can run and tick. The
line about TODO_TODAY is there because “before today” depends on the
day you run it, and a criterion you can only check on one date isn’t a
criterion. Writing the spec made that decision visible before any code
existed.
The spec is the source of truth for the change while the agent works. The agent reads it and you review against it, and the agent’s opinion of what the feature should do doesn’t change it by itself. Code and spec disagree at some point, because the agent has a good idea, or because the spec turns out to be wrong. When that happens, one of the two is updated on purpose. Either you change the spec first and then the code follows, or the code is brought back to the spec. What never happens is that the code drifts and the spec stays as a record of what you once meant.
The diff and the spec disagree
Section titled “The diff and the spec disagree”A learner has written a one-page spec with success criteria for a feature, and a coding agent is implementing it. The spec is meant to stay the document the agent works from and the reviewer checks against.
The agent’s diff prints the date before the text, and the spec says after. Both would work. What do you do?
Which document does the reviewer check the next increment against, and does it still say what you want?
Ask for increments in the brief
Section titled “Ask for increments in the brief”An agent given the whole spec at once builds the whole spec at once, and hands you one diff with two new commands, a changed output line, input validation and their tests. Reviewing that diff means holding every one of those changes in your head while you read. So the brief asks for increments, and it says what an increment is: a step that leaves the program working and its tests passing, and that adds one thing you can show. Asking for increments is part of the brief, because the agent’s default is the big diff [2].
Start your coding agent from inside the fixture copy and brief it. The brief points at the spec instead of repeating it.
Read SPEC.md, AGENTS.md and the three modules. I want to land the
due-dates feature in increments. Each increment must leave every existing
command working and the tests passing, and add one thing I can run and
see. Propose the increments and say which success criteria each one
ticks. Don’t change anything yet.
I read the spec and the code. The spec already lists three increments, and I would keep them, with one note on each.
1. `due` sets the field and `list` shows it. Touches todo.py and render.py, adds test_due.py. Ticks criteria 1, 2 and 5. store.py needs no change, because json.dump writes whatever keys an item has.2. `overdue`, reading today from TODO_TODAY or the clock. Touches todo.py, adds a test with the two dates from criterion 3. Ticks criterion 3.3. Refuse a malformed date with the standard library's date parser. Touches the `due` function, adds a test. Ticks criterion 4.One question before I start. In list, would you like items with a date
sorted to the top? The spec doesn’t say.
The transcripts in this lesson are illustrative. They show what a good exchange looks like and don’t record a specific model’s words. Your agent’s list may put the details differently.
The agent’s question is a spec question, and the spec answers it: the
overdue line says “numbered as in list”, the list line says nothing
about sorting, and the limits say list keeps working as it does. The
answer is no, and if you did want sorting, you would add it to the spec
before anyone wrote it.
Which of these are working increments?
Section titled “Which of these are working increments?”A learner is asking a coding agent to land a feature as increments, where each increment leaves the program working and tested and adds one thing that can be shown.
Which of these steps are increments in the sense the brief asked for?
After each step, can you run the program and the tests, and see one new thing?
The increment you are least sure of goes first
Section titled “The increment you are least sure of goes first”The spec lists three increments, and the order matters. Put first the one
whose assumption you are least sure of, so that if it is wrong you find
out while the change is small. Here the uncertain part is the data: every
module today assumes an item has exactly a text and a done flag, and the
first increment adds a field that some items have and some don’t, and
carries it through all three modules and the existing tests. If that
assumption fails, the data format and every module change, and you would
rather know before overdue is written on top of it. The overdue
command is arithmetic on a format that already works by then. Date
validation waits until after it, even though a bad date could be stored
meanwhile. The committed list has no dates and the test from increment 1
pins the format, so the only bad date is one you type yourself before
increment 3 is done. Validation guards against input, and the question
overdue answers first is whether the stored format is right.
Wording changes to the messages come last of all, because nothing depends
on them.
That first increment is also the thin end-to-end path: one command in and one line out, through every module the feature touches and nothing more. It proves that the modules pass the new field to each other before any detail is built on them. Early feedback turns a possible rewrite into a small correction [2].
Order the increments
Section titled “Order the increments”A team is adding a feature to a program with a coding agent, in increments. The ordering rule is that the increment whose assumption is least certain comes first, and the thin end-to-end path comes before the details.
- Store the new field and show it in the output, through every module
- Add the command that computes something from the new field
- Refuse malformed input to the command that sets the field
- Reword the messages the new commands print
Which step would force a rewrite of the others if its assumption turned out wrong? Which step depends on nothing, so nothing waits for it?
An increment nobody is sure about
Section titled “An increment nobody is sure about”A learner is ordering the increments of a feature for a coding agent. One increment depends on an outside service whose response format the team has never seen. The others are changes inside the program.
Suppose a different feature, with five increments, and one of them talks to a service nobody on the team has called before. Where does it go in the order?
On which day is it cheapest to learn that the outside service doesn't return what you expected?
Land the first increment
Section titled “Land the first increment”Ask the agent for the first increment and nothing else. Its own list said which files it touches and which criteria it ticks, so the brief can hold it to that.
No sorting. list keeps its order, as the spec says. Land increment 1
only: due and the date in list, with test_due.py. Touch todo.py,
render.py and the new test file, and stop when criteria 1, 2 and 5 pass.
Report which criteria you checked.
Done. todo.py has a due function and a due branch in main, and
render.list_items appends (due DATE) when an item has the field. I
ran the suite, then due 1 2026-10-01 and list on a copy of the list,
and checked criteria 1, 2 and 5. Criteria 3 and 4 wait for increments 2
and 3. store.py and todos.json are unchanged.
The report is a claim. Run the suite yourself.
Run the suite after the first increment
Section titled “Run the suite after the first increment”Run the tests and keep the last line. The course applies the same increment to its own copy and checks this output against it.
python3 -m unittest -q 2>&1 | tail -1OK
Output verified in CI from site/examples/coding-with-agents/spec-driven-increments/increment_one_tests.py.
Then run the one thing this increment lets you show, against a copy of the list so the committed file doesn’t change.
Set a date and list
Section titled “Set a date and list”Set a date on the first item and show the list.
cp todos.json /tmp/todos.jsonTODO_FILE=/tmp/todos.json python3 todo.py due 1 2026-10-01TODO_FILE=/tmp/todos.json python3 todo.py listdue #1: Buy milk by 2026-10-01 1. [ ] Buy milk (due 2026-10-01) 2. [x] Call the plumber 3. [x] Renew the passport 4. [ ] Water the plants 2 open, 2 done
Output verified in CI from site/examples/coding-with-agents/spec-driven-increments/increment_one_due.py.
The first list line is criterion 1 word for word, and the three items
without a date print as they did before, which is the second sentence of
criterion 1. The agent’s diff is one new function and one new branch in
main in todo.py, one if in render.py, and a test file. The change
to render.py:
line = f"{number}. [{mark}] {item['text']}" if item.get("due"): line += f" (due {item['due']})" lines.append(line)item.get("due") is the whole answer to the question the increment was
placed first to ask. An item without the field prints as before, and no
migration of todos.json is needed. Had the answer been different, you
would know now, with one increment landed instead of three.
The last check is the spec itself. The course keeps a script that runs every success criterion against its copy after the first increment and prints one line per criterion.
Check the criteria
Section titled “Check the criteria”In a clone of the course repository, from
site/examples/coding-with-agents/spec-driven-increments/, run the
criteria check. It copies the fixture, lands the first increment and runs
each criterion of SPEC.md.
python3 criteria.pycriterion 1: pass criterion 2: pass criterion 3: not yet criterion 4: not yet criterion 5: pass 3 of 5 criteria pass
Output verified in CI from site/examples/coding-with-agents/spec-driven-increments/criteria.py.
Three criteria pass and two say not yet, and those two are the ones the agent said belong to increments 2 and 3. That line is what an increment looks like from the spec’s side: the criteria it claimed, and no others. Commit the increment now, before the next one starts, so there is a point to return to and a diff a reviewer can read in one sitting. Then brief the agent for increment 2 the same way, one increment, its files and its criterion.
Which document changes?
Section titled “Which document changes?”A learner works with a coding agent from a one-page spec with success criteria. The spec is the source of truth: when the code and the spec disagree, one of them is updated deliberately, and the code never drifts from the spec without a decision.
Is the disagreement a better idea about what the feature should do, or a mistake in doing what it says?
Why this one first?
Section titled “Why this one first?”A team lands a feature with a coding agent in increments. The ordering rule is that the increment with the least certain assumption goes first, as a thin path through every module the feature touches.
A team puts the increment that stores a new field and shows it in the output first, before the command that computes from the field. Why?
What would the team have to redo if the first increment's assumption turned out wrong after the others were built?
Is it an increment?
Section titled “Is it an increment?”A learner asks a coding agent to land a tags feature as increments. The lesson counts a step as an increment when it leaves the program working and tested and adds one thing you can run.
Match each step to whether it is an increment.
After this step, is the program working, tested, and does it do one new thing you can run?
The first increment is done
Section titled “The first increment is done”A coding agent is building a feature from a spec, one increment at a time. It reports that the first increment is done, that the tests pass, and asks whether to go on with the second.
The agent says the first increment is done and the tests pass, and asks whether to continue. What do you do?
Whose check counts before the next increment starts?
Exercise
In a fresh copy of the fixture, land increment 2 from the spec with your
coding agent: write nothing new in the spec, brief the agent for overdue
only, with its files and criterion 3, and let it write the test with the
two TODO_TODAY dates before the code. Run python3 -m unittest -q
yourself and then the two overdue commands from criterion 3 on a copy of
the list. Plan on ten minutes. This is the loop you use for any change
whose design has more than one decision in it.
A good result: the suite passes, TODO_TODAY=2026-10-02 shows item 1 and
nothing else, TODO_TODAY=2026-09-30 prints nothing overdue, and the
diff touches todo.py and one new test file. Criterion 4 is still not
yet, and that is right. Finish by deleting your copy and copying the
fixture again. Then answer one question: did the agent ask a question
the spec should have answered, and what line would you add to it?
Stretch: Now do the same for a feature you choose, for example tags on items with a `tag` command and a `list` filter. Write the spec first, and say in one line which increment is the least certain and why.
Recap
- A feature starts from a one-page spec with a goal, the commands, the limits and success criteria you can tick one by one. The spec is the source of truth while the agent works [1]. When code and spec disagree, one of them changes on purpose.
- Ask for increments in the brief. Each one leaves the program working and tested and adds one thing you can run, and the agent’s default without that ask is one large diff [2].
- Order the increments with the least certain assumption first, as the thin end-to-end path through every module before any detail is built on it.
- After each increment, run the tests and the criteria yourself, commit, and only then brief the next one.
You can now
- Works in small increments, sequenced for early feedback
References
Section titled “References”- Anthropic. The AI-native SDLC playbook. Claude Academy. Course.
Academy ai-native-sdlc-playbook - Brilliant. Developing incrementally. Brilliant, Coding with AI skills map. Reference.
Brilliant INC