Herdr
Herdr is a terminal workspace manager for coding agents. Use it to inspect sibling panes, delegate to other agents, run servers or tests beside your current pane, and coordinate work without blocking.
For exact CLI syntax and less common workspace/tab/worktree operations, read references/command-reference.md.
Preconditions
test "${HERDR_ENV:-}" = "1"
command -v herdr
If HERDR_ENV is not 1, do not control live panes unless the user asked you to edit this skill or discuss Herdr. You may still read files and explain commands.
Operating Rules
- Discover first:
herdr agent list,herdr pane list. - Treat IDs as live-session handles. Re-read them after layout changes.
- Prefer
HERDR_PANE_IDas your pane handle when set;HERDR_WORKSPACE_ID/HERDR_TAB_IDname your workspace and tab. - For async agent task handoff use
scripts/herdr-msgorherdr pane run(the header carries reply-to). Useherdr agent prompt <target> "<text>" --waitonly for one-shot prompts whose reply stays in your own pane — it submits with Enter and rejects blocked agents. - Read a target once before sending, only to avoid interrupting a prompt/approval.
- After send:
herdr-msgchecks the target once.state=delivered→ stop.state=unconfirmed→ send Enter once totarget_pane, then stop. Do not poll. Do notherdr pane wait-output/--wait-replyfor normal chat. When a reply is injected into your pane, handle that inbound message. - Do not use
herdr agent wait/ status waits for multi-agent coordination. - Safety: do not close workspaces/tabs/panes you did not create unless the user asks; never run
herdr server stopfrom an active session; prefer--no-focusfor background work and--current/ explicit IDs over relying on UI focus.
Default Loop
- Discover agents/panes.
- Read the target once if needed.
- Send a short request with
scripts/herdr-msg(header carries reply-to + task). - On
state=delivered: end the turn. Onstate=unconfirmed: send Enter once totarget_pane, then end the turn. - When an inbound reply appears in your pane, integrate it.
After herdr-msg (mandatory)
- Non-zero exit → send/resolve failed; fix and resend. Do not poll.
state=delivered(exit 0) → obeyhint: stop. Helper already checked once.state=unconfirmed(exit 0) → checkenter_nudgein the receipt:enter_nudge=0→ send Enter once (herdr agent send-keys "$target_pane" Enter), then stop.enter_nudge=1→ the helper already pressed Enter; do not press it again. Stop. Do not pane read. Do not wait. Do not resend. Do not loop.
- The wake-up is the peer injecting a message into
reply-to(your pane). - Polling the target after the one allowed Enter is a protocol violation.
Lightweight agent messages
Keep it human and thin. One header line + body:
[herdr-msg reply-to:<your-pane-id> task:<short-label>]
<short actionable message>
Optional fields the helper may add: from:<name>, kind:reply / kind:update (requests omit kind).
| Field | Role |
|---|---|
reply-to |
Required. Where the peer should answer. |
task |
Human thread label (not a crypto id). |
from |
Optional sender label. |
kind |
Optional; default is request. |
Not required for normal chat: msg_id, sentinels, ack-of, status codes in the header, waiting recipes.
Send (default)
scripts/herdr-msg codex --task auth-review <<'MSG'
Review src/auth.ts for auth bypasses and missing tests.
Reply to reply-to with DONE or BLOCKED findings.
MSG
# receipt: delivered → end turn; unconfirmed → Enter once, then end turn
Reply
scripts/herdr-msg "$REPLY_TO" --task auth-review --kind reply <<'MSG'
DONE: Checked src/auth.ts. No bypass found. Missing refresh-token expiry test.
MSG
Manual fallback
SELF=$HERDR_PANE_ID
TARGET=$(herdr agent get codex | sed -nE 's/.*"pane_id":"([^"]+)".*/\1/p')
herdr pane run "$TARGET" "[herdr-msg reply-to:$SELF task:auth-review]
Review src/auth.ts. Reply to reply-to with DONE or BLOCKED."
Receipt
Tiny key=value lines: ok, state, target, target_pane, reply_to, task, kind, enter_nudge, target_status, hint.
delivered = helper saw working/blocked (or no agent). unconfirmed = still idle/done after the check — Enter once, then stop.
Agent states: idle = ready for input and its tab has been seen in the UI; done = the same idle state after unseen background work (the receipt treats it like idle); blocked = approval or question dialog; unknown = an agent is present but Herdr cannot classify it — herdr-msg treats it as started and does not nudge Enter. CLI reads never mark a tab seen; focusing the tab does.
Waiting Policy
Agent chat: do not wait. Send, end turn, handle inbound reply later.
Use herdr pane wait-output only for non-agent processes (dev servers, tests) where you can name a concrete marker such as ready or FAIL:
herdr pane wait-output "$PANE" --match "ready" --timeout 30000
herdr pane read "$PANE" --source recent --lines 40
Use --regex <pattern> for Rust regex matching; wait-output also matches output that already exists before the wait starts.
Only use herdr agent wait / status waits when the user explicitly needs UI status or you are attaching/taking over an agent.
Delegation Pattern
herdr agent list- Read target once.
scripts/herdr-msg <target> --task <label> <<'MSG' ... MSG- On delivered: end turn. On unconfirmed: Enter once, then end turn. No poll, no block-wait.
- When replies arrive on your pane, integrate; follow up only for gaps.
Starting Sibling Agents
herdr agent start launches a coding agent in an existing available shell pane (interactive prompt, no foreground command, shell in the foreground). It never splits or moves panes.
- Split a sibling pane first. Geometry: a wide pane splits
right, a narrow or tall one splitsdown; avoid repeated same-direction splits.PANE=$(herdr pane split "$HERDR_PANE_ID" --direction right --cwd "$PWD" --no-focus | sed -nE 's/.*"pane_id":"([^"]+)".*/\1/p') - Start the agent with a unique name and the kind the user asked for:
Pass native agent arguments afterherdr agent start reviewer --kind codex --pane "$PANE"--(-- <agent-args...>). Success means Herdr detected the agent in that pane and considers it ready for input. If it is blocked during startup you getagent_not_ready, but the name still works foragent read/agent send-keys; wait foridlebefore prompting. - Prompt it through the agent surface —
agent promptsubmits with Enter and--waitblocks until the first settledidle/done/blockedstate:
Useherdr agent prompt reviewer "Review the current diff. Reply with findings." --wait --timeout 120000--untilonly for state-specific waits, e.g.herdr agent wait reviewer --until blocked --timeout 120000. - Or hand off asynchronously with
scripts/herdr-msg reviewer --task <label>and end your turn.
Server And Test Panes
Split geometry: a wide pane splits right, a narrow or tall one splits down. For long-running panes, consider moving them to their own tab (herdr pane move "$PANE" --new-tab --label logs) and naming the outer terminal (herdr terminal title set "dev server").
SELF=${HERDR_PANE_ID:-$(herdr pane list | sed -nE 's/.*\{[^{}]*"focused":true[^{}]*"pane_id":"([^"]+)".*/\1/p')}
PANE=$(herdr pane split "$SELF" --direction right --cwd "$PWD" --no-focus | sed -nE 's/.*"pane_id":"([^"]+)".*/\1/p')
herdr pane run "$PANE" "npm run dev"
herdr pane wait-output "$PANE" --match "ready" --timeout 30000 || true
herdr pane read "$PANE" --source recent --lines 50
For tests, wait for framework markers (passed, failed, FAIL, …) or run tests in your own session.
Diagnostics
When state, detection, or layout looks wrong:
herdr status --json— client/server protocol compatibility.herdr agent explain <target>— why an agent is or is not detected.herdr server agent-manifests— active agent detection manifests.herdr api snapshot— full live session snapshot (agents, panes, workspaces).herdr config check— validateconfig.toml.
Gotchas
pane runsubmits text+Enter atomically;agent promptalso submits (bracketed-paste + encoded Enter) on the agent surface and rejects agents already at an approval dialog.herdr-msgchecks the target once after send and reportstarget_status. If still idle, receipt isunconfirmed.recent-unwrappedis better thanrecentwhen matching command output;detectionis the plain-text snapshot Herdr uses for agent detection.- Herdr sets
HERDR_ENV=1,HERDR_PANE_ID,HERDR_WORKSPACE_ID,HERDR_TAB_ID,HERDR_SOCKET_PATHin panes. --linescannot recover rows that left the terminal's alternate screen; if a read stalls, ask the agent to write its full response to a file and reply with the path.- After
pane move, the pane receives a new workspace-qualified ID; re-read IDs from the JSON result instead of reusing the old one. - Long diffs/logs: write a file path into the message; do not paste huge blobs into the peer prompt.
herdr-msgresolves targets by unique agent name, pane_id, or terminal_id. If several agents share a label (e.g. twocodexpanes) the label is ambiguous and resolution fails with the matching pane_ids listed — resend with a pane_id fromherdr agent list.- Request-to-self is refused by default (
--allow-selfto override). - Keep headers one line; put status and findings in the body (
DONE: …/BLOCKED: …).