Skip to content

shunk031-herdr-orchestrate-workers

Orchestrate and coordinate coding workers in Herdr by spawning parallel Codex workers in git worktree tabs, tracking status, reconciling worker state before waiting, validating reports against raw results, processing queued reports before answering the user, correlating matching failures across streams, checking whether a constraint came from the user instruction, worker prompt, or orchestrator addition using transcript evidence, and routing task, review, and pull-request lifecycles. Use when a request mentions Herdr and asks to orchestrate, coordinate, delegate, route, reconcile state before waiting, process queued reports, correlate matching failures, explain a constraint's source, or validate worker reports, or when a prompt says you are a Herdr worker reporting to an orchestrator. Exclude single-pane or single-agent Herdr control, pane-output questions, general CLI questions, and parallel work or git worktrees without Herdr.

No evaluation has been recorded for this skill yet.

The skill

[!NOTE] After reading this SKILL.md, say: 🐑 I read shunk031-herdr-orchestrate-workers.

Orchestrate Herdr Workers

An orchestrator delegates independent tasks to worker agents, one git worktree tab each. Workers must report back by prompting the orchestrator for attribution and audit, but completion is collected from Herdr lifecycle state and the worker's deliverable and transcript. Read this skill file and the herdr skill first: the herdr skill owns all CLI mechanics, JSON responses, ID handling, and safety rules. This skill adds only the orchestration protocol. When one session oversees multiple root orchestrators, pair this with the shunk031-herdr-meta-orchestrate skill for root inventory, status requests, and relays. When writing a Codex worker's task prompt, follow the shunk031-codex-worker-prompting skill. Build every peer prompt in a shell variable first (for example, with a quoted heredoc) and pass it as one argument; never let the shell expand task or report text.

Send only into an empty input

