How to Write Acceptance Criteria Your AI Agent Can Actually Verify
Stop ending AI coding sessions with 'close, but not quite' — learn to write acceptance criteria an AI agent can check before you review the diff.
Most AI coding sessions follow the same pattern. You describe what you want, the model produces something, you review it, and you say “close, but not quite” — then the loop repeats two, three, sometimes five times before the output matches what you imagined. The cost is measured in tokens, time, and frustration, but the root cause is nearly always the same: your acceptance criteria were not specific enough for the model to verify against its own output.
Acceptance criteria in human-led projects serve as the shared definition of “done” between the requester and the developer. In AI-assisted coding, they serve the same purpose, but the developer is a language model that reads every word literally. If your criterion says “make it look nice”, the model must guess what “nice” means. If it says “the form button should be blue, not disabled when all required fields are non-empty, and show a spinner on click for up to three seconds”, the model can check all three conditions before sending the code.
What makes a criterion verifiable?
A verifiable acceptance criterion has three properties: it is observable, unambiguous, and binary.
Observable means a human — or a model inspecting its own output — can determine whether the condition is met by looking at the result. “The button turns green when clicked” is observable. “The button looks good” is not.
Unambiguous means no room for interpretation. “The error message appears below the input field, left-aligned, in red 14px text” is unambiguous. “Handle errors gracefully” is not.
Binary means it passes or fails with no grey area. “The API returns a 200 response within 500ms” is binary. “The API performs well” is not.
When all three are present, the model can run a mental checklist against its own output before presenting it to you. If a criterion fails, the model knows it failed and can adjust. This is the difference between a model that guesses and a model that verifies.
Vague versus verifiable — side by side
| Vague criterion | Verifiable criterion | How the model checks it |
|---|---|---|
| ”Make it responsive" | "Layout collapses to a single column below 768px; navigation becomes a hamburger menu; font size scales down proportionally” | Inspect at breakpoint |
| ”Add validation" | "Email field rejects input without ’@’; submit disabled until all required fields are non-empty; inline error message in red below failing field” | Test each condition |
| ”Improve performance" | "Lighthouse Performance score ≥ 90 on mobile; LCP under 2.5s” | Run Lighthouse |
| ”Clean up the code" | "No unused imports; every function has a return type annotation; file is under 200 lines” | Static analysis |
| ”Make it secure" | "All user input sanitised with DOMPurify before rendering; API keys in environment variables, not source” | Inspect output |
Each verifiable criterion describes an outcome the model can check before it sends you the result. You are not writing unit tests — you are writing guardrails that steer the first attempt closer to what you actually want.
Writing criteria inside a prompt
The most effective place for acceptance criteria is at the end of the prompt, after you have described the task and the stack. This keeps the criteria separate from the instruction so the model treats them as a checklist rather than part of the specification.
Build a contact form component in React that collects name, email, and
message. Use React Hook Form for validation and Tailwind for styling.
Acceptance criteria:
- The email field rejects input without an '@' symbol and shows
"Please enter a valid email" below the field in red 14px text
- The submit button is disabled while the form is submitting and
displays a spinner
- On success, the form clears and a green banner says "Message sent!"
above the form
- On network failure, the form keeps the user's input and shows a red
"Could not send. Try again." banner
- The component exports as a default export from
`components/ContactForm.tsx`
The model can check each criterion against the component it writes. If the spinner is missing, it knows to add one. If the error banner uses the wrong colour, it fixes it. The first pass lands closer to shippable because the model was not guessing what “done” meant.
This approach maps directly to the patterns covered in Harness Engineering, which goes deeper into how to structure verifiable prompts at scale across large codebases.
Including edge cases
Models are optimised to produce plausible output, not to anticipate every edge case. If you do not mention empty states, error states, or loading states, the model will often skip them. A single additional group in your criteria fixes this:
Edge cases:
- Show an empty state with "No results found" when the data array is empty
- Handle a null or undefined user object without crashing
- Display a skeleton loader while data is fetching
Edge-case criteria have an outsized impact on quality because models default to the happy path. A prompt that explicitly asks for error handling, empty states, and loading indicators will produce a component that works in production — not just in a demo.
A reusable criteria template
Bookmark this format for your next session. It works across ChatGPT, Claude, Cursor, GitHub Copilot, and any other agent-based coding tool.
Task: [one-sentence description of what to build]
Stack: [language, framework, libraries, version constraints]
Acceptance criteria:
- [observable, unambiguous, binary condition 1]
- [observable, unambiguous, binary condition 2]
- [observable, unambiguous, binary condition 3]
Edge cases:
- [empty state]
- [error state]
- [loading state]
Out of scope:
- [features not to implement]
The “out of scope” line is surprisingly important. Without it, models sometimes add speculative features — authentication, pagination, dark mode — that you never asked for. Listing what not to build saves you from reviewing code you will delete.
For a deeper treatment of this template across an entire project lifecycle, see Agentic Coding Pro, which covers verification-driven development with AI agents from architecture through deployment.
Matching criteria to task size
For a single component, five criteria are enough. For a full-page layout, ten to twelve might be right. For an entire project brief, group criteria by feature area and keep each group under ten. The goal is not to write a test suite — it is to remove the ambiguity that causes rewrites.
The Google AI prompt engineering strategies guide recommends similar thinking: break complex tasks into smaller subtasks and define success for each one. Acceptance criteria are how you apply that strategy at the level of individual prompts.
Measure the difference
Next time you open a chat, try the criteria-first approach. Write your prompt, add a list of verifiable acceptance criteria, and compare the first output against what you would have received with a vague instruction. You will probably need one follow-up instead of five.
That saved time compounds across a project. Each session that lands closer to the target on the first pass means fewer hours reviewing, correcting, and re-prompting. Over the course of a week, verifiable acceptance criteria can halve the number of rounds between “build this” and “ship this.”
Further reading
More insights
All ArticlesA 90-Day Roadmap for Rolling Out Agentic Coding on Your Team
Roll out agentic coding without the chaos — a phased plan that gives your team standards, governance, and a rollout pace they can actually sustain.
Read articleContext Windows, Memory, and Why Your AI Keeps "Forgetting" the Project
Discover why your AI loses track of your project mid-session and how context windows, memory files, and smart prompting keep conversations on track.
Read articleCursor Rules, .cursorrules, and Project Context That Sticks
How a single .cursorrules file keeps your AI coding tool grounded in your project's stack, conventions, and preferences — session after session.
Read articleCursor vs Claude Code vs GitHub Copilot: A Practical Comparison for 2026
See how Cursor, Claude Code, and GitHub Copilot compare across real workflows — and which AI coding tool fits your team and project in 2026.
Read article