Herdr FAQ
Launch and drive coding agents through Herdr (>= 0.9.0) without losing prompts.
Syntax authority
herdr --skill (~13k chars) is the vendor reference: command model, IDs, lifecycle
states, prompt and wait mechanics, read sources, safety rules. Read it once per session
before your first herdr command - this file assumes it and repeats none of it. For
flags, run the command group bare (herdr agent, herdr pane) or --help; never bare
herdr (it launches the TUI). If a syntax question is not answered here, it is
answered there - go get it, never guess a flag.
When this lane
test "${HERDR_ENV:-}" = 1 # never drive herdr from outside a pane
Unset means this shell is not herdr-managed: no $HERDR_PANE_ID, no --current, and
an untargeted command lands on whatever pane the USER has focused. Use acpx instead
(acpx-faq). In-session fan-out stays on the harness's own subagent tool.
Invariants
- Exit 0 is submission, never completion - and
send-keyscan return{"type":"ok"}for keystrokes that never reach the pane. Confirm by effect: state moved, or the text is visible in the pane. - Screens no detection rule matches read as
idle. agy has no idle rule at all, so every agyidleis a guess. Read the screen before the first prompt, always. agent starttimeout = the child never launched (bad flag, PATH, or a wrapper process).pane readhas the real error;pane process-infoand herdr's own message do not.idleis not "finished". A claude turn that spawned background shells or a background MCP task ends and reportsidlewhile that work runs on;--waittracks lifecycle state, not turns.- The driving harness is a second gate. Dangerous passthrough flags
(
--dangerously-*,danger-full-access) get classifier-blocked, and so can ordinary brief content inlined into a shell command.sleep N; herdr ...polling is banned - use one backgroundedprompt, or wait on a file.
Per kind
Model defaults unless the task argues otherwise: codex gpt-5.6-sol at
reasoning_effort high (set via codex's own flags/config - check codex --help),
claude claude-opus-5, agy gemini-3.7-flash-medium (measured on par with Opus for
rubric-driven bulk work and far faster; gemini-3.8-flash-high measured worse on the
same task). One directory per agent, always.
codex
P=$(herdr pane split --current --direction right --cwd "$D" --no-focus | jq -r .result.pane.pane_id)
herdr agent start cx1 --kind codex --pane "$P" --timeout 90000 -- --approve-for-me --no-alt-screen
herdr agent read cx1 --source detection --lines 40 # ALWAYS, before the first prompt
herdr agent prompt cx1 "Carry out $D/brief.md." --wait --timeout 1800000 &
Never --full-auto: removed in 0.15x, it surfaces as a bare timeout and can wedge
the pane in startup-pending. The trust-directory and startup-update dialogs are
detected, so an untrusted dir fails fast with agent_not_ready rather than eating the
first prompt. The one-time post-integration install hooks-review gate is NOT
detected (WONTFIX) - answer it once per machine. Session ref binds at the first prompt
for a fresh start; an explicit -- resume <id> is persisted at launch.
claude (alias claude-code)
# Pre-trust first - the folder-trust dialog IS detected, so an untrusted dir fails the
# start. Trust is INHERITED: a new dir under an already-trusted parent needs nothing.
CFG=/tmp/agentcfg; mkdir -p "$CFG" # isolated from your real config
printf '{"projects":{"%s":{"hasTrustDialogAccepted":true}}}' "$D" > "$CFG/.claude.json"
P=$(herdr pane split --current --cwd "$D" --no-focus --env CLAUDE_CONFIG_DIR="$CFG" \
| jq -r .result.pane.pane_id)
herdr agent start cl1 --kind claude --pane "$P" --timeout 90000 -- --model claude-opus-5
herdr agent read cl1 --source detection --lines 40 # ALWAYS, before the first prompt
herdr agent prompt cl1 "Carry out $D/brief.md." --wait --timeout 1800000 &
- The safe key differs per dialog. The folder-trust dialog puts the cursor on
No, exit, so a bareenterkills the agent - senddown enter. The Bash-permission dialog puts the cursor on1. Yes. idlelies in a specific way here. Background shells end the turn and reportidlewhile they run; background agents and MCP tasks keep itworking, and a background MCP task can holdidlefor its whole 3-10 minute life.- Grey next-prompt-suggestion text reads as real unsubmitted text in a plain
read - disambiguate with
--format ansi(the suggestion carries the dim attributeESC[2m). - Session ref binds at launch. A Claude Code UI update can break detection
(
herdr server update-agent-manifests). Native-launcher installs run under a version-string process herdr cannot identify: launch viaHERDR_AGENT=claude exec <path>, thenagent rename. - Under a different
CLAUDE_CONFIG_DIRthe agent loads its own skills. Confirm they are current before its first turn - a running session has already snapshotted the old text.
agy (aliases antigravity, antigravity-cli)
Thinnest detection of the three: no idle rule at all (invariant 2), and nothing in the pre-flight is recoverable once the agent is up.
D=/abs/dir/for/this/agent # ONE dir per agent - agy reads sibling files
mkdir -p "$D"; : > "$D/report.md" # pre-create the output file
python3 - "$D" <<'PY' # pre-trust; the dialog is unanswerable (below)
import json, pathlib, sys
p = pathlib.Path.home() / ".gemini/antigravity-cli/settings.json"
d = json.loads(p.read_text()) if p.exists() else {}
tw = d.setdefault("trustedWorkspaces", []) # a list of exact paths, not a map
if sys.argv[1] not in tw: tw.append(sys.argv[1])
p.write_text(json.dumps(d, indent=2) + "\n")
PY
P=$(herdr tab create --workspace "$WS" --cwd "$D" --label ag1 --no-focus \
| jq -r .result.root_pane.pane_id) # one TAB per agent, never N panes in one tab
herdr agent start ag1 --kind agy --pane "$P" --timeout 120000 \
-- --model gemini-3.7-flash-medium --effort medium --mode accept-edits
herdr agent read ag1 --source detection --lines 15 # read the banner, not the status
herdr agent prompt ag1 "Carry out $D/brief.md. Write your report to $D/report.md." \
--wait --timeout 1800000 & # then poll for the file, not the state
- The trust dialog is unanswerable from this side.
agent startreturnsidle, the screen holdsDo you trust the contents of this project?, andsend-keys enterno-ops against it - repeatedly, silently, exit 0.--dangerously-skip-permissionsis classifier-blocked before it reaches herdr (invariant 5). Only a config entry written beforeagent startpre-empts it. - Trust lives in
trustedWorkspacesin~/.gemini/antigravity-cli/settings.json, NOT~/.gemini/trustedFolders.json(the Gemini CLI's file - observed ignored on agy 1.2.0). Pre-writing it is not confirmed end to end, and a harness classifier may refuse the edit as a trust-config change - when it does, drive agy through acpx instead (acpx-faq): its ACP server has no folder-trust dialog. - One tab per agent. Four agy panes in one tab leaves ~29 columns: the TUI stops rendering, input races swallow prompts, and detection sees only the pane's own rows, so the state you read is wrong too.
- Read the startup banner before blaming herdr.
<account> (Google AI Pro)means entitlement is reaching Code Assist. A bare address, orEligibility check failed: UNAVAILABLE (code 503), means dead on arrival however healthy the status looks - re-auth, do not re-prompt. not a valid artifact pathis agy-internal, not your filesystem.ArtifactMetadataon awrite_to_fileconfines the write to~/.gemini/antigravity-cli/brain/<session-id>/. It self-recovers by retrying asEdit; pre-creating the file empty skips the round trip.- agy reads sibling files unprompted - agents sharing a directory reproduce each
other's output. Own directory each, and
md5any rerun before believing an agreement number: byte-identical multi-KB prose is copying, not consensus. - Premature
donelasts up to ~50s mid-turn while the pane visibly streams - wait on the report file, never a settled state. A first prompt can be swallowed entirely withagent_prompt_stalledand no trace in the composer; re-prompt, neversend-keys. No session ref until the first prompt. Integration install target isantigravity-cli;~/.gemini/configmust exist (orANTIGRAVITY_CLI_CONFIG_DIR).
Integrations for all three are session-restore only - they never improve state
detection - and their hooks silently no-op without python3 on PATH.
integration status cannot see a codex-side disabled hook (WONTFIX); verify
agent_session is present after the first codex turn instead.
Launch and fleets
- Fleets: one tab per agent (
herdr tab create --workspace "$WS" --cwd "$D" --no-focus), own workspace,--no-focuseverywhere. Repeated splits leave panes narrow, and a TUI below its minimum width stops rendering, swallows input, and hides its state from detection (which sees only the pane's own rows; fallback 24). - Always
agent read --source detectionbefore the first prompt (invariant 2) and branch on it - never blind-fire a key at a dialog. After answering one, gate onagent wait <n> --until idle done --timeout 60000: a barewaitreturns the still-blockedstate within milliseconds, a no-op that looks like the key never landed. - Names die with the agent and freed names get recycled - namespace them
(
myproj-reviewer). After a killedagent start, oneherdr agent get <name>frees the reservation (reconciliation is lazy). An agent TARGET can be a bare pane id (agent prompt w1K:p1 '<text>') - how you reach an unnamed agent without renaming someone else's. - Test
-n "$P"after capturing a pane id: an empty one makes later parsers blame the wrong token.
Composing traps
agent readandpane readreturn raw pane text; the other agent/pane/workspace commands return JSON. Piping raw text tojqdies withparse error: Invalid numeric literaland you silently lose the read - usetail,grep -qF.pane read <id>takes the pane positionally and has no--paneat all (unknown option: --pane);pane closelikewise. When in doubt read theUsage:line - it names positionals in angle brackets.agent start --timeoutdefaults to 30000 and caps at 300000.agent prompt --timeouthas no cap (1800000 is fine) but is rejected with--timeout requires --waitwhen--waitis absent - and backgrounded, that failure is invisible and the prompt is never written. Fire-and-forget sends take no flags.agent listJSON is sparse:nameis absent (not null) for unnamed agents, and a pending start is{"agent_status":"unknown","launch_pending":true}. Null-guard every string op:select((.name // "") | startswith("myproj-")).- Env vars go in
--env KEY=VALUEat pane/tab/workspace creation, neverpane run 'export ...'(a subshell the agent never sees). Repeated flags go inline - zsh does not word-split an unquoted$E, soE="--env A=1 --env B=2"arrives as one bogus argument. - Bad flags print usage partly on stdout, so a
| jqpipeline turns it into a misleading parse error - check exit status before parsing.
Drive
- Wait on the artifact, not the state. End every non-trivial brief with "write
your full report to
<path>and reply with only the path", and poll for the file.idlearrives on interrupted turns, on fleet parents that dispatched children, and for the whole life of a claude background MCP task (invariant 4) - a watcher on!= workingfalse-alarms on every one of those. - Gate decisions on
agent_status, neverinteractive_ready(it stays true while blocked). - Long text is safe through
agent prompt(bracketed paste; 8 KB arrives intact); the 1024-byte macOS tty truncation applies topane send-textand keystrokes. Pass file paths anyway - it also keeps brief content out of the shell command (invariant 5). - Queue follow-ups behind a working agent freely (
promptrefuses blocked agents before writing anything) - but no--waiton a queued follow-up: it can be satisfied by the turn already running. - Slash commands that open dialogs trip the fixed 5s stall gate - verify those by
screen read, not
--wait. - Exit: confirm positively -
pane process-infoshows the shell back in the foreground - thenpane close. Never regex the prompt.
Failures
Triage first:
- Any start failure ->
herdr pane read <pane> --source visibleFIRST. Most common: the child launched, printederror: unknown option ..., and exited -process-infolooks innocent and herdr reports a baretimeout. Thenprocess-info: a foreign foreground process = busy/race; an agent under a wrapper (node, a bare version string) = relaunch viaHERDR_AGENT=<kind> exec <cmd>, thenagent rename; underdocker exec,podman exec, orssh -t= permanently undetectable (WONTFIX). - Any wrong state ->
agent explain <target> --verbose+agent read --source detection. Matched rule null +default_known_agent_idle_fallback= herdr is guessing. Stale manifest ->herdr server update-agent-manifests(no restart). Local rule patch:~/.config/herdr/agent-detection/<kind>.toml. Logs:~/.config/herdr/herdr-server.log,HERDR_LOG=herdr=debug. - After a binary update, do NOT reflexively
server stop- it kills every pane process and in-flight turn, unresumably. 0.9.0 leaves a compatible server and its agents untouched; trustherdr status(endpoint_compatible,restart_needed,server_binary_stale). A stale CLIENT inverts the picture: it calls the server "old" whilepane listcorrectly saysclient protocol N is older than server. - "herdr is stuck" is often the terminal emulator, not herdr - open a fresh tab before touching the server.
Catalog:
-
agent_not_ready(start) - a dialog is on screen; exit 1 but the agent is running and the name is bound. Read, answer viasend-keys(safe keys in Per kind), gate--until idle done, prompt. A blocked launch never times out (launch_pendingstays true until answered). agy's trust dialog is NOT detected and reads asidleinstead - see agy. -
timeout(start) - invariant 3; read the pane.command not foundin a non-login shell: set[terminal] shell_mode = "login", recreate the pane. Under--remote, panes inherit the SERVER's PATH, so a~/.local/binagent CLI gives a bare timeout. -
agent_name_taken/agent_launch_pending- reservations reconcile lazily: oneherdr agent get <name>frees an expired one. Still wedged: burn both - fresh pane, fresh name. -
agent_pane_busy- "available shell" = the shell itself, alone, in the foreground. (a) racy rc files (starship,direnv): herdr retries only 2s andpane getlooks identical ready vs not - retry with backoff, clean up orphaned tabs; (b) genuine occupant: split a new pane, never reclaim (killing the occupant cascades intopane_not_found). (Windows: pwsh chain-launch profiles nest shells - set[terminal] default_shell = "pwsh.exe".) -
agent_not_found- downstream symptom: failed start, exited agent, or a bad name earlier in the loop. A live pane can rarely lose registration while the TUI runs fine -agent rename <pane> <same-name>restores it. -
timeout(prompt --wait / wait) - usually the turn outlasted the timeout:agent getshowsworking. Use 1800000+, background it, never resend on timeout alone. -
agent_prompt_stalled- not proof of non-delivery; the text may have landed and been consumed. Causes in observed order: stale manifest; a dialog-opening prompt; a target-side paste modal swallowing Enter (omp's Large Paste Menu triggers on line count, not bytes - disable it in omp/settings). Never blind-resend and never recover with a lonesend-keys enter. Read the pane, then re-send with a freshagent prompt:for i in 1 2 3; do herdr agent prompt "$A" "$TEXT" --wait --timeout 60000 && break herdr agent read "$A" --source recent-unwrapped --lines 200 | grep -qF "${TEXT:0:80}" && break done -
agent_blocked(prompt) - refused before anything is written. Read detection, surface the dialog, answer via send-keys. -
invalid_agent_name-[a-z][a-z0-9_-]{0,31}; uppercase from shell loops cascades into a wall ofagent_not_founds. -
pane_not_found/workspace_not_found/unknown option: <valid-looking value>- IDs are runtime-only, never reused: re-list at session start, recreate only what is missing.unknown optionon a whole flag string is the zsh variable trap (see Composing traps); on a lone token it is usually an empty$P. -
workspace_group_close_required- closing a primary workspace with open worktree workspaces needsworkspace close --group;tab closecan likewise returnconfirmation_requiredfor a worktree group. -
Harness blocks - classifier denial on dangerous passthrough flags: put permissiveness in the child agent's own config ("Stage 2 classifier error" is transient - retry once). Allowlist the read-only commands (
agent get/read/list/wait/explain,pane read/list/process-info,workspace list) or every call prompts.
Silent failures (exit 0, no error): fallback-idle prompt swallowing (invariant 2);
send-keys returning ok without delivering; a bare agent prompt leaving text
unsubmitted in an out-of-view pane; a first turn going straight
unknown -> working -> idle skips done and its notification; a tall dialog partly
invisible in a short pane (detection sees only the pane's own rows);
pane wait-output matching the echoed command itself - never use it for readiness;
agent focus returning ok without moving the viewport in 0.9.0 - use tab focus.
