`▸ run phase` guarded its own door and left the neighbour's open:
`/api/agent/start` accepted a phase card, cut `task/<stem>` and handed a
list of other cards to a work agent as a brief. It did that once, and the
agent implemented two cards at once in a worktree nobody was watching.
The refusal is a server rule, in `_validate` with the stage check — ahead
of the claim and well ahead of the worktree, so it costs nothing and
leaves nothing to clean up — and it names ▸ run phase rather than just
saying no. Which headless kinds a phase card may host is now decided kind
by kind where the guard lives: ▸ start work and ↻ act on PR refuse it
(both are work agents), while ◔ still true? and ◔ review PR are allowed —
the latter now told the phase's own branch, since its PR is from
`phase/<stem>` and `task/<stem>` was never cut. The guard is about
starting, so a card retyped under a running agent is left alone.
And the card says which state it is in: an `in-progress/` phase nobody has
started read exactly like one mid-run, the header chip being absent in
both cases. It now wears `not started` (or `held`) in the settled
register, with the line under it saying what ▸ run phase would do — never
the accent, the breathing mark or the caret, which mean work is happening.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Board view stops drawing a phase's members: the phase card stands for them there, and the columns go back to counting what you can see. Nothing is deleted, moved or marked — a member keeps its stage, its file, its agent and its actions.
heldByPhase()/columnCards()/columnNote() in board.html: the count is the length of the list drawn, and '+2 in phases' beside it says where the rest went, only where members are actually hidden. Membership is the only thing that hides a card, so removing it is the un-hiding: an archived phase card and an edited '## Cards' list stop deriving it, and a phase in done/ holds nothing. A card wearing phase drift is never hidden. The phase card carries the summary it now owes: a '1 of 2 merged' chip opening its own sheet, the member in flight on its activity line, and a halt worn in --alarm. Only the Board hides; Sessions, Focus, the header's live chip and the tab title still see an agent working on a hidden member.
Covered by tests/test_phase_members_hidden.py — collect() reads a throwaway tasks/ tree and the page's own rules run over that reading in node, plus source-level wiring invariants. Full suite green.