Running a phase
Everything between starting a phase and the one PR it ends with: where each member's branch is cut, what advances the run and what stops it, how a restarted board picks the phase back up, and what merging it does to the cards it carried.
Running a phase works its list into a single integration branch. Starting
one cuts phase/<task-stem> from the newest origin/main it can see — the
same rule and the same timeout a task branch is cut by — and gives it a
worktree beside the task worktrees. From there each member is branched
from the phase's tip, run headless exactly as ▸ start work runs any
card, and merged back into the phase branch when its checks are green;
then the next one starts. At the end one PR, from the phase branch into
main, for a human.
That is why the branch exists. Members of a phase are related by
definition, so card two branched from main could not see card one's work
while card one sat unmerged in review/ — it would conflict, or quietly
build the same thing twice. Gating on a merge into main would fix the
branch point and destroy the point, because main is merged by a person
and the phase would stall on every card. So the human gate moves from every
card to the phase boundary, and the promise survives intact: the board
merges into a branch it created, inside a scope you opened, and main
still waits for your click.
The runner is a beat, not an agent. Everything it decides is already
structured state — a card's stage, a PR's CI verdict, whether one branch is
contained in another — so an agent paid to poll would be the wrong tool at
the wrong price. It is a plain thread (BOARD_PHASE_INTERVAL, 30s), silent
when no phase is running.
The beat is stateless. Each pass recomputes which members are finished,
which is first unfinished and what that one needs; it holds no registry of
where a phase is. Two durable things carry the memory instead, and both
are things the board already writes: git, where a member is finished
when its branch is contained in the phase branch, and the phase card,
which grows a ## Phase log section the runner adds one line to per
decision — a run started, a member started, a member merged, a halt. The
log is the record a person reads, and the only thing that can tell "this
member has run and it ended badly" from "the phase has not reached it yet";
without it a restarted board would relaunch a run that died. So a restart
resumes a phase by looking, and the same logic answers "what now?" whether
the last event was a launch, a merge or a crash.
Advance on green. A member is finished when its card reaches review/
and its checks are not against it. Green is read from the same PR poll the
board already runs: red halts the phase, running holds it, and a member
with no checks at all advances — a project without CI must not deadlock
every phase it runs. A member with no branch at all that is already in
review/ or done/ is simply finished; there is nothing to bring.
Halt, never skip. Five conditions stop a phase, each already a visible
state on the card: a member that exits NOT READY, a run that exits
non-zero, a clean exit that committed nothing, CI red, and a merge into the
phase branch that is not mechanical. A phase that stepped over a failed
card would build the rest on a foundation that never landed. The halt is
written into the log and then held — said once, not once a beat — and
nothing retries by itself. Running the phase again is a person's decision,
and it is what appends the line that clears the halt. A member whose
**Depends on:** names something unfinished is a wait, not a halt: the
phase idles until the dependency lands (merged, for a card inside the
phase; done/, for one outside).
Merges are additive, always. Nothing here rebases and nothing
force-pushes. main is merged into the phase branch on every beat, so a
phase that runs for hours does not drift into one enormous conflict at the
end; a conflict there halts the phase like any other, aborted cleanly, with
the colliding files named. When every member is in, the branch is pushed, a
PR into main is opened with the member list as its body, the **PR:**
line is written into the phase card and the card moves to review/ — where
the existing apparatus applies unchanged: the CI chip, ◔ review PR,
⚑ copilot, and drag-to-done/ for merge & clean up.
One board runs it. State syncs; reactions don't, so the phase card's Assignee is where "who runs it" is written down — the same claim that gates starting work. A replica renders the phase and advances nothing.
Members run one at a time. Running independent members in parallel is a separate card.
Watching one run, and watching it stop
A phase card is a card, so the PR chip, CI, the review actions and the merge-and-clean-up sheet all reach it by inheritance. Three things do not, and they are the phase's own interface:
- ▸ run phase, on an
in-progress/phase card, in the slot ▸ start work occupies on an ordinary one — arming and firing like every other launch, and reading ▸ take over on a card someone else holds. Moving the card toin-progress/stays the commitment; this is the second half of it. ‖ hold replaces it while the phase runs and means what it means everywhere else: the run stops and the member agent in flight stops with it, while the phase branch, every card already merged into it and every worktree are left exactly as they were. A hold is not an undo, and a held member's card keeps its own work — so running the phase again may halt on it, which is the honest reading of a run that ended without reachingreview/. - A chip in the header while a phase runs, beside the agents chip and
present on the same terms as the sync chip — only when there is
something to say. It breathes in
--accentand names the phase, how far it has got and the card in flight:⟶ Ship the site · 2/5 · on #33. Progress is what has landed on the phase branch, which is a different fact from which card is running, so both are there. Two phases could in principle run on one board, and two phases get two chips — a chip that silently showed one of them would be worse than none. - The halt, in
--alarm, holding.⟶ Ship the site · halted at #35 — it is not readystays in the header until the phase is run again or held, rather than scrolling away; a toast fires with it, because a halt is rare and actionable; and the ticker keeps the line. The member card wears its own failure independently — a phase you started and stopped watching is only trustworthy if its halt is impossible to miss, so it is told three times at three altitudes, exactly as a dead run is.
And the card itself says which state it is in. The header chip only
appears while there is something to say, so it cannot tell "the phase has
not been started" from "there is no phase here at all" — which left an
in-progress/ phase card nobody had run looking exactly like one mid-run.
So the card carries the distinction quietly, in the pill and the line
under it: not started (▸ run phase cuts its branch and starts the
first card), held, the accent and the member in flight while it runs,
and halted in --alarm. Only a run wears the working vocabulary — the
breathing mark, the accent border, the caret — because only a run is work
happening.
And the card does not move while its work runs. A phase card stands
for cards the Board no longer draws, so dragging it to another stage — or
onto the archive tray, which is a move like any other — while a member has
an agent in it would leave the card in one place and a live run, a
worktree and a branch in another. That move is refused, and the refusal
names the way through rather than the wall: which member is working, and
that ‖ hold stops the phase and the agent it has in flight while
leaving the phase branch, everything merged into it and every worktree
exactly as they are. The card stays where it was and the reason is in the
toast. The answer is the server's — /api/move, /api/archive and
merge-and-clean-up all ask it, so a stale page cannot get past a rule the
drag alone would enforce. What is refused is narrow on purpose: a phase
between members has nothing in flight and moves freely, a halted one has
nothing running by construction and moves freely — which is exactly when
walking it back is the thing to do — and "running" is read from the runs
themselves rather than from what the log says was once started, so a
member's run that died cannot lock its phase card. An ordinary card never
reaches the question.
Every advance is narrated in the ticker as it happens: the member that came up green, the merge into the phase branch, the member starting next. And the phase card's sheet lists its members in run order with each one's stage — and, while a phase is in flight, the runner's own reading of each (merged in, working, checking, stopped here) — so the card answers "where is this up to" without a hunt across five columns.
Finishing a phase finishes its cards
A member stops at review/ on purpose: done/ has always meant merged
into main, and merged into a phase branch is not that. Merge & clean
up on the phase card is the moment it becomes that. The phase's PR goes
into main, so every card the phase carried is in main too — and since
a phase reaching done/ releases its members back onto the Board, the
alternative is handing back three or five cards you have already judged,
in the column whose note is "your move".
So the merge sweeps. After it has actually succeeded — never before, and
never if it conflicts — every member the phase merged moves to done/,
and each one's workspace is cleared exactly as completing an ordinary card
clears its own: the worktree removed, the local branch deleted, the branch
on the remote deleted. That half is not cosmetic. A stale worktree is a
trap laid for whoever reopens the card, since a work launch refuses a card
whose worktree already exists, and the accumulation is per member per
phase.
Four rules keep it honest:
- Only what the phase merged. A member is swept when its card settled
into
review/ordone/and its branch is contained in the phase branch — the same pair the runner reads a member asmergedby, and a member with no branch at all was finished before the phase reached it. One that halted, was held or was walked back is neither moved nor cleared: there is work in its worktree and its branch, and it is the reason a person will look at this phase afterwards. - Nothing uncommitted is thrown away. A member's worktree comes out
without
--force, so one with uncommitted changes in it is reported in the ticker and kept, with its branch. Removing a worktree with work in it is the one unrecoverable step in the ending. - One ending, told once. The cards move together in a single commit
naming all of them (
board: 47, 52 → done with phase 53), and the ticker gets one line — the phase, how many cards went with it, and how many it never merged and left where they are — instead of five moves scrolling past. In team mode that commit publishes like any other. - Only merging sweeps. "Just move the card" leaves the work alone,
including the members'. Archiving the phase card, or dropping a number
from its
## Cards, releases the members to the Board in whatever stage they are genuinely in (that is card 56's rule, and it is unchanged) — neither one says anything is done, because neither one puts anything inmain. The members' own PRs are closed by their own merges into the phase branch and are not touched here.