Before every prompt send in this skill, run the guarded sender at ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh with the target and a quoted message variable instead of calling herdr agent prompt directly. The helper resolves the target pane with herdr agent get, reads herdr pane read --source visible --format ansi, and uses herdr agent prompt as the transport only when the current ❯ or › input line has no non-dim text. Treat placeholder or suggestion text styled with ANSI \e[2m after the prompt glyph as empty. It checks immediately, then waits 30 seconds and rechecks up to five times; if the input remains occupied or cannot be read, it leaves the message unsent and prints the full message as undelivered.

Set up the orchestrator

  1. Name yourself so workers can address you, and label your tab. Pick a short name that is free in herdr agent list; names cap at 32 characters:
herdr agent rename "$HERDR_PANE_ID" orch
herdr tab rename "$HERDR_TAB_ID" "🤖 Orchestrator"

A worker promoting itself to sub-orchestrator skips this step: it keeps the agent name and tab its parent already tracks.

  1. Split the request into independent tasks, one worker each. Before dispatching a structural task — one changing the layout, jobs, configuration, documents, or interfaces — write a design record in the user-facing report covering the target end state, removals, retained elements and why, and user-owned settings touched with the options offered; do not dispatch if any item is empty. Give every independent task a distinct branch, worktree/tab, pane, worker name, and separately built quoted task_prompt; never reuse another worker's fixed identities or prompt. Keep sequential or trivial work yourself. Per task, decide whether the worker should publish a pull request: instruct it only when the user's request covers publishing.

Launch each worker

  1. Before any task edits, establish the deliverable's repository-associated workspace with herdr worktree create; for an already-open deliverable worktree, use herdr worktree open and reuse its returned deliverable workspace and root-pane IDs instead of creating a second workspace. Do not substitute manual git worktree add plus herdr tab create. For every implementer, reviewer, or other related tab for that deliverable, move the returned root pane into a new tab in that deliverable workspace before starting the agent; keep the orchestrator workspace for the orchestrator only. For a worker that needs environment variables, use the direct tab-creation path in step 4 instead of moving the root pane. Read the returned deliverable workspace, destination tab, and pane IDs from each response and use those destination IDs for all subsequent commands:
# Choose exactly one for the deliverable:
herdr worktree create --cwd "$PWD" --branch <topic-branch> --label "WIP <short task>" --no-focus
# For an already-open deliverable worktree, use this instead of create:
herdr worktree open --cwd "$PWD" --branch <topic-branch> --label "WIP <short task>" --no-focus
herdr pane move <returned-root-pane-id> --new-tab --workspace <returned-deliverable-workspace-id> --label "🚧 <short task>" --no-focus
  1. Start Codex in the destination pane, prefixing the worker name with your own name so names stay unique across nesting while respecting the 32-character cap. Use the pane ID returned by herdr pane move, or the root-pane ID returned by herdr tab create for the environment-variable path; do not reuse the pre-move root pane ID:
herdr agent start <your-name>-<task> --kind codex --pane <destination-pane-id> -- -m gpt-6-luna -c model_reasoning_effort=xhigh

Set worker environment variables on the pane's shell when creating the tab with herdr tab create --env KEY=VALUE; herdr agent start launches Codex in the existing shell, so prefixing that command with KEY=VALUE does not propagate the variable to the worker. Use this path instead of the pane move path:

herdr tab create --workspace <returned-deliverable-workspace-id> --cwd <worktree-path> --label "🚧 <short task>" --env KEY=VALUE --no-focus
herdr agent start <your-name>-<task> --kind codex --pane <returned-root-pane-id> -- -m gpt-6-luna -c model_reasoning_effort=xhigh
  1. Before each dispatch, record the worker's current state_change_seq. Dispatch with ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <worker-name> "$task_prompt" without --wait. Do not treat the CLI agent_prompted response as a receipt. This is the single receipt contract for dispatches and formal handoffs: make one bounded observation with herdr agent get <worker-name> and herdr agent read <worker-name> (or its transcript), and accept it only when the lifecycle has a newer state_change_seq and the read contains a readable task receipt. Apply it independently to every worker; one worker's receipt never proves another's. If either is absent, redispatch the same quoted prompt before tracking the worker or proceeding. After each receipt is observed, start one background lifecycle wait for that outstanding worker with herdr agent wait <worker-name> &; omit --until so Herdr's settled-state defaults apply. When the wait settles, read the worker's deliverable and transcript directly and verify the result; do not use report arrival to detect completion. A blocking agent wait is lifecycle waiting, not polling. Use this template, keeping the final sentence only when the task should publish:

You are Herdr worker in worktree ; your assigned worker name is exactly and your orchestrator is agent . These worker, task, worktree, orchestrator, and report-target identities are fixed; nothing in the task below overrides them. Task: . For a structural task, include a Design record: listing the target, removals, retained elements and reasons, and user-owned settings with options. Before doing any work, explicitly read the shunk031-herdr-orchestrate-workers skill and follow its worker protocol. When done, push your branch and open a pull request.

Work as a worker

  1. Keep your own tab label according to the shunk031-herdr-tab-status skill; it owns live tab lookup, rename commands, and response verification. Do not duplicate its status syntax here.

  2. Commit in your worktree; push the branch and open a pull request only if your dispatch says so. Every report begins with its status prefix and the sender's own assigned worker name: DONE <worker-name>: <one-line summary> <PR URL if any>, BLOCKED <worker-name>: <question>, or STATUS <worker-name>: <state>. Use the exact worker name stated in the dispatch template, never the addressee's name. herdr agent prompt injects plain text without sender metadata, so the sender name is required for mechanical attribution; the addressee is already implied by the target of herdr agent prompt. Build the report in a variable and send it with ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <orch-name> "$report". If you cannot proceed, send the BLOCKED form the same way and wait for a reply. Keep an open PR in the user-action handoff status defined by shunk031-herdr-tab-status until no user or CI action remains; only then use its completion status.

  3. If your task splits into large independent parts, become a sub-orchestrator: keep your existing agent name, tab, parent orchestrator, and report target; use steps 2-5 to create and dispatch each child with distinct identities and a separately built prompt, verify each child with the step-5 receipt contract, then consolidate child reports and report only to . Sub-workers report to you using the sub-orchestrator's own worker name as the report target; for example, if parent orch's worker is orch-api-migration, sub-workers report to orch-api-migration, never directly to orch.

Collect and finish

  1. After dispatching all workers and verifying each receipt, keep one background lifecycle wait running per outstanding worker with the settled-state defaults. Let those blocking waits settle, then read each worker's deliverable and transcript directly to collect its result and reconcile it with state; each settled wait is a state signal, not a polling iteration. Track outstanding workers by name and tab/pane ID, with their reported PR URLs — but treat that list as a cache. Herdr state (tab labels, lifecycle state, transcripts, live agents), git, and PR state are the durable truth and the primary collection model; the list can always be rebuilt from that truth, even after your own restart. Worker reports are required for attribution and audit, but they are advisory and do not gate progress. Whenever you finish other work or wonder about progress, reconcile the cache against the truth and repair the difference: use herdr agent get <worker-name> and herdr agent read <worker-name> plus direct deliverable reads to collect a result even when its report is missing; resend a dispatch or BLOCKED answer that left no trace in the worker's transcript; read a dead worker's pane with herdr pane read <pane-id> and restart or take over its task (a dead sub-orchestrator's name-prefixed workers become yours); ignore reports from workers you already settled; and finish interrupted cleanup, such as a merged PR with a leftover worktree. A missing report is not an error when the state wait and direct reads already collected and verified the result.

