Fleet 1.13:Teams are now shipping 5x more PRs with autonomous pipelines.See what's new →
FleetFleet
Guide

How to Write GitHub Tickets AI Agents Can Actually Execute

An AI agent does not push back on a vague ticket the way a human engineer does. It reads what you wrote, fills the gaps with assumptions, and produces a PR that reflects those assumptions. A thin ticket gives you a thin PR. The quality of agent output is bounded by the quality of the issue that triggered it, so writing a good ticket is the highest-leverage thing you can do to improve results.

This guide covers how Fleet turns a GitHub issue into agent work: the required_label gate that decides whether an issue enters the chain at all, the labels that drive the reactive chain from ready to shipped, and how the agent resolves which task to work on. It also covers the writing itself — acceptance criteria, scoping small — with an honest note: Fleet cannot rescue an underspecified ticket.

Before you start

  • Fleet initialized in your repository with `fleet init`
  • The Fleet watcher running (`fleet watcher start`)
  • At least one developer agent subscribed to `ticket_ready` in `.fleet/config.yaml`
  • GitHub CLI (`gh`) authenticated with write access to issues and labels
1

Know the required_label gate

Fleet's config has a required_label field. When it is set, an issue must carry that label before the watcher will route it into the reactive chain — even if it already has ready. This is a deliberate triage gate: it stops every labeled issue in the repo from being picked up. If your agents never start on issues you thought were ready, this gate is the first thing to check.

# .fleet/config.yaml
repo: your-org/your-repo
owner: your-github-username
required_label: fleet

agents:
  - name: dev-1
    role: backend-developer
    department: engineering
    reports_to: tech-lead
    subscribes_to: ticket_ready
2

Label the issue to enter the chain

With required_label: fleet set, an issue needs both the gate label and ready to enter the chain. The watcher polls labels roughly every two minutes, translates ready into a ticket_ready fabric event, and the subscription processor dispatches the matching developer agent. Add the labels and the work is queued.

# Issue must satisfy the required_label gate AND be ready:
gh issue edit 42 --add-label fleet
gh issue edit 42 --add-label ready

# Confirm the dispatch in the log:
fleet log --type decision --since 10m
3

Understand the labels that drive the chain

The reactive chain advances through labels. ready starts a developer; the developer opens a PR and adds needs-review, which starts a reviewer; an approval lets the release-manager merge and mark the work shipped. You write the ticket, but you should understand the lifecycle your label kicks off, because the ticket has to carry enough context for every stage — not just the first.

# Label lifecycle the ticket flows through:
#   ready        -> developer agent starts
#   needs-review -> reviewer agent starts
#   shipped      -> release-manager merged it
#
# One human action (adding `ready`) sets the whole chain in motion.
4

Know how the agent resolves its task

When an agent starts, the handbook tells it how to find its task, in priority order: an explicit FLEET_TASK_TITLE (set by fleet task assign or a pipeline), then the triggering event payload, then its inbox, then open issues. Understanding this order tells you where to put the work. A label-driven dispatch lands in the trigger payload; a targeted hand-off uses fleet task assign. Either way, the issue body is what the agent reads to understand scope.

# Task-source resolution order (from the handbook):
#   1. FLEET_TASK_TITLE   (fleet task assign / pipeline)
#   2. trigger event payload (label-driven dispatch)
#   3. inbox
#   4. open issues
#
# Targeted dispatch bypasses labels:
fleet task assign dev-1 "Add cursor pagination to GET /api/posts"
5

Write concrete acceptance criteria

The single biggest quality lever is acceptance criteria the agent can verify against. State the expected behavior, the interface, edge cases, and what 'done' looks like. Vague verbs like 'improve' or 'handle' give the agent nothing to test against. Concrete, checkable criteria let the agent — and the reviewer agent downstream — confirm the change actually meets the spec.

## Add cursor-based pagination to GET /api/posts

### Acceptance criteria
- [ ] `GET /api/posts?cursor=<id>&limit=<n>` returns up to `n` posts
- [ ] Response includes `next_cursor` (null on last page)
- [ ] `limit` defaults to 20, capped at 100
- [ ] Invalid cursor returns 400 with `{ "error": "invalid_cursor" }`
- [ ] Unit tests cover: first page, middle page, last page, bad cursor

### Out of scope
- Offset pagination (keep existing behavior untouched)
6

Scope each ticket small

One ticket should be one cohesive change an agent can finish in a single focused run and a reviewer can evaluate in one pass. A sprawling epic produces a sprawling, hard-to-review PR — and a higher chance the agent drifts. Decompose large work into independent tickets, each with its own acceptance criteria, and let the chain run them. Small tickets also make re-work cheaper when a reviewer requests changes.

# Too big (one ticket):
#   "Rebuild the posts API with pagination, caching, and auth"
#
# Right-sized (three tickets, each independently shippable):
#   #42 Add cursor pagination to GET /api/posts
#   #43 Add response caching to GET /api/posts
#   #44 Require auth on POST /api/posts

Common pitfalls

  • Forgetting the `required_label` gate. If it is set in config, an issue with only `ready` never enters the chain. This looks like a broken watcher but is usually a missing gate label — add the configured label (e.g. `fleet`) to the issue.
  • Writing a vague ticket and expecting a precise PR. The agent does not ask clarifying questions the way a person does; it fills gaps with assumptions. A vague ticket produces a vague PR — front-load the context.
  • Putting acceptance criteria nowhere the agent reads. The agent resolves its task from the trigger payload or issue body; criteria buried in a linked doc or a comment thread may be missed. Keep the spec in the issue body.
  • Over-scoping a single ticket. A large epic yields a large PR the reviewer agent struggles to evaluate, raising the odds of multiple changes-requested rounds. Decompose into small, independently shippable tickets.
  • Relying on the GitHub assignee field to route work. Fleet routing is label and event driven, not assignee driven. For a targeted hand-off to a named agent, use `fleet task assign`.

When Fleet is the right tool

Investing in ticket quality pays off the moment you let Fleet's chain run unattended, because the issue body is the only brief the developer agent gets before it starts coding. It matters less if you are hand-feeding tasks interactively with fleet task assign and watching each run — there you can correct course in real time. But for the autonomous label-driven flow, a well-scoped ticket with concrete acceptance criteria is the difference between a mergeable PR and a wasted run. Be honest with yourself: if you cannot write clear acceptance criteria, the work is not ready to dispatch.

Frequently asked questions

Why is my issue labeled `ready` but no agent starts?

If your config sets `required_label`, the issue must also carry that label (e.g. `fleet`) before it enters the chain. This is a triage gate, not a bug. Add the configured label alongside `ready` and the watcher will dispatch on its next poll.

How does the agent decide which task to work on?

The handbook resolves the task in priority order: an explicit `FLEET_TASK_TITLE` (from `fleet task assign` or a pipeline), then the triggering event payload, then the agent's inbox, then open issues. For label-driven work, the task arrives in the trigger payload, so the issue body must hold the real spec.

Does a more detailed ticket really produce a better PR?

Yes, directly. Agents do not push back on ambiguity — they fill gaps with assumptions. Concrete acceptance criteria give the agent something to build and test against, and give the downstream reviewer agent something to verify, so detailed tickets produce sharper PRs.

How small should a ticket be?

Small enough that the agent can finish it in one focused run and a reviewer can evaluate the PR in a single pass. Decompose epics into independent tickets, each with its own acceptance criteria, rather than dispatching one sprawling issue.

Run your first agent fleet

One binary. Five minutes. See every agent, coordinate every handoff, and keep a full audit trail of what your fleet did.