We started writing specifications differently once we noticed the pattern: the tickets a coding agent implemented cleanly were not the detailed ones, they were the decided ones.
Ambiguity is the failure mode
A model does not stop and ask when a requirement has two readings. It picks one, writes confident code, and the ambiguity surfaces in review — or worse, in production. Humans do the same thing, but a human usually leaves a comment about it.
So the first rule is blunt: if a sentence in your ticket can be read two ways, it will be.
The shape that works
Our tickets now have five parts, in this order.
- Context — one paragraph on why this exists and which user does it.
- Contract — the inputs, the outputs, the types, and the error cases by name.
- Constraints — what must not change: existing routes, database columns, public interfaces.
- Acceptance — what a passing test would assert, in plain sentences.
- Out of scope — the adjacent things an eager implementation would touch and shouldn't.
The last section is the one teams skip and the one that saves the most review time.
Name the files
Pointing at the existing module that does something similar is worth more than three paragraphs of description. "Follow the pattern in the invoices service" gives a model — and a new hire — the conventions, the error handling and the test style in one line.
A specification is not documentation. It is the set of decisions someone has already made so the implementer doesn't have to.
Keep the contract machine-checkable
Where we can, the contract is a type, a schema, or a failing test committed before implementation. Then correctness is not a matter of reading the diff carefully; it is a matter of running something.
What it costs
Writing this kind of ticket takes about fifteen minutes longer than the version we used to write. It has replaced review cycles that ran to hours. That trade has held on every project where we have measured it.