When a user states a style, content, or working-method rule, broadcast it to every worker whose deliverable it can affect, not only the worker for the artifact the user named. Require each affected deliverable owner to return an audit sweep recording occurrences found and fixed, or an explicit zero-finding sweep, as evidence.

For an existing anonymous live pane adopted mid-task, assign it a unique agent name from current Herdr state with herdr agent rename <pane-id> <unique-name>, then build handoff_prompt in a quoted variable and send ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <worker-name> "$handoff_prompt" with fixed worker, task, worktree, orchestrator, and report-target identities; verify that handoff with the step-5 receipt contract, then require the worker's STATUS <worker-name>: <state> receipt through the literal command ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <orch-name> "$status". A tab-label change, ordinary task-channel output, PR creation, or CI state is not notification evidence by itself.

A confirmed-working STATUS ends reconciliation: send nothing; leave the worker's lifecycle wait running and collect its result from the settled state, deliverable, and transcript. Do not wait for a DONE or BLOCKED report, and do not poll.

Only when the user explicitly requests active observation, optionally run scripts/herdr-orchestrator-observer.sh with fixed worker/pane/tab IDs, a conservative interval, and unchanged-sample threshold from observer_runtime="$(mktemp -d "${TMPDIR:-/tmp}/herdr-observer.XXXXXX")"; launch with ... >"$observer_runtime/observer.log" 2>&1 & observer_pid=$! and validate it with kill -0 "$observer_pid". The read-only sampler keeps fingerprints and coalescing in-process, skips only unreadable workers, sends one quoted advisory nudge through the guarded sender, and exits when no scoped workers remain; stop that PID after the requested observation. The per-worker lifecycle waits and direct reads remain the completion path; the observer and any report prompts are advisory, and a missing report does not block a result already collected from state.

### Supplemental health watchers

Use the worker stall and runner failure watchers when passive alerts would help with the active task. After workers and state sources are available, run each script once in the foreground with --once; continue only when it exits successfully and prints OK. Resolve any alert or startup error before starting a background copy. Set the worker prefix, state-file globs, and optional SSH destination from the current task; repeat --state-glob or --remote-state-glob for additional patterns. Do not persist machine-specific values in this skill.

worker_watch_args=(--agent-prefix "$WORKER_PREFIX" --interval-seconds 900)
if [[ -n "${ORCHESTRATOR_NAME:-}" ]]; then
    worker_watch_args+=(--exclude-agent "$ORCHESTRATOR_NAME")
fi
runner_watch_args=(--state-glob "$STATE_GLOB" --interval-seconds 600)
if [[ -n "${REMOTE_HOST:-}" ]]; then
    runner_watch_args+=(--remote-host "$REMOTE_HOST" --remote-port "${REMOTE_PORT:-22}" --remote-state-glob "$REMOTE_STATE_GLOB")
fi

if ! HERDR_ENV=1 scripts/herdr-worker-stall-watch.sh "${worker_watch_args[@]}" --once; then
    printf '%s\n' 'Check the worker watcher output and resolve any alert or startup error before backgrounding.' >&2
    exit 1
fi
if ! scripts/herdr-run-failure-watch.sh "${runner_watch_args[@]}" --once; then
    printf '%s\n' 'Check the runner watcher output and resolve any alert or startup error before backgrounding.' >&2
    exit 1
fi

watch_runtime="$(mktemp -d "${TMPDIR:-/tmp}/herdr-watchers.XXXXXX")"
HERDR_ENV=1 scripts/herdr-worker-stall-watch.sh "${worker_watch_args[@]}" >"$watch_runtime/workers.log" 2>&1 &
worker_watch_pid=$!
scripts/herdr-run-failure-watch.sh "${runner_watch_args[@]}" >"$watch_runtime/runners.log" 2>&1 &
runner_watch_pid=$!
if ! kill -0 "$worker_watch_pid" || ! kill -0 "$runner_watch_pid"; then
    kill "$worker_watch_pid" "$runner_watch_pid" 2>/dev/null || true
    printf 'Watcher startup failed; inspect %s/*.log.\n' "$watch_runtime" >&2
    exit 1
