6.3 Define "done" with acceptance criteria
Overview and links for this section of the guide.
On this page
Why “define done” prevents bad output
Without acceptance criteria, the model has to guess what success looks like. When a model guesses, you get:
- missing features (“I thought that was optional”),
- extra features (“I assumed you wanted auth/logging/config”),
- fragile behavior (“works for the happy path only”),
- argument-driven debugging (“it should work”) instead of evidence.
Acceptance criteria replace guessing with an observable target.
Vibe coding becomes engineering when “done” is testable.
What good acceptance criteria look like
Good acceptance criteria are:
- Observable: you can run a command or test and see pass/fail.
- Complete enough: includes success + failure behavior.
- Minimal: only what’s needed for this iteration.
- Unambiguous: avoids subjective language like “fast” or “nice.”
Examples
- “Given input X, return Y.”
- “If input is invalid, return exit code 2 and print an error.”
- “Existing tests pass; add 3 new tests for edge cases.”
- “Output must be valid JSON matching this schema.”
“The UI should be intuitive” is not testable. Turn it into something observable: “The primary button is disabled until required fields are filled.”
Types of acceptance criteria (practical)
1) Functional criteria
What the system does (inputs → outputs). Example: “When I pass --json, the tool prints valid JSON.”
2) Error and edge-case criteria
How failures behave. Example: “Invalid input returns a validation error, not a stack trace.”
3) Compatibility criteria
What must not break. Example: “Existing CLI flags remain unchanged.”
4) Non-functional criteria (only when needed)
Performance, cost, latency, security, etc. Keep these concrete:
- “No network calls.”
- “Must run within 2 seconds for a 1MB input.”
- “No secrets in logs.”
How to write acceptance criteria quickly
Use this 60-second method:
- Write 2–4 happy-path examples (the main thing working).
- Write 2–4 failure/edge cases (invalid input, timeouts, empty input).
- Write 1 “must not change” constraint (protect existing behavior).
That’s enough to anchor the model.
Acceptance criteria are easiest for both humans and models when they’re bullet points.
Turning acceptance criteria into tests
When possible, turn acceptance criteria into automated tests:
- Unit tests for pure logic.
- Integration tests for CLI/HTTP paths.
- Golden tests for structured outputs (exact JSON shape).
If you can’t automate yet, write a repeatable manual checklist (“run these 5 commands and compare outputs”).
When you paste a failing test into the next prompt, the model gets a precise target. That’s why tests increase vibe speed.
An acceptance-criteria prompt template
Task:
[Describe what to build/change.]
Acceptance criteria (must be true):
- [Happy path example]
- [Happy path example]
- [Edge case / failure behavior]
- [Edge case / failure behavior]
- [Must not change / compatibility rule]
Constraints:
- Language/runtime: [...]
- Dependencies: [...]
Process:
- If tests exist, update/add tests to cover the acceptance criteria
- Output diff-only changes
- Explain how each acceptance criterion is satisfied
Common anti-patterns (and fixes)
Anti-pattern: vague “done”
Fix: convert to commands, outputs, schemas, and exit codes.
Anti-pattern: too many criteria
Fix: pick the minimum set that defines correctness for this iteration. Save the rest for future loops.
Anti-pattern: no error behavior defined
Fix: always include at least one “invalid input” criterion so the model implements defensive behavior.