How to write a spec an agent will not misread

“Users have to be able to cancel an order.” You hand it to the agent and ten minutes later the feature is there. It works.
Then the questions arrive. Can you cancel an order that already left the warehouse? Does cancelling refund the money on its own, or does somebody do that by hand? Is the customer notified? What happens if they cancel twice? Is the reserved stock released immediately or when someone confirms? None of that was in the sentence you wrote, and all of it is decided in the code you just received — because the agent resolved it on its own, without telling you.
This is the practical side of working with specifications: how to write one that does not leave those gaps. If you want the overview first —what the method solves, what it costs and when it is worth it— that is in the previous article. Here we go straight to the writing.
A spec is not a long document
The most common mistake is measuring a specification by its length. A six-page spec written in loose prose can have the same gaps as the sentence above, and now you have to read all of it to find them.
What makes it good is not how much it says, but that every statement is verifiable and admits only one reading. And there is one dividing line that organises everything: the spec says what has to happen, never how it is implemented. The how is the next stage. If you are writing function names or deciding the database structure, you have crossed the line.
Anatomy of a spec
Five parts, and none of them is optional:
1. Goal. One or two lines: what problem this solves and for whom. It is what gives you a criterion when a question comes up that the spec does not cover.
2. Scope: what is in and what is out. The second half is the one almost nobody writes and the one that pays most. “Cancelling does not modify invoices already issued” stops the agent from wandering into the billing module trying to be thorough.
3. Requirements. The heart of it, and where the notation in the next section comes in. One per line, each verifiable on its own.
4. Edge cases and unwanted behaviour. What happens when something goes wrong, arrives twice, or arrives out of order. This is what the agent fills in with assumptions when it is missing.
5. Acceptance criteria. How you check it is done. If a requirement cannot be checked, either it is badly written or it was not a requirement.
Where each thing lives: AGENTS.md and the spec
One confusion that costs time: mixing up the two documents the agent reads, which have different lifespans.
AGENTS.md holds the project’s permanent rules — the commands, the
conventions, the decisions already made. It applies to any task and is
written once. The spec describes one specific change and lives as long as
that change does; once the feature is built, it has done its job.
The rule for telling them apart is asking whether you would repeat it:
something you would have to write again in the next specification —that
amounts are stored in cents, that dates are in UTC— is not part of this
feature, it is a project rule and belongs in AGENTS.md. The reverse holds
too: filling AGENTS.md with the details of one feature bloats it and
dilutes the rules that matter. How to write it is in the repository
article.
EARS: five templates that leave no gaps
Here is the most useful tool in all of this, and nobody from the AI world invented it. EARS —Easy Approach to Requirements Syntax— was created by Alistair Mavin and his team at Rolls-Royce while writing the requirements for a jet engine control system, and presented at the IEEE requirements engineering conference in 2009. It predates agents by a long way, because the problem it solves is not new: two people reading the same sentence and understanding different things.
Five sentence templates. Every requirement fits one:
Ubiquitous — always true, no condition.
The system shall record every cancellation with user, date and reason.
When — a response to a trigger.
When the user confirms the cancellation, the system shall release the reserved stock.
While — true during a state.
While the order is being prepared, the system shall allow cancelling it at no cost.
Where — true if a feature is present.
Where the order was paid by card, the system shall start the refund automatically.
If… then — unwanted behaviour: the error and how it is handled.
If the order has already shipped, then the system shall reject the cancellation and report that a return has to be started instead.
Those five lines answer every question left open by the sentence at the top. And notice what happened along the way: each one turns into a test almost without translation, because the condition, the action and the expected result already come apart.
The formation rule is simple: zero or more preconditions, zero or one trigger, one system, one or more responses. If a sentence needs two triggers, it is two requirements.
One honest caveat: EARS is not built into spec-driven development tooling —spec-kit has an open request to add it. It is a writing discipline, not a feature you switch on. Which also means you can use it today without installing anything.
Plan mode: the cheapest way to start
If all of the above sounds like a lot, there is an entry point that costs a keyboard shortcut. In Claude Code it is Shift+Tab; other tools have their equivalent. The agent goes into plan mode: it can read your code but cannot write anything.
What changes is the conversation. Unable to touch files, the agent does the only thing left to it: read the project, tell you what it understood, and ask you what is missing. Those questions are exactly the gaps it would have filled on its own in normal mode.
It is the cheapest way to get most of the method’s benefit without adopting any methodology: you do not write a spec, you draft it with the agent. And if what it describes is not what you had in your head, you correct it when that costs a sentence, not when it costs twenty files.
The full workflow, and why it is a loop
If you have never done it, the confusing part is that the workflow is listed as seven steps but does not behave like a list. Two things worth seeing before the names: the constitution is not part of the cycle —it is written once per project and stays— and the other six repeat.
The two stages most often skipped are precisely the ones holding the cycle together. Clarification is the moment the agent asks about what the specification does not say, instead of assuming it — the step where gaps get caught while they are still cheap. And validation is checking that what was built does what the spec said, not that it “works”: those are different things, and without written acceptance criteria there is nothing to compare against.
The practical question is when it ends, because this loop has a trap: there is always something to polish, and the agent will never tell you “that’s enough”. The stopping rule that works is one full pass with no changes. If you reviewed the specification, the plan and the implementation and changed nothing, it is done. If you do not set that rule explicitly, the cycle keeps going on inertia alone and ends up costing more than the feature.
What to ask for at each step
That was the theory. This is what I would write at each stage, with one example followed end to end: a Python CLI for tracking hours worked per project. What matters in these requests is not the wording but the constraints: they are what stops the agent from running ahead.
Constitution. Written once, it sets the project’s rules.
We are going to write the constitution for a new project: a Python CLI for
tracking hours worked per project. Propose a docs/constitution.md with
non-negotiable principles, short and verifiable, covering: stack
simplicity, the relation between spec and code, separation of logic and
interface, testing policy, data persistence, and the language of the code
and the messages. Maximum 15 lines. Wait for my approval.
The 15-line cap is what prevents the bloated document nobody ends up reading. And “wait for my approval” hands you back control before it carries on by itself.
Specification. The what, never the how.
Do not write any code at any point. We are going to draft the specification
for the first feature. Read docs/constitution.md.
Initial idea: a CLI to start and stop tracking time against a project, and
to see the total per project and per week.
1. Ask me questions ONE AT A TIME to remove ambiguity: edge cases, what
happens on errors, what is out of scope for the MVP. Six questions max.
2. With my answers, generate specs/001-hours/spec.md with: context and
goal, users, user stories, numbered functional requirements (FR-1,
FR-2…) with acceptance criteria in EARS notation, non-functional
requirements, edge cases, out of scope, completion criteria, and any
remaining doubts marked [NEEDS CLARIFICATION].
3. Only the what and the why. No stack, architecture or file names: that
goes in the plan.
One question at a time is the difference between you answering and you giving up: nobody works through fifteen questions delivered at once. The cap of six forces it to prioritise the ones that remove the most ambiguity.
That is where the abbreviation used in every step that follows shows up: FR is a functional requirement, each of the things the system has to do. They are numbered —FR-1, FR-2, and so on— so you can point at them later without transcribing them: “the plan does not cover FR-4” is a sentence that lands without rereading the whole specification.
Clarification. Find holes, without plugging them yet.
Review specs/001-hours/spec.md as a very demanding QA person would. List:
(1) remaining ambiguities, (2) contradictions between requirements, (3) edge
cases not covered, (4) anything that clashes with the constitution.
Do not propose solutions yet: just find them.
“Do not propose solutions yet” is not fussiness: if it starts fixing while it searches, it stops searching.
Technical plan. Only here does the how appear.
Read docs/constitution.md and specs/001-hours/spec.md. Do not write code.
Generate specs/001-hours/plan.md with: module structure, data model with an
example, the CLI contract (commands, output, exit codes), justified
technical decisions —including the alternative you discarded— and a testing
strategy. Mark which FR each part covers.
Asking it to mark which requirement each part covers turns the plan into something checkable: if an FR appears nowhere, something is missing.
Tasks. Split the plan into steps that fit in one sitting.
From spec.md and plan.md, generate specs/001-hours/tasks.md: small tasks
(20 to 30 minutes each), in dependency order, each with the FRs it covers
and a verifiable "Done when:" line. Use checkboxes.
The “Done when” is what stops a task being called finished on a feeling rather than on a check.
Implementation. One task at a time, and stopping.
Implement ONLY task T2 from specs/001-hours/tasks.md, following plan.md and
the constitution. Write the tests first, then the code. Run pytest -q and
show me the result. When you finish: tick T2 in tasks.md, tell me which FR
it covers, and stop. Do not start T3.
That “stop, do not start T3” is the most important constraint of all. Without it the agent chains tasks and you end up reviewing six changes at once — exactly the problem the method was there to avoid.
Validation. Against the spec, not against the impression.
Go through specs/001-hours/spec.md requirement by requirement. For each one
tell me which test covers it and the result of running that test. If any is
uncovered or failing, say so plainly. Then check the completion criteria and
give me a verdict: is the spec satisfied?
That final question is what separates “it works” from “it does what the specification said”. Those are two different things, and only one of them is written down.
The minimum that changes the result
You do not need the full workflow for any of this to pay off. If you were to keep two things:
Write the requirements with the templates. Turning “users have to be able to cancel an order” into five sentences with when, while and if… then takes five minutes and removes most of the assumptions before they exist.
And do not let it write until you have read back what it understood. Whether through plan mode or by asking in one sentence, it is the point in the whole method where the least effort buys the most.
Both are done without installing anything and without adopting any methodology. It is, once again, the same old idea: what costs you dearly is not the agent writing badly, it is the agent writing well something you never asked for.
Comments
Sign in to comment and to like this article.
No comments yet. Be the first to write one.