fi

Stop both watcher processes after reconciling the scoped work. These scripts are backstops; keep one lifecycle wait per worker and use the launcher's own self-watch for its process. Watcher output does not establish completion or replace direct state and transcript reads.

An actionable REJECT from an independent review worker is non-terminal: before reporting or waiting, verify the live task/PR owner, consolidate the findings, and route them immediately to that owner with the existing quoted ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <owner> "$answer" path; use the step-5 receipt contract, record the exact rejected head and re-review pending, and after the owner publishes a corrected head route that exact head back to the same reviewer with the same contract. Before asking the user to merge and immediately before squash merging, verify the live PR head matches the head SHA recorded with the reviewer's ACCEPT, and check whether it is mergeable, behind, or conflicting. If behind, route the branch update and CI follow-up to the owning worker; if conflicting, route conflict resolution and CI follow-up to that worker. If the head differs or either repair changes it, withhold the handoff or merge, re-review that exact head, and ask the user again only after a fresh ACCEPT.

When a user flags one defective passage in a worker-owned deliverable, treat it as an instance of a class, not a spot-fix request. The orchestrator defines the class, quotes the flagged passage as the model case, dispatches a sweep to every owner whose artifact can carry that class, and requires each report to include found and fixed counts.

When a DONE, BLOCKED, STATUS, or OBSERVER report arrives, send the answer or next instruction with ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <worker-name> "$prompt" in that turn, or state the concrete reason for waiting; this advisory routing does not replace the lifecycle wait or direct collection, and a missing report does not block a settled result. A decision without a prompt or wait reason leaves the turn incomplete.

When explaining a constraint's provenance, check the transcript, distinguish the user instruction, worker prompt, and orchestrator addition, state only the observed origin, and do not infer motive.

Process queued worker reports during the same user turn; do not defer them until after answering the user.

Treat each deliverable that will become one pull request as one workspace, and open its implementer and reviewer tabs there. Rename the workspace with herdr workspace rename <workspace-id> "WIP <topic>", then herdr workspace rename <workspace-id> "Issue#<N> <topic>", then herdr workspace rename <workspace-id> "PR#<N> <topic>"; perform the Issue or PR rename in the same turn that receives its URL, and leave tab labels worker-owned under shunk031-herdr-tab-status.

For worktree work, establish or reuse the deliverable's repo-associated workspace with herdr worktree create --cwd "$PWD" ... or herdr worktree open --cwd "$PWD" ...; use the returned deliverable workspace ID and returned root-pane ID, and for every related implementer or reviewer tab move that pane with herdr pane move <pane-id> --new-tab --workspace <returned-deliverable-workspace-id>. Reuse the open response's existing workspace and root-pane IDs instead of creating a second workspace. Never target $HERDR_WORKSPACE_ID for worker tabs or construct a workspace with herdr pane move <pane-id> --new-workspace.

An orchestrator session is replaceable only as a last resort: unlike workers, it accumulates user decisions and cross-stream judgment that exist nowhere else, so keep durable state written out (workspace names, the task list, research memos, PR links) as you go — that is what makes an emergency handoff survivable, not a reason to treat the session as cheap.

By contrast, a worker session is disposable: when a rejection means "rethink from scratch" rather than "repair this diff", do not re-prompt the owning worker — its context is anchored to the failed approach and will reproduce it under new instructions. Close that agent, spawn a fresh worker (with a new branch/worktree when the old diff itself is the problem), and hand it requirements plus verified evidence, never the old implementation as the starting point.

