← Blog

Writing Acceptance Criteria a Coding Agent Can Verify

9 min de lecture

Key takeaways

  • Rewrite every acceptance criterion as a Given-When-Then statement where the When clause names the test invocation and the Then clause names the assertion.
  • A criterion is agent-verifiable only if a command you would trust in CI can return pass or fail on it.
  • Keep two to four machine-checkable criteria per story so each pull request stays reviewable against its stated assertions.
  • Review an agent's pull request criterion by criterion, running the edge-case tests first and rejecting anything as a new criterion rather than a comment.
  • Split any story whose criteria require human judgment, such as design taste or prod-safety calls, into a testable story plus a separate design review.

Writing acceptance criteria for coding agents means converting each criterion into a machine-checkable assertion: a named test, command, or exit code the agent can run itself before claiming done. Human-readable Given-When-Then statements still work, but only if every clause maps to something runnable. This article rewrites one user story twice to show exactly what changes.

Why do acceptance criteria written for humans fail when handed to a coding agent?

Traditional acceptance criteria assume a human implementer who can read intent, ask a teammate, and fill gaps with judgment. "The user can easily filter invoices" works for a person. A coding agent reads it literally, picks an interpretation, and builds it. If the interpretation is wrong, every downstream prompt in the queue inherits the error, because the agent has no way to check itself against a vague adjective.

The second failure is verification. You wrote criteria so a human reviewer could eyeball the result. With parallel agents, nobody is eyeballing anything. "How do I know the implementation is actually complete?" is the question that comes up once you have six worktrees running at once, and reading every diff defeats the point of the fleet.

The fix is to make each criterion a machine-checkable assertion. The Given-When-Then structure from Cucumber's documentation already points this way: it maps cleanly to test setup, action, and assertion. We built Aerunit around that idea for teams running Cursor Cloud Agents on Linear, and you can request access to try it.

What makes an acceptance criterion verifiable versus subjective for an AI agent?

A verifiable criterion is one a CI command can return pass or fail on. "The export endpoint returns 400 when the CSV exceeds 10,000 rows" is verifiable: an agent can run a test, read the exit code, and know. "The export flow feels fast" is subjective: the agent will guess, ship something plausible, and you will find out during review.

The quick test: could a command you'd trust in CI return pass or fail? If not, rewrite it or cut it.

VerifiableSubjective
POST /invite returns 409 when the email existsHandle duplicate invites gracefully
npm test passes with zero failures on the auth suiteCode is well-tested
Bundle size stays under 200 KB gzippedPage loads quickly
Lighthouse accessibility score is 95+Site is accessible

Subjective isn't always wrong; "the diff touches no files outside src/billing/" is a scope check an agent can also run. The failure mode is vague criteria on multi-step work: the agent drifts at step one and every queued prompt inherits the error. We apply the same test to our own story templates (What makes a good story for a coding agent — Aerunit), and the honest gap is that some criteria, like "the migration is safe to run on prod," still need a human judgment call the agent cannot self-verify.

How do you write Given-When-Then criteria that map to automated tests an agent can run?

Given-When-Then works with agents because each clause already names a trigger and an observable outcome. The translation rule is simple: the When clause becomes the test invocation, and the Then clause becomes the assertion. If you cannot name the command the agent would run to check a criterion, the criterion is not written for an agent yet.

The Atlassian guide on user stories frames acceptance criteria as a shared definition of done between writer and implementer. That framing survives the handoff to an agent only if "done" is executable. Take a criterion like "the user can reset their password." As written, an agent has to guess what counts as reset: email flow? token expiry?

rate limiting? Each guess is a place where the next queued prompt inherits the wrong assumption, which is exactly the context-degradation problem people hit when chaining sessions. Rewrite it as "Given a registered email, when POST /reset-password is called twice within 60 seconds, then the second response returns 429" and the agent can write the test before the feature, run it after, and paste the result into the PR.

Worked example: the same story rewritten for an agent

Before: "Users can filter the dashboard by date range."

After, three runnable criteria:

  • Given a range wider than 90 days, when the filter is applied, then the API returns 400 and the UI shows the existing error state.
  • Given a valid range, when the filter is applied, then the row count in the response matches a fixture.
  • Given an empty result set, when the filter is applied, then the empty-state component renders.

Each one names a fixture, an endpoint, or a component, so the agent verifies itself and you review the diff against evidence instead of reading six terminals of output. We keep a running set of criteria patterns like these on the Aerunit blog.

How should acceptance criteria in Jira or Linear change when the implementer is an agent?

Three things change when the implementer is an agent instead of a teammate reading the ticket.

First, every criterion becomes a checkable assertion. "Password reset works" becomes "submitting a reset token older than 60 minutes returns 422 and the token is invalidated." The agent can run the second one; the first one waits for you.

Second, the ticket carries its own verification path. Point the agent at the test file to extend, the command to run, and the branch or worktree to work in. If you plan work in Linear and run parallel Cursor Cloud Agents, a ticket missing these fields becomes a prompt you have to paste manually, and you are back to being the glue between every step. Structure like this is what lets parallel coding agents stay busy without you queueing the next prompt.

