The concepts are written down and the glossary is agreed — now they can be turned into the product engineer's main working artifact: the document an agent builds from and you accept the result against.
The temptation to skip this step is strong: the slice is clear in your head, the agent is smart — why not describe the task in two sentences? Because the agent will fill in the rest itself, and not from your product but from the average of everything it has seen. You say "build order placement" — it decides what an order is, which fields are required, what happens on a double click, where the money goes on failure. Each decision on its own is reasonable. But reasonable in general is not reasonable for your slice.
The result is recognisable: code arrives that looks convincing, compiles, even has tests — and does the wrong thing. Not crudely wrong, just a couple of steps sideways: the boundaries are wider than you wanted; the edge behaviour is different; the wrong contract sticks out. You notice it not at acceptance but two weeks later, when you build the next slice on top of it. And the more capable the agent, the more plausible the wrong thing looks: vagueness in the task is not compensated by the executor's intelligence, it is amplified by it.
A spec is a conceptual model with commitments
Look at what a good specification is made of:
- Entities and their attributes — the concepts of the model: order, line, payment.
- States and transitions — the life cycle:
draft → placed → paid → shipped, "there are no transitions out ofcancelled." - Commands — what can be done: "place an order," "cancel," "pay" — with preconditions ("only a
placedorder can be paid"). - Events — what happens as a result: "order placed," "payment received" — what other parts of the system react to.
- Invariants — rules that are never violated: "the sum of the lines equals the order total," "an order has exactly one customer."
- Acceptance criteria — verifiable scenarios: given → when → then.
The first five are exactly the elements of a domain model (objects, states, events, relations, rules), only with commitments: a spec does not merely describe concepts, it fixes what the system guarantees about them. That is why trying to "write a spec" without a conceptual model fails — there is nothing to unfold. What comes out is a retelling of wishes, where the agent still has to guess what an order is and whether a cancelled one can be paid.
The order of work is: concepts and relations → states (forbidden transitions matter more than allowed ones) → commands and events in the shared glossary (CancelOrder and OrderCancelled, not synonyms by mood) → invariants → acceptance criteria. The output is a few pages with not a word about frameworks or tables: domain only. Technology goes in a separate section and is not mixed into the model — we have already seen what mixing produces.
The slice contract: the unit of work
A spec describes a whole area, but work happens in slices — pieces of product each of which solves a real user problem and tests a hypothesis with minimal effort (on cutting down to such a slice, see prioritising the smallest slice).
One slice unfolds into a short contract. This is not a hundred-page requirements document but four things, recorded precisely enough that you can both build from them and check against them afterwards.
1. Boundaries — what is in and what is out. The most underrated part. You list explicitly what the slice includes, and — as a separate list — what it deliberately does not. "Card payment — yes; saved cards, instalments, refunds — no, not in this slice." The "not included" list is worth more than the "included" one: that is exactly where the agent wanders by default, building something plausible.
2. The scenario in the user's words. One use case described the way a person lives it, not the way a system executes it: "the buyer confirms payment, sees a confirmation, the money is charged once." Not "the controller accepts a POST and calls a service." If the scenario does not fit into one clear sentence, the slice is not cut finely enough yet.
3. The interface — input, output, errors, repeat calls. Not an implementation but a signature: what the operation takes, what it returns, which errors it answers with, and what happens on a repeat. Idempotency is fixed here, at contract level, not "we will sort it out in the code": a repeat call with the same key returns the same result, not a second charge. This is the boundary neighbouring pieces of the product see — it cannot be left to the agent's discretion.
4. Acceptance criteria — verifiable ones. Not "works correctly" but a list of statements each of which can be answered yes or no. "A repeat with the same key does not charge twice." "An unavailable gateway leaves the order unpaid and the user sees a clear error." If a criterion cannot be checked, it is a wish, not a criterion, and the agent will quietly ignore it.
What it looks like
The contract is short and language-agnostic: the craft the agent already knows, your job is to set the frame.
UseCase: Pay for an order
Boundaries:
in — card payment for one order, single charge
not in — saved cards, instalments, partial payment, refunds
Scenario:
The buyer confirms payment for an order in the "awaiting payment"
state and gets a confirmation; the money is charged exactly once.
Interface:
input — order identifier, idempotency key
output — order status, payment link
errors — order not found; order already paid; gateway unavailable
repeat — the same key returns the first result, no second charge
Acceptance criteria:
1. A successful payment moves the order to "paid" and returns a link.
2. A repeat with the same key does not charge a second time.
3. An unavailable gateway leaves the order "awaiting payment", error is clear.
4. Paying an already-paid order is rejected and charges nothing.
Twenty lines. But an agent builds predictably from them: the boundaries keep it from sprawling, the scenario holds the meaning, the interface fixes the seam, the criteria say when it is done.
Why it works
Three reasons, all of them things a vague task does not give you.
The contract is verifiable. Invariants and acceptance criteria are ready-made tests, and accepting the result turns from "looks about right" into running scenarios. It is written once and works twice: as the brief on the way in and as the basis for acceptance on the way out.
It removes guessing. The agent does not decide for itself whether a cancelled order can be paid — that is written down. The less freedom there is in meaning, the more useful the agent's speed becomes: freedom stays where it belongs, in the implementation.
It outlives sessions. Session context disappears, the document remains. The next session — another agent, or you in a month — starts from the same text, and three sessions produce one solution rather than three.
And one less obvious point: the contract is not an extra stage between "thought it up" and "built it," it is a translation. To its left is product language: problem, value, hypothesis. To its right is build language: command, interface, error handling. Without the contract the transition still happens — silently, inside the agent, at its discretion. You hand a product decision to someone who does not know your product.
The discipline scales from there: a contract per slice is the unit of work, a set of contracts is the model of a service. That is how the Use Case Pattern methodology this whole site rests on is built: the specification lives next to the code, is executed by the agent, and is checked on every change.
In short
- A vague task yields a plausible wrong thing, and the more capable the agent, the better it is disguised.
- A spec is a conceptual model with commitments: entities, states, commands, events, invariants + acceptance criteria.
- The order: concepts → states (forbidden matters more than allowed) → commands and events in the shared glossary → invariants → acceptance criteria. Domain separate, technology separate.
- The unit of work is a contract for one slice: boundaries (including "not included"), the scenario in the user's words, the interface (input, output, errors, repeats), verifiable criteria.
- The "not included" list is worth more than the "included" one: that is where the agent adds extras.
- A contract is verifiable (criteria = tests), removes guessing (invariants are not interpreted) and outlives sessions.
- Written once, it works twice: as the brief on the way in and as the basis for acceptance on the way out.
What to read next
- The language of the domain — where the concepts a contract unfolds from come from.
- Accepting AI output — how criteria turn into verification.
- The smallest valuable slice — cutting a task down to one line of value.
- Use Case Pattern — a methodology where such contracts live next to the code.