Written from a run that happened — 33 sessions, 22 verified reports, 0 overclaims 2026-08-01
One session drives. The rest you can watch.
Subagents you cannot see. Sessions you can. The coordinator pattern is the second one, run deliberately.
YOU TALK TO ONE THREAD · IT DRIVES THE OTHERS
docs.propflowai.coTHE COORDINATOR
02 /SHAPE
Three roles, and you only speak to one of them
Workers do the work. One thread drives. You decide.
WORKERS
One goal each
Real sessions in the Agent view — own worktree, own context, resumable. You can click into any of them and read the actual reasoning, not a summary of it.
COORDINATOR
Drives and validates
Holds the ledger. Asks for status, checks the answer against reality, chases what's open, and surfaces anything that needs you — by name, with the link.
YOU
Decisions only
Money, customers, destructive, capability-arming. The coordinator is built to run unattended up to that line and stop dead at it.
The point is not automation — it is that you can look. A worker that goes wrong is a session you open and read. That is the whole reason to pay the coordination cost instead of firing off invisible subagents.
workers → coordinator → you02
03 /SHAPE
Both are useful — they fail differently
A subagent returns an answer. A session leaves a record.
Subagent
Cheap, invisible, gone
Perfect for "sweep 40 files, tell me the one answer." You pay almost nothing in context and keep the conclusion.
But: you cannot watch it, cannot resume it, and when it is confidently wrong you have no transcript to catch it with.
Session
Costly, visible, durable
Survives compaction, keeps its worktree, and can be opened and read turn by turn.
Today that mattered: four workers finished real work and their reports never arrived. Every one was recovered by reading their transcripts. A subagent's would have been gone.
Use both, nested. A worker that hits something hard should spawn its own subagents rather than grind — and the coordinator should say so out loud when it sees one struggling, because the worker rarely asks.
visible where it matters, cheap where it doesn't03
04 /DONE
The single rule the whole pattern rests on
"It said it was done" and "it is done" are two measurements.
REPORTS VERIFIED
22
each against live gh
OVERCLAIMS
0
not one, all day
FINISHED SILENTLY
4
done, and told nobody
SELF-CORRECTED
6
retracted their own claims
Zero overclaims is not the same as zero risk. The workers were honest — the transport lost four reports, and roster status lied about two more. Had the coordinator trusted "no news," it would have called finished work abandoned and restarted it onto shipped code.
verify with a different instrument than the claim04
05 /DONE
Four instruments, none of them the worker's own word
01 · ASK
Send it the goal, and ask what remains
A worker that says "nothing left" has at least committed to a claim you can then check. Cheapest step, never the last one.
02 · GH
Check every PR and issue it named
One gh pr view per number. ⚠️ Squash-merge defeats --is-ancestor and git diff main — both give confident false negatives on landed work.
03 · READ
Read the tail of its transcript
The ground truth, and it survives everything. Four of today's reports exist only because of this step.
04 · DIFF
Check its worktree for work it never mentioned
One worker left an 18KB test file uncommitted in another session's worktree while reporting the issue unowned. Nothing in its report was false — it just wasn't complete.
Status is not evidence. Roster busy means "the process said it was in a turn" — twice today it meant a session that had ended 27 hours earlier.
ask · gh · transcript · worktree05
06 /COMMS
Two independent bugs ate reports today, both now fixed
Four workers finished and told nobody. Neither was their fault.
Loss path 1 — the busy target
msg exited non-zero
Refusing to type into a running turn is right; exiting and leaving the caller to cope was not. Every worker signs off by messaging the coordinator — and a coordinator is mid-turn almost by definition, because driving is what it is doing.
Fixed: the message parks in an inbox instead of dying.
Loss path 2 — the viewer pane
Delivered to a window, not a process
One worker addressed the coordinator correctly, by full UUID, and msg still typed into claude-god — a claude attach viewer holding 0 tokens.
The same pane swallowed a /compact. Fixed: a proven host now beats an inferred one, and the tool prints how it chose.
The rule that outlives both bugs: a coordinator must never read "no news" as "not finished." Silence is the one signal that means nothing at all.
agent-smith #160 · #161 · #16306
07 /LOOP
Why a coordinator stops too early, and how to stop it stopping
A loop is only as good as its definition of "work left".
/goal — soft, per-turn
+
Stop hook — hard, persists
+
five releases
=
gate
The bug that proves the point
The first gate measured only the message queue. The moment the last message was delivered it announced "a stop here is a real finish" — with two PRs open and obvious work left. It answered are there undelivered messages, not is the work done.
The fix
Track the actual work — PRs are now checked for actionable state, not merely open: red means fix, green-and-mergeable means merge, conflicts mean rebase. Checks still running stays a release — block to force work, never to force patience.
gate answers "am I actually being held, and why". ARMED and HOLDING are different states, and only one of them means work continues.
a gate that only blocks is a trap07
08 /FAN-OUT
The coordinator should say this before you have to
"This looks hard — spawn help" is the coordinator's job, not yours.
A worker deep in a problem is the last one to notice it should have split the work. It has context, momentum, and a plausible next step. The coordinator sees only the shape: three hours, no PR, the same file.
FAN OUT
Breadth, or independence
Many files to sweep · several approaches worth comparing · a claim that wants an adversarial second opinion · anything you'd undo with git checkout.
DON'T
Depth that needs one head
A single tangled call chain. Splitting it produces two partial mental models and a reconciliation problem — which is exactly what duplicate sessions already cost us.
NEST
Workers get subagents too
A worker's subagents cost the coordinator nothing and stay inside that worker's context. This is the cheapest depth in the system — use it before adding another session.
the struggling worker never asks — the coordinator must08
09 /HYGIENE
Measured, not asserted
Two workers on one goal produce two coherent, conflicting answers.
What it cost
Twin A shipped analysis #5164. Twin B shipped #5166. They contradicted each other. #5230 exists only to reconcile them — its title says exactly that.
Neither twin was wrong. Neither touched the other's files. Git had nothing to complain about.
What the coordinator must do
✓ One goal, one session, one worktree ✓ An explicit do-not list per worker ✓ Dedupe the roster by name before dispatching — six duplicate pairs were live at once ✓ Treat a fork of analysis as a decision worth naming, not one to silently pick a winner from
The duplicate is never the one you are talking to. Every pair had 1,000–4,000 entries on both halves. Neither could see the waste — it was visible only from outside both.
the failure mode is agreement-shaped, not conflict-shaped09
10 /CONTEXT
The quiet reason this pattern beats one big session
You are not compacting one context. You are compacting the weakest one.
When the coordinator compacts, the workers still hold their detail. When a worker compacts, the coordinator still holds the ledger. The load is spread, so no single compaction loses the run.
Coordinator keeps
The ledger, the decisions, who owns what, and what you actually want — the part worth carrying across every compaction.
Workers keep
File-level detail, failed approaches, the reasoning behind a choice. Recoverable from their transcripts even if they compact badly.
Disk keeps
The ledger file, the queue, the archives. Context is a cache; the file is the record. Compact carrying pointers, not prose.
Name what must survive./compact keep: the two-gate design, address by full UUID, Gera's open decisions — specific nouns survive. "Keep the important stuff" has no referent.
no single compaction should be able to lose the run10
11 /THE LINE
The test is reversibility, never difficulty
The coordinator runs unattended up to this line, and stops dead at it.
Goes to you, always
✗ Touches money ✗ Changes what a customer is charged or sent ✗ Destructive or irreversible ✗ Arms a capability
Parked refusing-by-default until you answer — not paused, not assumed.
It works, unprompted
✓ A worker refused to un-draft a PR to unblock CI, because a green re-review would have auto-merged a diff that reports an undelivered letter as sent ✓ Another refused to merge two green PRs that were not its work ✓ A third flagged an issue's owner before opening a PR on it
None of those needed a human to catch them. The line holds because workers are told the rule and it is worth more than their throughput — which is the coordinator's job to keep saying.
reversibility is the test11
12 /RUNBOOK
What you actually do
Six steps, and you are in exactly one of them.
01 · YOU
Spin up a worker per task, then a coordinator
One goal each, own worktree. Name them for scope, never for version — final-touch-budget, not delinquency-v7.
02 · YOU
Tell the coordinator who it owns
By full UUID. Names collide — two live sales-v2 did, and an 8-char prefix matched 89 transcripts.
03
It dispatches, then chases
Messages park if a worker is mid-turn, so nothing is lost to timing.
04
It validates every claim before believing it
gh, transcript, worktree. Anything unverifiable is logged as unverifiable, never as done.
05 · YOU
It surfaces what needs you — you click in and look
Question, decision, or a worker stuck. You get the session name and the link; you read the real thread, not a summary.
06
The gate refuses to let it stop while work is actionable
And releases loudly, with a reason, the moment waiting is the only move left.
you are in steps 1, 2 and 512
13 /GAPS
Where this pattern is still thin — stated, not hidden
Four things that will bite before they get fixed.
01
The gate only knows work it was told about
It reads a queue and a PR list. Sixteen unowned issues are invisible to it, so when those are all that remain it will still say allow. A loop is only as good as its work list.
02
Noise scales with worker count
One thread carrying twelve conversations is genuinely harder to read than one. This is a real cost, not a rough edge — the answer is fewer, better-scoped workers, not a cleverer coordinator.
03
Roster status lies, and nothing warns you
busy meant "ended 27 hours ago" twice today. Validation has to reach past the roster every time.
04
The main context is bloated before the first turn
Tool schemas and an unoptimised CLAUDE.md cost the coordinator context it should be spending on you. Next piece of work.
stated so the next run does not rediscover them13
14 /THE CHILD
A coordinator that has to babysit five children can really run one
Children write. God polls. Nobody interrupts.
01 · THE RULE
A message typed into the coordinator’s terminal arrives mid-turn
One child doing that is a nudge; twenty is a coordinator that cannot hold a thought — and the failure is invisible, because each individual message looked reasonable. So msgparks into an inbox drained on the coordinator’s schedule. Write freely; never expect a reply.
02 · AUTONOMY
--permission-mode auto is what makes a child autonomous
The default is manual: a session spawned without the flag stops at the first permission prompt — forever, silently, looking exactly like thinking. Two children stalled that way on day one; one sat on a single Bash approval while its coordinator believed it was working.
03 · SELF-LOOP
A child that cannot wake itself stops silently
It arms its own gate and its own idle nudge, on its own session id. ⚠️ The shared marker holds ONE id — a child that writes its own takes the loop from its coordinator. A loop you took from your parent is not a loop you own.
04 · ESCALATION
STUCK requires a failed Fable attempt first
A second model with fresh eyes resolves most walls, and “I tried, and it also failed” is what turns a mood into a fact a human can act on. Then keep the loop armed and go work on something else — blocking on a reply is how a child becomes a stalled process nobody can see.
It works. Four children, four PRs — a CI lane split, a docs-link ratchet, an eval re-pin traced to the exact commit that broke it, and a tour calendar that refused to invent a date for a prod row whose day and time are both literally <UNKNOWN>.
the skill: ~/.claude/skills/child/SKILL.md14
15 /HAZARDS
Every one of these cost a session before it became a line in the skill
Four ways a fleet quietly stops working.
Shared state is a shared fate
The stop command was machine-wide, and the skill told children to run it when they finish. A child succeeding disarmed its coordinator. Scope every shared verb to the caller — and the same held for the registry file a finishing child cleared, telling the coordinator its whole queue was done.
No worktree, no isolation
A child in a single-checkout repo owns the working directory. Two processes writing the same files is not a merge conflict — it is last-write-wins, silently. Decide who owns the checkout before spawning.
Never fight a TUI with send-keys
Fixing permission mode after the fact means typing into an interface you cannot reliably read. A bare 2 meant for a menu landed in a text field and corrupted the dialog. Kill and respawn.
A child that never started reports busy forever. One sat 43 minutes with a transcript containing exactly one entry — the kickoff prompt — while the coordinator reported downstream work as “blocked on the child”. The block was fiction. Liveness is the transcript’s last write, never the status field; the process being alive proves only that it is alive.
arrow keys do not register agent view — they navigate15
16 /CLOSE
The argument in one line
The coordinator earns its cost the day a worker is confidently wrong.
Today none of them were. Twenty-two reports, zero overclaims, six sessions retracting their own claims unprompted. That is the good case — and it still took four transcript recoveries, two transport fixes and a rebuilt stop-predicate to see clearly.
What you get
One thread that knows what you want, holds the ledger across compaction, and can be asked "where is everything" and answer from evidence rather than memory.
What it costs
Coordination overhead, a noisier main thread, and the discipline of never accepting a claim from the thing that made it.
Silence is not a signal. Status is not evidence. A name is not an identity. Every one of those cost something real today, and every one is now a rule the coordinator runs on.