Skip to content

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 needsWhy
The ready-for-agent labelIt is how a Run finds the Ticket
No assigneeAnyone assigned has claimed it
Acceptance Criteria: - [ ] lines in the body or any commentThey are the only thing the verify Stage grades
Blockers as native blocked by links, all closedThey decide which Tickets may run, and in what order
No native sub-issuesAn 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:

SkillProduces
grillingA plan whose questions you, not an agent, have answered
to-specA Spec: one parent issue describing a whole feature
to-ticketsTickets: native sub-issues of the Spec, each one agent session of work, labelled ready-for-agent
triageTickets 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 the ready-for-agent label
  • every issue a body names under Blocked by is a native link too (Blockers)
  • the Spec carries no ready-for-agent: to-spec puts it on, and the Spec is skipped as no-criteria until its Tickets exist, when the spec guard takes it off

To file one by hand with its parent and blockers in place:

bash
gh issue create --title "Drain the Frontier" --parent 12 --blocked-by 14,15 --label ready-for-agent

Acceptance 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.

markdown
- [ ] `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 bullet

The 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.

bash
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 off

A 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 asWhere the section ends
Blocked by: #14, #15 on one lineAt the end of that line
A ## Blocked by heading followed by a listAt 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.

LabelPut on byWhat the pipeline does with it
needs-triageHumans; the pipeline on its standing Notes issueNothing, except opening the Notes issue with it
needs-infoHumansNothing
ready-for-agentPlanning; a Release; a human handing a Ticket backTakes the issue as a candidate. Removed at the claim
in-progressOnly a Run, at the claimMarks a Ticket a Run holds. Removed at merge, Hand-off and Release
ready-for-humanA Hand-offLeaves the Ticket alone until a human relabels it
wontfixHumansNothing

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:

ReasonWhat the candidate didWhat the pipeline does
specIt has native sub-issues, so it is a SpecSkips it and removes ready-for-agent: its Tickets are picked up one by one
no-criteriaNo - [ ] line in its body or comments, so verify has nothing to gradeSkips it and keeps the label, since a comment can still add criteria
body-only-blockersIts Blocked by section names an issue with no native linkSkips 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.

References ​