sinatra.dev
← All posts

How to write tickets for AI agents

· Sinatra

How to write tickets for AI agents: behavior-first titles, real acceptance criteria, constraints worth stating, and a copyable template.

Most advice about coding agents is about the agent: which model, which vendor. The stronger lever is the ticket. Learn how to write tickets for AI agents and nearly any competent agent does visibly better work; write vague ones and the best model on the market will confidently build the wrong thing.

None of this is specific to Sinatra. It applies to any agent that picks up a ticket and works alone: Copilot coding agent, Devin, OpenHands, a Claude Code script you wired to your queue. The ticket is the interface, and the interface is where the quality gets decided.

The ticket is the whole spec

When a teammate picks up a ticket, the ticket is maybe a third of the spec. The rest arrives through channels you don't think about: they sat in the planning meeting and overheard the customer call, and when something is ambiguous they ask you in the hallway.

An agent has none of that. It reads the ticket and the code, then starts working. Every gap in the ticket gets filled by inference from the codebase and the model's priors. Sometimes the inference is right. When it's wrong, you find out at review time, after the code exists, which is the most expensive moment to find out.

So the ticket is the whole spec. Ticket quality matters more than model choice, and unlike model choice, it's entirely under your control.

The anatomy of an agent-ready ticket

Four parts. Most take a sentence or two.

A title that names the behavior, not the solution

"Fix the date handling in export.ts" prescribes a solution, and the prescription might be wrong. If the defect is actually in the query layer, an agent pointed at export.ts will dutifully change export.ts. "CSV export drops rows created after the user's local midnight" names what's wrong and leaves the diagnosis open. Titles that state solutions narrow the search before it starts; titles that state behavior let the agent (or the teammate) find the real cause.

Two to four sentences of context

Where in the product this lives, what's wrong or missing, and what correct looks like. If you know the relevant file or module, name it; that one pointer can save the agent a long detour. You do not need a design document. Context answers "where do I look, and what does done mean," not "how should I build it."

Acceptance criteria as observable outcomes

Each criterion should be checkable by someone who never reads the diff. "The exported CSV contains every row visible in the on-screen report" is observable. "The export logic is correct" is not. Observable criteria do double duty: they tell the agent when to stop, and they hand your reviewer a checklist. If you can't state one observable outcome, the ticket isn't ready for anyone, agent or human.

Constraints worth stating

These are the things you'd otherwise say in review: use the existing date helper, don't touch the public API, don't add a dependency for this. Each constraint you state up front converts a review round into a sentence. If part of the problem is deliberately out of scope, say that too; "don't build bulk archiving" saves the agent from gold-plating and saves you from reviewing it.

What to leave out

Implementation prescriptions the agent should decide: how to structure the diff, what to name the internals. You'll review the result anyway; prescribing the internals just means the agent optimizes for your guess instead of for the codebase.

And links. One link to the one document that matters is context. Twelve links to everything vaguely related is noise: an agent will read them all and can't tell which one you meant, so the load-bearing paragraph in document nine competes with eleven documents of nothing. If everything is context, nothing is.

Two rewrites

A bug, before:

Title: Export is broken

Description: The CSV export doesn't work for some users. Can we fix this?

After:

Title: CSV export drops rows created after the user's local midnight

Context: The export button on /reports generates a CSV of the current
filter. Users in timezones east of UTC report that rows they created
"today" are missing. The on-screen report is correct; only the export
is wrong. The export query filters created_at by the server's UTC day
(see buildExportQuery in reports/export.ts).

Steps to reproduce:
1. Set your profile timezone to UTC+10.
2. Create a report entry at 9am local time.
3. Export "today" from /reports.
4. The entry appears on screen but not in the CSV.

Acceptance criteria:
- The exported CSV contains exactly the rows visible on screen for
  the same filter.
- A test covers a user in a non-UTC timezone.

Constraints: Use the existing toUserTimezone helper in lib/dates.ts.
Don't change the on-screen report query; it's already correct.

The first version forces the agent to guess which export, broken how, for whom. The second contains a diagnosis hint, a repro the agent can run in its sandbox, and a stopping condition.

A feature, before:

Title: Archiving

Description: We should let people archive things. A few customers
have asked.

After:

Title: Let project owners archive a project from project settings

Context: Customers with many finished projects want them out of the
sidebar without deleting them. Archiving should hide a project from
default lists while keeping its data and URLs intact.

Acceptance criteria:
- Project settings shows an "Archive project" action, visible to
  owners only.
- Archived projects disappear from the sidebar and the default
  projects list.
- An "Archived" filter on the projects page lists them, each with
  an Unarchive action.
- Direct links to an archived project still load, with a banner
  noting it is archived.
- No data is deleted, and unarchiving restores the project exactly.

Constraints: Reuse the existing status field on Project rather than
adding a new boolean. Bulk archiving is out of scope.

"Archive things" could mean ten different features. The rewrite picks one, bounds it, and makes "done" a list you can walk through in review.

A template for writing tickets for AI agents

Title: <the behavior that should change, not the solution>

Context: <2-4 sentences: where in the product, what's wrong or
missing, what correct looks like. Name files or modules if you
know them.>

Steps to reproduce (bugs only):
1. <step>
2. <what happens, and what should happen instead>

Acceptance criteria:
- <an outcome someone could verify without reading the diff>
- <the edge case you care about>

Constraints: <helpers to reuse, things not to touch, work that is
out of scope. Omit if none.>

That's the whole thing. A ticket in this shape is usually under 150 words and takes about five minutes, most of which is thinking you'd have had to do at review time anyway, moved earlier, where it's cheap.

When the ticket is vague anyway

Sometimes you can't write the acceptance criteria because you don't know them yet. The honest ticket is two sentences of "customers keep asking for X and I'm not sure what shape it should take." Don't dress that up as a spec; fake precision is worse than admitted vagueness, because it sends the agent confidently in an arbitrary direction.

A good agent treats a vague ticket as a question, not an instruction. Sinatra routes vague tickets to a brainstorm phase: it reads the codebase, drafts the requirements it thinks you mean, and posts them on the issue as a comment for your approval before any code gets written. You correct a paragraph instead of a pull request. A vague ticket costs you a review round, not a wrong PR, and the requirements you approve become the spec the implementation is judged against.

What happens after approval is the normal loop: branch, change, tests, PR. We've written that up in how Sinatra turns tickets into PRs.

The skill transfers

Everything above is also just good ticket writing. A behavioral title helps the teammate who picks the ticket up in six months. Observable acceptance criteria are what QA wanted from you all along. Constraints stated up front save review rounds no matter who authored the diff. Agents didn't invent the need for clear tickets; they removed the hallway conversations that papered over unclear ones.

Try it on something real: take one ticket from your backlog, rewrite it with the template, and hand it to an agent. If you want the agent part handled, you can start for free or read the docs to connect a repo.