Planning the work
The pipeline does not plan. Planning stays with humans, and the pipeline only takes what Planning hands it and carries it, unattended, from picking a Ticket to merging it (ADR-0001). Because it trusts what it is handed, it reads only a few things off an issue, reads them strictly, and refuses an issue that gets one of them wrong rather than guess.
| A Ticket needs | Why |
|---|---|
The ready-for-agent label | It is how a Run finds the Ticket |
| No assignee | Anyone assigned has claimed it |
Acceptance Criteria: - [ ] lines in the body or any comment | They are the only thing the verify Stage grades |
Blockers as native blocked by links, all closed | They decide which Tickets may run, and in what order |
| No native sub-issues | An issue with sub-issues is a Spec, not a Ticket |
The same list, written for the agents that plan, is the conventions document init puts in every Target: docs/agents/pipeline-conventions.md.
Specs and Tickets
Planning is a chain of skills from the mattpocock-skills plugin, run in your own sessions:
| Skill | Produces |
|---|---|
grilling | A plan whose questions you, not an agent, have answered |
to-spec | A Spec: one parent issue describing a whole feature |
to-tickets | Tickets: native sub-issues of the Spec, each one agent session of work, labelled ready-for-agent |
triage | Tickets from issues that arrived some other way, with the brief (and its criteria) posted as a comment |
A Spec is only ever implemented through its Tickets. A Ticket should be small enough for one implement Stage, which by default has 300 turns and 60 minutes (Configuration).
After to-tickets, check what a guard would otherwise refuse:
- each Ticket has
- [ ]criteria and theready-for-agentlabel - every issue a body names under
Blocked byis a native link too (Blockers) - the Spec carries no
ready-for-agent:to-specputs it on, and the Spec is skipped asno-criteriauntil its Tickets exist, when thespecguard takes it off
To file one by hand with its parent and blockers in place:
gh issue create --title "Drain the Frontier" --parent 12 --blocked-by 14,15 --label ready-for-agentAcceptance Criteria
One criterion is an unticked task list item at the head of a line: optional indent, a -, * or + bullet, one space, then [ ]. Nothing else is one, however much it reads like a promise.
- [ ] `run 3 7` takes only #3 and #7 ← a criterion
* [ ] a nested item counts too ← a criterion
- [x] already ticked ← not: nothing left to grade
The command must refuse bad input. ← not: prose
1. [ ] a numbered item ← not: no bulletThe verify Stage grades each one as met, unmet or unverifiable (From Ticket to merge).
Write criteria that a session can check by running or reading code. A criterion that needs a human's eyes (a screenshot, a feel) comes back unverifiable.
Blockers
Only GitHub's native blocked by links count (ADR-0003), so what a Run takes is what GitHub's own UI shows as unblocked.
gh issue edit 23 --add-blocked-by 14,15 # add links to an existing issue
gh issue edit 23 --remove-blocked-by 15 # take one offA Ticket waits while any of its blockers is open. A blocker that another Lane of the same Run is still working on is open too, and that is the whole of what keeps two Tickets out of each other's way: nothing else decides which Tickets are safe to run side by side. The moment a blocker merges, the Ticket it held back can be taken in the same Run. So link a blocker between Tickets that change the same code: the second is then built on the first, where without one they run side by side and the later one's rebase goes to the conflict Stage.
A Blocked by section in the body is fine as a human-readable copy, as long as every issue it names has a native link as well. When it names one without, the body-only-blockers guard refuses the Ticket. The guard reads only the section itself, so issue references elsewhere in the body are prose:
| Written as | Where the section ends |
|---|---|
Blocked by: #14, #15 on one line | At the end of that line |
A ## Blocked by heading followed by a list | At the end of the first list under it |
A reference to another repository (other/repo#12) is never read as a blocker.
Labels
The labels are the triage state machine: one state label per issue at a time. init creates all six; labels renames them.
| Label | Put on by | What the pipeline does with it |
|---|---|---|
needs-triage | Humans; the pipeline on its standing Notes issue | Nothing, except opening the Notes issue with it |
needs-info | Humans | Nothing |
ready-for-agent | Planning; a Release; a human handing a Ticket back | Takes the issue as a candidate. Removed at the claim |
in-progress | Only a Run, at the claim | Marks a Ticket a Run holds. Removed at merge, Hand-off and Release |
ready-for-human | A Hand-off | Leaves the Ticket alone until a human relabels it |
wontfix | Humans | Nothing |
To keep the pipeline off a Ticket, take ready-for-agent off it, or assign someone: a candidate with any assignee is treated as claimed and passed over in silence. Do this before running the plugin's /implement-spec on a Spec in your own session: it leaves no mark on the Tickets it works, so a Run would take them too and do the same work again on other branches.
Guards
Planning goes wrong in a few known ways. A guard passes such a candidate over before it is claimed and says once why, but never repairs the issue itself, so you fix the Planning output and run again. Every open ready-for-agent issue a Run looks at is a candidate, and the Frontier is the candidates left once the claimed and the blocked ones are dropped:
| Reason | What the candidate did | What the pipeline does |
|---|---|---|
spec | It has native sub-issues, so it is a Spec | Skips it and removes ready-for-agent: its Tickets are picked up one by one |
no-criteria | No - [ ] line in its body or comments, so verify has nothing to grade | Skips it and keeps the label, since a comment can still add criteria |
body-only-blockers | Its Blocked by section names an issue with no native link | Skips it and keeps the label; the body line is never read as a blocker |
Each skip posts one warning comment saying what to fix, at most once per reason, and appears in the Run summary as skipped #<n> <reason>. Fix the issue and the next Run takes it.
Related pages
- Running: the Frontier a Run drains, and a Run narrowed to named Tickets.
- From Ticket to merge: what happens once a Ticket is claimed, and how verify grades the criteria.
- Configuration: renaming the labels.
- Install and remove:
initcreates the labels and the conventions document.