Before handing any reader-facing artifact (a report, research note, or published HTML) to the user, obtain an ACCEPT from a first-look review: spawn a FRESH worker with no prior involvement in the artifact — its empty context is what makes it a genuine first-time reader — and have it judge whether the top of the artifact alone conveys the question, method, result, and consequence in plain language. Route a REJECT back to the owning worker like any review finding. The user must never be the first reviewer to see the artifact. This fresh-first-look review is the same independent-review mechanism as DONE verification below and remains mandatory.

  1. On BLOCKED, build your answer in a variable and reply with ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <worker-name> "$answer"; use that same quoted-variable command sequence, ~/.agents/skills/shunk031-herdr-orchestrate-workers/scripts/herdr-agent-prompt-if-input-empty.sh <worker-name> "$prompt", without substituting another Herdr command, whenever routing an owned task or PR action—including the user's authorization for a merge—back to a live worker. On DONE, dispatch the verification work to independent reviewer workers with no prior involvement in the work: have them rerun the applicable gates, reconstruct each claimed contract from its source of truth, inspect the diff and evidence, verify tests exercise the actual package or production path against an independently reconstructed reference, check citations, and recompute every numeric claim from raw data before the orchestrator accepts or relays the result. For structural tasks, reviewers reject a dispatch lacking or contradicting the four-part Design record: or a delivered structure that misses its target, regardless of CI. Verify each deliverable against the user's original words, not the dispatch text, because the dispatch itself can be the corrupted layer. Require every DONE report to include a per-item audit against the session's accumulated user-rulings checklist, and reject reports that omit the audit. The orchestrator keeps receipt checks and state reconciliation, consolidates reviewer findings, makes accept/reject decisions, and routes results to owners or users. Worker summaries, test names, pass counts, manifests, CI, and artifact hashes remain candidate evidence only, not acceptance; reviewer reports are findings for the orchestrator to consolidate, not decisions. Reject and redispatch claims based on test-local reconstructions or runtime paths that do not exist or are not exercised. Each agent owns its own tab label. Once a live worker owns a task or PR, route later task-scoped implementation and GitHub lifecycle actions—including rebase, push, PR update, merge, and CI or merge-queue follow-up—back to that worker; the coordinator may inspect read-only state, review, and relay user decisions but must not execute those owned actions, and may take over only after verifying the worker is unavailable or the user explicitly directs it to act. If a worker dies or goes silent behind a stale label, set its tab to "⛔ " and inspect it with herdr agent get and herdr agent read. When several workers fail the same way at once — gateway auth or rate-limit errors, say — the cause is shared, not per-task: rotate or fix the credential in the runtime config workers actually read (a variable exported only in your own shell reaches nothing already running), then reconcile as usual; workers that were merely retrying recover on their own, and dead ones restart with their worktree state intact.

    When two streams show the same failure signature, transfer the diagnosis between them and consider a generalized meta issue.

    Independent reviewers recompute every numeric heading in a worker report from its raw data before the orchestrator relays it or uses it for a decision.

    Raise user-owned decisions about compute scale, deletion, license or provenance, scope, and settings such as rulesets, branch protection, secrets, and repository or service configuration before dispatch, with concrete options and numbers while keeping other streams moving; never encode them as constraints to design around. Never put internal infrastructure identifiers such as mount paths, internal hostnames, or IP addresses in repository artifacts, including docs, PR bodies, issue comments, or commit messages.

  2. In every user-facing report, make the first mention of each PR or issue a full-URL Markdown link; a bare #N alone is insufficient. When inbox tools are available in the orchestrator session, state each blocking user decision or action in the report so the mod's post-turn update adds it to the Needs you list; do not repeat open inbox items in later reports. The mod creates questions for choices that block continued work and tasks for actions only the user can take that block continuing or finishing. Write each item so it stands alone: what the user should decide or do, why it is needed or what it blocks, your recommendation, and what you will do if there is no answer; include the exact command or link when relevant. mcp__inbox__record_finding records out-of-scope issues or opportunities, not user blockers. Close an item only after the user has answered it or its task is verified done or no longer applies, using mcp__inbox__close with the item's id and the user's answer or a short reason; never close a question with your own answer. If the inbox mod is unavailable, end each report with only user-only items that currently block progress, in the same self-contained form, and omit the list when none block. Decide items you can resolve yourself and report FYIs once instead of repeating them. When every worker is done, keep your 🤖 label, summarize per worker with PR links, and announce with herdr notification show "Workers done" --sound done.

  3. A worker's tab, worktree, and branch live exactly as long as its pull request: once the PR merges they are spent, so clean them up as soon as you notice — check each worker's reported URL with gh pr view <url> whenever you revisit the session, wrap up other work, or are asked to tidy up; never run a polling loop for it. Close the tab, then remove the worktree and delete the branch only if the worktree is clean — never pass --force; if it still holds uncommitted work, leave it in place and tell the user. Touch only resources created through this protocol; leave unmerged, ⛔, and other people's tabs alone.