Third, scope shrinks. A story with eight criteria produces a diff too large to review confidently. Two to four criteria per ticket keeps each pull request reviewable against its stated criteria.

Honest limitation: writing machine-checkable criteria takes longer than writing prose, roughly two to three times the effort per ticket in practice. You save that time on review, not on planning.

Which common acceptance criteria mistakes cause agents to drift off-target?

The first mistake is writing criteria as intent instead of assertion. "The checkout flow should feel fast" gives an agent nothing to run; the Scrum Guide defines acceptance criteria as the conditions a work item must satisfy, and that only pays off when each condition is falsifiable. Rewrite every criterion as a Given-When-Then statement with a concrete trigger and observable result.

The second mistake is burying context the agent needs. If a criterion says "existing behavior is unchanged" but never names which endpoints or files count as existing behavior, the agent guesses, and a wrong guess in step one compounds through every queued prompt that follows.

The third is criteria that require taste: naming choices, error-message wording, "clean" refactors. Agents declare done early on these because there is no test that can fail.

The fourth is omitting the negative case. Without "rejecting a duplicate email returns 409," the agent optimizes only for the happy path.

Aerunit attaches each criterion to the story it dispatches, so agents verify their own work against the assertions you wrote rather than waiting for you to review the diff. The tradeoff: criteria still have to be written well up front, and a vague list produces confident, wrong completions faster. For worked examples of criteria shaped for agent execution, see Qué hace buena una historia para un agente — Aerunit.

When should you split a user story so its acceptance criteria are agent-executable?

Split a story when any criterion would need a human judgment call rather than a runnable check. If you find yourself writing "the checkout flow feels fast" or "the UI is clean," that story is two stories: one with testable behavior, one with a design review attached. Agents can verify the first; you review the second.

A practical slice: one story per agent run, scoped to a change that fits in a single reviewable diff. Cursor's cloud agents receive the task context you attach to the issue and work in an isolated environment, so a story that spans three repos or mixes refactoring with new features forces the agent to guess priorities mid-run (Cursor docs).

Two heuristics that hold up:

  • One story, one verifiable outcome. "Add rate limiting to the API" passes. "Add rate limiting and migrate the auth service" fails, because the second criterion's tests depend on the first being correct.
  • Criteria must not reference future work. "Done once ticket ENG-402 lands" is not machine-checkable. If a criterion depends on another story, that is your split point.

When a story still resists slicing, the problem is usually the criterion, not the story: rewrite it as Given-When-Then and the boundary often becomes obvious.

How do you review an agent's pull request against its acceptance criteria?

Review the diff criterion by criterion, not line by line. For each Given-When-Then criterion in the Linear issue, find the test or check in the PR that proves it, run it yourself, and mark it pass or fail. If a criterion has no corresponding assertion in the diff, the PR is not done, regardless of how clean the code looks. The reason this works is arithmetic: a four-criterion story needs four checks, each one a command with a binary result, so a review that would take forty minutes of code reading takes ten minutes of verification. You are no longer asking whether the implementation looks right; you are asking whether each assertion is honest and whether the test behind it would actually fail if the behavior were deleted. That question, asked once per criterion, catches most agent drift, because an agent that misunderstood the story almost always wrote a test that passes trivially.

Start with the failing cases. Run the tests that encode edge conditions first, because an agent that nails the happy path but fails a boundary case has misunderstood the story. Then review the diff for scope creep: changes to files the criteria never mention are a signal the agent drifted.

When "done" is defined by machine-checkable assertions, your review reduces to verifying the assertions are honest, not re-deriving the whole implementation. Ask one question per criterion: would this test fail if the behavior were removed?

Log anything you reject as a new criterion rather than a comment. "Handle empty cart" becomes a Given-When-Then the agent can verify on its next pass, and you stop being the glue between every step.

FAQ

How many acceptance criteria should a user story have when a coding agent implements it?

Two to four. A story with eight criteria produces a diff too large to review confidently, so you end up reading code cold instead of checking assertions. Keep each criterion machine-checkable and scoped so the whole change fits one reviewable pull request. If you need more than four, the story needs splitting, not compressing.

Do I still need acceptance criteria if I write tests first?

Yes, but they get shorter. Tests already encode the assertion, so the criterion can simply name the test file and command the agent should run and extend. What tests cannot replace is scope: a statement like "the diff touches no files outside src/billing/" is a check no test suite enforces, and it belongs in the criteria.

Can I write acceptance criteria after the agent has started working?

You can, but you lose the main benefit. Criteria written mid-run arrive after the agent has already picked an interpretation, and a wrong guess at step one compounds through every queued prompt that follows. If you must add them late, treat them as a new dispatch with the criteria attached, not as a correction mid-flight.

What's the difference between acceptance criteria and a definition of done for agent work?

Acceptance criteria are per-story assertions: named tests, commands, or exit codes that prove this specific behavior works. A definition of done is repo-wide and applies to every story: tests pass, linting clean, diff stays in scope. Agents need both, but only acceptance criteria change per ticket. Keep the definition of done in your agent setup once and stop repeating it.