Herdr Dispatch
Captain protocol for running other coding CLIs as workers inside Herdr. Call the bundled script. Do not improvise raw herdr agent start / wait / prompt sequences.
Preconditions
- Run
scripts/herd-dispatch doctorfirst. It must report"ok": true,"herdr": true, andself.pane_id. - If doctor prints
"ok": falsefor any reason (includingHERDR_ENV!=1, missing verbs, or unresolved captain pane), stop. Show the JSON to the human. Do not debug by running rawherdr agentcommands. - Never target the captain pane. The script refuses self-operations; do not bypass it.
- Never run bare
herdr(that attaches the TUI and hangs). Do not run bareherdr agenteither; kinds come from doctor JSON orherdr agent start --help. - Workers must not dispatch further workers. Only the captain uses this skill.
Tools
All verbs print JSON. Read ok, name, status, deliverable, result_excerpt, error.
status is the Herdr lifecycle: idle, done, working, blocked, or unknown. It is never settled.
ok means this turn of conversation settled, not that the work is good:
assign/ asyncsend:statusisworkinghandoff/collect --wait/send --wait:statusisidleordone
If deliverable is set, that file is the answer. result_excerpt is leftover pane chrome; result.md is the TUI dump. See references/envelope.md.
ok: true with status: unknown is a script bug. Treat it as failure.
If status is blocked or ok is false, do not invent an approval. Show result_excerpt to the human.
| Verb | CAO equivalent | Blocks? | Use |
|---|---|---|---|
assign |
assign | until working |
Fan-out independent work |
handoff |
handoff | until idle/done/blocked |
Next step needs the worker output |
send |
send_message | optional | Follow up an existing worker |
collect |
inbox harvest | optional | Pull assign results later |
models |
— | no | List live model ids for one --kind |
Script path (resolve relative to this skill): scripts/herd-dispatch.
python3 "$SKILL_DIR/scripts/herd-dispatch" doctor
Replace $SKILL_DIR with this skill directory. A herd-dispatch on PATH is also fine.
Assign (async)
python3 scripts/herd-dispatch assign \
--kind codex \
--role implementer \
--name impl-auth \
--worktree \
--message-file .herd-dispatch/jobs/impl-auth/input.md
Returns once the worker is working (not when the task is finished). Then end the captain turn and harvest later.
python3 scripts/herd-dispatch collect --name impl-auth --wait --timeout 900000
collect --wait is the same kind of wait as handoff — only blocking if this captain's command tool can sit that long (see Handoff).
Parallel assign is allowed only when tasks do not share a writable tree. Use --worktree for any implementation.
If assign returns idle/done, the prompt never started. One send retry, then stop.
Handoff (sync)
python3 scripts/herd-dispatch handoff \
--kind claude \
--role reviewer \
--name rev-auth \
--message-file .herd-dispatch/jobs/rev-auth/input.md
The script waits until idle, done, or blocked. That wait is real only when this captain's command tool timeout is ≥ --timeout. Grok Build (and any captain whose shell calls background around two minutes) must not treat handoff as synchronous: assign, end the turn, collect later.
Read deliverable if set, else result_excerpt. Pane result.md is a TUI dump.
Send message
python3 scripts/herd-dispatch send \
--name impl-auth \
--wait \
--message "Address only the reviewer items listed below. Do not expand scope."
--wait makes it a synchronous follow-up: just the message, then the reply. Same tool-timeout rule as Handoff. Without --wait, fire and collect later. If the worker is already blocked, the script refuses unless the human explicitly asked you to --force.
Surprise memory (field guide)
.field-guide/ at the repository root accumulates what workers learn the hard way: model weights are frozen, so only surprises earn an entry — environment quirks, non-obvious invariants, approaches that looked right and failed.
- Point every job brief at the guide files relevant to that job, plus any new or recently corrected entries.
- Ask workers to report surprises in their deliverable. At collect time, transcribe the lasting ones into the guide yourself: short dated entries citing paths or error strings, no opinions. Workers never edit the guide.
- Append only; stale entries get a dated one-line correction, never a rewrite.
The guide belongs to the repository and outlives any job. It needs no protocol to be useful — an unmentioned guide still helps workers that look.
Model
--model is a spawn-time flag on assign / handoff. The script maps it to that kind's native start argument (grok --model, cursor-agent --model, and the same for claude/codex/pi/gemini). send cannot change the model of an already-started worker.
Grok Build is kind grok (aliases: grok-build, grok build). Cursor Agent CLI is kind cursor (alias: cursor-agent). The Cursor IDE binary cursor is not the worker.
When the human names a model, or you need to choose one, list ids first. Do not invent ids. Probe only the kind you are about to spawn:
python3 scripts/herd-dispatch models --kind grok
python3 scripts/herd-dispatch models --kind cursor
Read models[].id and default. Then pass that id on spawn:
python3 scripts/herd-dispatch assign --kind grok --model <id> --name impl-auth --message-file ...
python3 scripts/herd-dispatch assign --kind cursor --model <id> --name impl-cursor --message-file ...
doctor.model_kinds is the set of kinds this probe supports. If ok is false, show the JSON; do not guess. If the kind has no mapping, pass native flags with --args instead of --model. Do not pass both. Cursor catalogs can be hundreds of ids — pick one (or the default) instead of pasting the whole list.
Talking
This is a conversation with another pane. Say what you want in --message or --message-file. There is no required schema.
assign / handoff send that text plus a short worker note (do not spawn peers; answer in the reply). send is only the follow-up message.
After handoff, send --wait, or collect, read deliverable if set, else result_excerpt. Do not paste a multi-thousand-line dump into --message; point at paths.
Routing defaults
| Work | Kind default | Mode |
|---|---|---|
| Implementation, tests, mechanical refactors | codex or pi |
assign if independent, else handoff |
| Architecture, merge decisions, spec | captain (this session) | do not dispatch |
| Review of captain or Codex output | codex, pi, or cursor — not the author |
handoff, or assign then collect |
| Read-only exploration | cheaper kind available on PATH | assign |
Trivial local commands (git status, one-file reads) |
captain | do not dispatch unless the human is testing dispatch |
Override --kind when the user names a CLI. grok and cursor are first-class when they appear in doctor's kinds array. Inspect that array; do not assume every machine has every CLI.
Do not default --kind grok for a worker. Grok's first prompt often never starts (idle / unknown, agent_prompt_stalled). Spawn grok only when the human named that CLI. Grok as this captain is fine.
Status handling
workingafter assign — normal. Do not busy-poll more than once per collect.idle/done— readdeliverableif set, elseresult_excerpt. Scriptokonly means they finished talking.blocked— human gate. Never send y or enter to dismiss approvals.- Start or prompt failure (
okfalse, still idle) —status --name, then onesendretry. If still dead,stop --name --yesonly for workers this captain created, and pick a different kind from the routing defaults instead of respawning the same one.
Cleanup
Leave workers up when the human may inspect panes. Stop only workers you spawned:
python3 scripts/herd-dispatch stop --name impl-auth --yes
Never herdr server stop. Never close the captain pane.
Placement
assign / handoff never split the captain pane.
- If
--nameis already a live agent, the script refuses. Usesend --nameinstead of spawning a second pane. - The first worker in this cwd for this captain gets
tab create --workspace <captain workspace> --cwd <work> --label herd --no-focus. The agent starts on.result.root_pane.pane_id. - Later workers tile on that owned tab with
pane split <id> --no-focus(explicit pane id, never--current). Overflow is geometric: if a split would leave a child below 40×12, or the tab already has 4 panes, the script createsherd-2(thenherd-3, …). - Ownership is the tab id stored under
.herd-dispatch/index.jsonlayoutskeyed byworkspace_id:captain_pane_id. Do not discover tabs by label — humans rename them, and another captain may also have a tab calledherd. - A stale pointer (tab closed, or no panes left) degrades to a new
tab create. The script does not fall back to splitting the captain. - Always
--no-focus.pane renameto the job name is best-effort. Failed starts close only the pane created for that attempt. Idle workers and their tabs stay up. - Do not steal empty shells from other captains. Reuse only panes recorded in this captain's layout entry.
Efficiency
- Dispatch every independent assign first, then end the turn, then collect.
- One worker per deliverable. Do not stack nested captains.
- Prefer
--message-fileplus--worktreeover huge pasted context. - Cap live workers. If more than ten are
working, collect or stop before spawning. - After collect, summarize from
deliverableif set, elseresult_excerpt. - If this captain cannot wait (see Handoff), every long job is assign then later collect.
- Judge jobs by deliverables and verification results, not by activity; busywork is not progress.
References
- What the worker sees, and how to read the reply — references/envelope.md
- Errors and recovery — references/errors.md
- Install onto Claude / Codex / Pi / Grok / Cursor — references/install.md