The orchestration lifecycle: from phase to ticket to retro¶
If you have read the builder's-eye post in this series -- Tickets in, a game I can play out -- you have the user's view of my AI build loop: a feature becomes a ticket, the orchestrator hands back reviewed code, I run the real app and play it, and what I notice becomes the next ticket. That post deliberately stopped at the surface. It said "there are review gates" and "there are run records" and refused to open either box, because the whole point was that you do not need to open them to use the loop.
This post opens one of the boxes. Not the review gates -- those have their own companion post, Review surfaces and the AI-first guardrails, and I am going to keep my hands off them here so the two posts read as a pair instead of two overlapping essays. This post is about the other box: the lifecycle. How a phase of work decomposes into tickets, how a dependency graph schedules those tickets so parallel agents do not corrupt each other's merges, how a single ticket runs through its stages, and how the whole thing closes the loop with a retrospective that feeds lessons back into the system.
I want to be precise about the thesis up front, because it is easy to mistake this for a post about productivity. It is not. The lifecycle I am about to describe is governance, not autonomy. Every part of it exists to make an autonomous run debuggable by a human after the fact. That framing is the load-bearing idea, and it is worth holding onto as the pieces accumulate.
A phase decomposes into tickets, and the decomposition is the design work¶
The unit of planning is a phase -- a chunk of the overview docs build plan, like "the shell slices" or "the Memory Match proof-of-rebuild." A phase is too big to hand to an agent. The first real act of engineering is cutting it into tickets.
A ticket is not a prompt. This is the distinction the whole system is built on. A prompt is a wish typed into a chat box; a ticket is a contract. The template at the workflow docs enumerates what that contract carries, and every field earns its place by removing a specific failure mode:
- A goal and a "why." Not so the agent has nice prose to read, but so it can make judgment calls when the spec turns out to be ambiguous mid-run -- which it always does.
- Scope in and scope out. The single most important field for autonomous work, because scope creep is the number-one way an unsupervised agent goes wrong. "What this change must not touch" is as load-bearing as what it must.
- A
## Touchesblock -- machine-readable path prefixes the ticket is allowed to edit. This is not just documentation; the scheduler reads it (more on that in a moment). - Acceptance criteria an agent can literally tick through, concrete enough that both the agent and I can tell whether each one was met.
- Verification commands -- the exact strings the agent runs to check its own work instead of asking me. As the authoring guide (the ticket-authoring contract) puts it: autonomy lives in this field.
- A "definition of rejected" that tells the agent when to stop -- when to declare the premise wrong and bail, rather than churn for hours down a dead end.
Writing this contract is the skilled part. The authoring guide spends most of its length on one question that has nothing to do with code: is this one ticket or several? Its rule is that independent pieces -- disjoint ## Touches, no shared output -- get their own ticket each, because the moment one agent is editing backend logic and socket code and CSS in a single undifferentiated pass, it cross-wires them, and one bad piece sinks the whole PR even when the rest was correct. "Multi-system is a split signal, not a phasing signal" is the line I keep coming back to. The decomposition is the design.
The agent never sees a phase. It sees one ticket, in a fresh context, with a bounded scope. That is the first governance move: the work is cut down to something a single context can hold and a single review can judge.
The dependency graph is what makes parallel AI work safe¶
Once a phase is a set of tickets, the question becomes: which can run at the same time, and which must wait? Get this wrong and two agents edit overlapping files in parallel clones, both branches go green on their own, and the second merge silently corrupts the first one's work. There is no test that catches "these two correct changes are incompatible" -- by the time you find out, it is a merge already on the primary branch.
Two fields encode the answer. ## Depends on lists the concrete predecessor tickets whose merged output a ticket consumes -- a helper, a fixture format, a logging event, a schema, a doc contract. Only true foundations, the ones that can run against the primary branch with no sibling merged, are allowed to say - none. And ## Touches, the path scope from above, feeds a planner that refuses to put two tickets with overlapping paths in the same parallel bucket.
The crucial part is the project's stated stance when the answer is unclear. From the authoring guide, almost verbatim:
When in doubt, serialize over parallelize. A needless dependency costs a little wall-clock; a missed collision corrupts a merge and routes to human triage with no retry. The asymmetry always favors serializing.
That asymmetry is the single most important idea for anyone running more than one agent at once, so it is worth sitting with. The two failure modes are not symmetric in cost. If I serialize two tickets that could actually have run in parallel, I lose some minutes of wall-clock -- annoying, fully recoverable, invisible by tomorrow. If I parallelize two tickets that actually collide, I get a corrupted merge that no gate detects, that costs an entire run's work to unwind, and that I might not notice until three tickets later. One mistake is cheap and reversible; the other is expensive and silent. When the downside is that lopsided, "I think these probably don't collide" is not good enough. "Probably" is doubt, and doubt serializes.
This is the same shape as the no-auto-kill decision in the spiral-detection post: when two failure modes have wildly asymmetric costs, you design for the expensive one even though it makes the cheap case slower. Conservative serialization leaves parallelism on the table on purpose. It is a deliberate trade of throughput for safety, and it is the right trade precisely because the unsafe failure is the one you cannot cheaply recover from.
One ticket runs through stages, not a single shot¶
A ticket does not get "executed" in one agent call. It moves through a small state machine of stages, and the boundaries between them are not bureaucratic -- each boundary is a fresh agent context with a single job, which is what keeps any one context from conflating jobs that should be kept apart.
The lifecycle, documented in the workflow docs, runs roughly: AUTHORED (the ticket file exists on the primary branch, no run record yet) -> RUNNING (a throwaway worker clone is spawned and an agent is implementing) -> IMPLEMENTED (branch pushed, run record written, no PR) -> REVIEWED -> READY -> PR OPEN -> MERGED -> RETIRED (clone gone, branch deleted, ticket file stays on the primary branch as the permanent record of what was asked).
The dispatcher that drives these transitions is a single script, the orchestrator dispatcher, with one verb per stage -- ticket, phase, review, ship, status, and a handful of others. Each stage is its own executable (you can see them enumerated in the workflow docs): implement clones the project, runs the execution agent under a watchdog, captures the run record, and pushes the branch; review runs the preflight checks and the surface reviews (the box this post is not opening); ship opens the PR; merge acquires a cross-ticket mutex, waits for green CI, and squash-merges; cleanup deletes the worker clone.
Two design choices in here are worth calling out because they are the governance, not just the plumbing.
First, the work happens in a disposable clone, not a long-lived checkout. The clone is a throwaway workstation; GitHub is the single source of truth. The implementing agent is explicitly forbidden from git pull, git rebase, git stash, or touching remotes -- the clone is brand new and already current, so there is nothing to sync, and every one of those commands is a way to corrupt state instead. One branch, one ticket, one PR, then the clone is destroyed.
Second, every stage writes its progress to a .state/ run record that the orchestrator reads. The stage contract is that each stage advances the state first, does its work, then exits zero or non-zero -- so at any moment the orchestrator can answer "where is this run, and is it still alive?" from a file on disk. That is what lets a watchdog supervise a run it did not personally launch, and it is what the spiral detector reads when it decides whether an agent is making progress or just spinning. The stages are small and the state is external on purpose: a long single-shot agent call is a black box, and a black box is not debuggable.
Why a deterministic script, not one big LLM agent¶
The obvious-seeming alternative to all of this is a single capable LLM agent that you hand the phase to and let it spawn sub-agents -- one to implement, one to review, one to open the PR -- coordinating them itself. It is the shape most "autonomous developer" demos take, and it is the single most common question this architecture provokes: why isn't the orchestrator just an agent?
The answer is that the control spine is deliberately not an LLM. The dispatcher (the orchestrator dispatcher) and the stage scripts under the workflow scripts are plain, deterministic bash. They own everything that has to be exactly repeatable: the control flow between stages, the lifecycle state transitions, the disposable-clone lifecycle, the cross-ticket locks and the merge mutex, and the advance-state-first stage contract from the previous section. None of that is delegated to a model. A model is asked to do exactly four things, and nothing else.
LLM agents are invoked only at bounded points, each in a fresh single-job context. Reading straight down the "External commands" rows of the workflow docs, there are exactly four agent roles in the happy path:
- The implement stage runs the execution-role agent -- it writes the code. (Everything else in that stage -- clone, autofix, commit, push -- is bash.)
- The review stage runs the review-role agent (plus an optional lint-fix-loop agent) -- it applies fix-in-place and writes findings. The preflight lint/test wall around it is bash.
- The ship stage runs the ship-role agent -- it crafts the PR title and body from the run record. The
gh pr createcall itself is bash. - The merge stage runs the merge-readiness agent -- it gates the merge by judging whether CI is actually green. Acquiring the mutex and the
gh pr merge --squashare bash.
That is the whole list. Stages that look like they "must" involve intelligence do not: cleanup is an rm -rf of the worker clone via lib-cleanup.sh, no agent; the stage-to-stage transitions are a bash loop, no agent; the dependency scheduling is a planner reading ## Touches strings, no agent. An LLM is invited in for a bounded act of authorship, then the deterministic machinery takes the result and moves on.
Why draw the line there instead of letting one agent run the show? Every reason is the same governance idea from the top of this post -- make an autonomous run debuggable by a human after the fact -- applied to control flow:
- Determinism and auditability. A bash dispatcher does the same thing every time, and every transition it makes is a line in a script you can read. "Why did the run go from review to ship?" has a code answer, not a vibes answer. An agent orchestrating itself gives you a transcript, not a control-flow guarantee.
- State lives on disk, so restart is idempotent and resumable. Because the spine advances a
.state/record before doing the work, a run can be killed and resumed, and a watchdog can supervise a run it did not launch -- it just reads the file. An agent holding the orchestration state in its own context is a black box: kill it and the state dies with it. - A per-stage audit boundary. Each stage is a fresh context with one job, so a failure is localized to a stage and a run record, not smeared across one long opaque session. The boundary is where the audit trail attaches.
- No recursive, opaque autonomy. "An agent that spawns agents" has no natural floor and no external place to stand and watch it. The cost asymmetry that governs the rest of this system -- a cheap recoverable mistake versus an expensive silent one -- argues for putting the irreversible decisions (what merges, what gets deleted, what runs next) in code you can read, and reserving the model for the reversible, reviewable act of writing a diff.
The short version: the script is in charge precisely because it is dumb and legible, and the model is on a leash precisely because it is powerful and opaque. The orchestrator is not an agent; it is the harness an agent runs inside.
Reading the two charts: the legend¶
The two flowcharts below use one consistent visual convention:
- Plain rectangles are deterministic-script steps -- bash in the dispatcher or a stage script. No model is involved.
- Stadium-shaped, dashed-outline nodes (the
agentclass) are LLM-agent steps -- the four bounded invocations named above.
Every node is one or the other. If you want the one-glance takeaway from this whole post, it is the ratio of rectangles to stadiums in these charts.
Chart 1 -- the whole flow, ticket start to PR merge¶
The happy path is the spine (top to bottom); the dotted edges are the load-bearing error cases. Stage names and lifecycle states are annotated on each node to match STAGES.md and TICKET_LIFECYCLE.md.
```mermaid
flowchart TD
A[draft stage: dispatcher authors ticket files
state: AUTHORED] --> B[implement stage: clone project + spawn under watchdog
state: RUNNING]
B --> AG1([implement agent: writes the code]):::agent
AG1 --> C[implement stage: autofix + commit + push branch
state: IMPLEMENTED]
C --> D[review stage: run preflight lint/test]
D --> AG2([review agent: fix-in-place + findings]):::agent
AG2 --> E[review stage: commit findings
state: REVIEWED]
E --> F[followup: escalations cleared
state: READY]
F --> AG3([ship agent: crafts PR title/body]):::agent
AG3 --> G[ship stage: gh pr create
state: PR OPEN]
G --> AG4([merge-readiness agent: judges CI green]):::agent
AG4 --> H[merge stage: acquire mutex + gh pr merge --squash
state: MERGED]
H --> I[cleanup stage: rm -rf worker clone
state: RETIRED]
B -. implement agent timeout .-> DIAG[diagnose stage]
DIAG -. retry within cap .-> B
DIAG -. escalation .-> HT[human triage]
D -. review preflight failure .-> NH[NEEDS-HUMAN]
NH --> HT
H -. merge conflict / CI not green .-> HT
classDef agent fill:#ffffff,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5;
Notice the shape of it: of the nodes on the spine, only four are stadiums. The agent writes, reviews, titles, and judges-CI; the script does everything between and around those four acts, including every transition and every irreversible operation.
Chart 2 -- zoomed in on the stages that branch¶
Most stages are a straight line. Three have real decision logic worth opening up -- implement, review, and merge -- and the same legend applies: rectangles are bash, stadiums are agents.
```mermaid flowchart TD subgraph IMPLEMENT I1[clone project + advance state] --> I2([implement agent under watchdog]):::agent I2 --> I3{autofix format + lint --fix ok?} I3 -- yes --> I4[fold autofix into commit + push] I3 -- no --> I5[fail: implement-autofix-failed] end subgraph REVIEW R1[run preflight: lint / test] --> R2{preflight pass?} R2 -- lint fails --> R3[deterministic lint fix-loop] R3 --> R3b{pass after fix-loop?} R3b -- no --> R6[NEEDS-HUMAN] R3b -- yes --> R4 R2 -- pass --> R4([review agent: per-surface rule packs]):::agent R4 --> R5{escalations open?} R5 -- yes --> R6 R5 -- no --> R7[READY] end subgraph MERGE M1[acquire cross-ticket merge mutex] --> M2([merge-readiness agent: poll CI]):::agent M2 --> M3{CI green within cap?} M3 -- yes --> M4[gh pr merge --squash --delete-branch] M3 -- no --> M5[park PR --> human triage] end I4 --> R1 R7 --> M1
classDef agent fill:#ffffff,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5;
The pattern repeats at every zoom level: the agent is a single node bracketed by deterministic gates. In implement the bash autofix decides whether the commit lands; in review the bash preflight and fix-loop decide whether the agent even runs, and a bash check of the escalation list decides READY versus NEEDS-HUMAN; in merge the bash mutex and the squash-merge bracket the one agent call that judges CI. The intelligence is bounded on both sides by code you can read.
Run records are the memory¶
Every ticket emits a *.run.md sibling next to its ticket file -- the format is specified in the run-record contract -- and this is the part of the system I would least want to give up.
The run record is where the agent writes down what the code itself cannot tell you: the surprises (spec gaps, rules that turned out ambiguous), the dead ends (what it tried first and why it failed -- a negative-result log), the judgment calls it had to make where the ticket was silent, the outcome (merged, escalated, completed-no-changes, abandoned -- a fixed vocabulary, not free text), and the wall-clock the run actually took.
Three things make this more than a diary.
It is co-located and greppable. The run record is a sibling of the ticket in the same directory, so "is this ticket shipped, and what happened when it ran?" is answerable by listing one folder, and a question like "what did past tickets that touched this module run into?" is a search across *.run.md files. (The co-location is also a dodge around a real constraint -- Claude Code hard-rails writes under the local agent configuration, so the run record lives next to the ticket instead of in some runs directory. A workaround that turned into a better design.)
It is the per-ticket audit trail that makes an autonomous run debuggable. When I come back in six weeks and wonder why a thing was built the way it was, the answer is on disk in the dead-ends and judgment-calls sections, not in a chat window I closed. An autonomous system you cannot retrospect on is a system you cannot trust; the run record is what you retrospect on.
And it is calibration data. The wall-clock estimates feed the time-budget buckets the orchestrator uses to decide when a run has gone from "healthy" to "soft band" to "stuck." The early tickets in this project carried time budgets anchored on human engineer hours and were 10-20x too high; the run records are what let us recalibrate those numbers to observed agent wall-clock. The memory does not just explain the past -- it tunes the supervision of the future.
The retro closes the loop¶
Per-ticket run records are local memory. The retrospective is where local memory becomes a change to the system itself, at the phase or batch level.
After all the tickets in a phase or a feedback cluster merge, a retro ticket runs last (its templates are the workflow docs and the workflow docs). Its job is not to write code. Its job is to read every sibling run record's lessons and review findings, read the merged PRs, and synthesize the cross-ticket patterns into process changes: an edit to a project agent rule, a new entry in the ticket template, a new review rule, a behavioral skill. As the phase-retro template puts it, the retro is "the one place where cross-ticket patterns become process changes." Without it, per-ticket lessons stay trapped in individual run records and quietly drop between cycles.
The spiral-detection watchdog is the cleanest example of this loop producing a durable guardrail. It exists because runs spiralled -- an agent burned thirty minutes debugging a fixture that had been correct since minute five -- and those spirals were visible in the run records and the recorded run corpus. The retro-shaped response was not "tell agents to try harder." It was a new mechanical signal added to the detector after a specific 90-minute test-loop spiral, plus a cluster of CLAUDE.md rules and skills aimed at the causes of spirals (ban the throwaway-script habit, keep temporary files where the detector can see them, stop agents hunting the filesystem for binaries). A failure mode observed in run records became a detector signal and a set of rules. That is the loop closing: phase -> tickets -> run records -> retro -> a changed system that the next phase's tickets run inside.
The honest edges¶
I would be selling you something if I stopped at "and then the loop closes and everything compounds." Here is where the lifecycle costs you.
Ticket authoring is itself skilled work, and it does not disappear. The system moves the labor of writing the code off my plate, but it moves the labor of writing the contract onto it -- and writing a good ticket (the right split, an honest scope, a real definition of rejected, a correct dependency edge) is genuinely hard. A vague ticket produces a clean, gated, well-recorded implementation of the wrong thing. The orchestrator guarantees the code matches the ticket; it cannot guarantee the ticket matches reality. That judgment never leaves the human.
Conservative serialization leaves throughput on the table, on purpose. The "serialize when in doubt" rule is the right call given the cost asymmetry, but it means the system is, by design, slower than a perfectly-scheduled version of itself would be. Every dependency edge I add out of caution is wall-clock I am choosing to spend to avoid a corruption I cannot cheaply undo. That is a real cost; I am just convinced it is the cheaper one.
The whole thing still assumes a human in the loop. The watchdog does not auto-kill -- it pages me and I decide. Escalated tickets route to human triage, not to an automatic retry. The retro proposes process changes; a person still reviews them. This is the point I most want to land: what I have described is not a self-driving development system. It is an instrumented one. Every stage, every run record, every dependency edge exists so that when something goes wrong -- and it does -- a human can see exactly where, and why, and decide what to do. The lifecycle buys you coherence across many autonomous tickets. It buys that coherence with governance, and governance has a human at the end of it.
That is the lifecycle: a phase cut into contract-shaped tickets, scheduled by a dependency graph that errs toward safety, each ticket run through small staged hops in a disposable clone, every run leaving a greppable memory behind, and a retro that turns those memories into changes to the system itself. If you came from the builder's-eye post, this is the machine under the loop you were driving. And if you want the other half of the machine -- the review preflight, the lint wall, and the AI-first guardrails that make agent output reviewable in the first place -- that is the companion deep-dive, Review surfaces and the AI-first guardrails, which opens the box this post left closed.
Or: subscribe for the companion post on the review surfaces and AI-first guardrails.