Delegating to agents without losing the thread
Past two or three workers, the thing that breaks is not the machines. It is the one head holding all of them.
This is what the coordinator skill encodes, so a session acting as router loads it instead of being told. Most of it is not about worktender at all.
npx skills add steig/worktender --skill coordinator -g
Verify, do not relay
Never read a worker's diff. A file read costs two thousand tokens; the check that settles the same question costs ten.
PASS=$(go test -count=1 -v ./... | grep -cE "^--- PASS")
“Did you run this, or reason it?” Bugs that survived a careful argument were caught by execution every time. When a worker explains why something must work, ask what it printed.
Reports drift without anyone lying. Test counts, branch names, “fixed” meaning the mechanism exists but was never exercised. Check the thing, not the sentence about the thing.
What to delegate, and what to keep
Delegate bounded authoring work with a mechanical done condition. If you cannot state the command that proves it finished, groom the slice before handing it out.
Keep. These are not preferences:
- Live or shared state — the running session,
main, the install itself. - Anything needing two slices at once. Merge resolution needs both sides' intent; a worker has one.
- All verification.
Check file overlap before a wave goes out. Two tasks touching one file is one worker's job, or two waves — never two workers.
Make the report a shape, not a sentence
A worker's task usually arrived as an issue, and an issue body is written by anyone who can file one. So a report should be fixed slots: a status from a closed set, a machine-checkable artefact, and a capped note treated as data.
Branch on the status and the artefact, never on the note. A predicate over free text hands whoever wrote that issue the decision of when your next agent starts.
A done is a claim. The report proves something
well-formed arrived, not who composed it or whether the work exists. Check the
artefact it names.
Wait on a signal, not a clock
Never hand-roll a readiness wait. Sleep-polling has failed
with agent_not_ready every time it has been tried here —
including by a coordinator that knew the readiness event existed and
hand-rolled the poll anyway.
A worker reporting blocked is a good outcome. It
releases the wait immediately instead of burning the timeout, and you are the
only party who can unblock it.
The stall you cannot clear
A delegated agent has no human at its terminal. It stalls on the first permission prompt and stays there — and the agent that dispatched it structurally cannot answer. So a worker needs two things settled before the wave: a way to start without asking, and a way to escalate when it genuinely cannot finish.
- Pass the brief inline, never as a file path. A worker asked to read a path stalls on a grant nobody can give.
-
Grant a permission mode before the wave, not after it stalls.
--permission-modepasses straight through, including the modes that stop the agent asking at all — a worker that cannot start is the failure the whole command exists to prevent. worktender warns on stderr rather than refusing, because it cannot sandbox the agent it starts:claudetakes no sandbox flag, and this plugin does not write your agent's configuration. The boundary is yours to provide — a sandbox profile, or a separate uid. -
An allowlist is not that boundary. A worker denied
Bash(herdr agent start:*)reached a live agent anyway through the socket, logging zero denials. Blocking the CLI blocked the convenient path, not the capability. -
blockedis the escalation channel. A worker that hits a real wall reports it, the gate fails immediately with “it will not reach done without you”, and the question lands with the one party who can resolve it. There is no way to answer a worker mid-run — resolve it yourself, then re-dispatch with--resumeto keep its transcript.
Four failure modes
- Agents argue instead of proving.
- The dangerous thing is not where the caution points. Workers have escalated a read-only probe while the next slice would have armed event hooks in a live session.
- Reports drift. The quiet one.
- Knowing better does not prevent the old habit. See the sleep-poll above.
Handoffs
Write one before delegating, and before your own context is cleared. A handoff that gets corrected by the agent receiving it is working.
Hold in your own context: decisions, invariants, what has been verified. Nothing else.
What the coordinating agent runs
herdr worktree create --branch feat/12-thing --base main --no-focus --json
worktender dispatch --pane <pane> --name thing --model sonnet
herdr agent prompt <pane> "$(cat brief.md)"
worktender gate --any thing,other,third --until done --timeout 20m
Never pick one worker to block on. Dispatch returns as soon as
the brief is typed, so nothing tells the four-minute slice from the
forty-minute one. --any waits on all of them and releases on the
first to report, naming it — drop that one and gate again on the rest.
Dispatch, then gate — the order matters. The gate ignores whatever the pane already held; that was a previous task's answer. Dispatch has the rest, including the two channels a report travels over and why neither wins.
Worked examples walks five fan-outs through all of this — and one case where you should not fan out at all.