herdr: The Agent Multiplexer with a Scriptable API
herdr is an open-source terminal multiplexer for coding agents. It keeps agents in real terminal panes on a background server, marks each pane working, blocked or idle, and exposes a CLI and socket API that let one agent start, prompt, wait on and read others. For Claude Code, Codex and Cursor, that state comes from screen detection, not hooks.
You have five agents running. One is mid-refactor, one finished eleven minutes ago, one has been waiting on a permission prompt since you left for coffee, and two are doing something you can only reconstruct from scrollback. In plain tmux you find out by visiting every pane, and the blocked agent has already wasted its eleven minutes.
This page is for developers who run several terminal agents at once, and for tech leads deciding whether a team should standardise on a scriptable fleet terminal. It covers herdr 0.9.1 (released 2026-09-16), the current release on 2026-09-26.
What this page covers
Section titled “What this page covers”- A working install with the per-agent integrations and the herdr agent skill
- An accurate model of when
blockedis trustworthy, and how to debug it when it is not - The reviewer flow: one command sequence that starts a Codex reviewer next to your session and reads its verdict
- A fan-out script that runs three implementers in herdr worktrees and one reviewer, with independent test runs as the gate
- Two copy-paste prompts that make your orchestrator agent drive herdr for you
- The traps: the npm placeholder, the Herd name clash, and the state-detection limits
When is herdr the right tool?
Section titled “When is herdr the right tool?”herdr earns its place when you run three or more terminal agents, want to see which one needs you without visiting panes, and want an agent (not only you) to drive the fleet. It is a poor fit when the team wants a graphical diff and review board.
| You need | Use | Why |
|---|---|---|
| One or two Claude Code sessions | Claude Code’s own --worktree and agent view | No new binary; see running agents in parallel |
| A mixed fleet (Claude Code, Codex, Cursor) in one terminal, scriptable | herdr | 24 agent kinds, one CLI for all of them |
| Zero new dependencies, or a security team that will not approve a new binary | tmux | See tmux for agent fleets |
| A desktop app with a diff and review UI per task | Conductor, Orca and similar | See alternative IDEs and agent shells |
Popularity as of 2026-09-26: herdrdev/herdr had 40,779 GitHub stars, read through the GitHub API for this site’s agent-tools dossier. The repository was created on 2026-03-27 and is licensed Apache-2.0. Stars measure attention, not fitness.
Install herdr and its agent integrations
Section titled “Install herdr and its agent integrations”herdr is one Rust binary. Install it from the official script or a package manager, then add the integration for each agent you run.
-
Install the binary (terminal, Linux or macOS):
Terminal window curl -fsSL https://herdr.dev/install.sh | sh# or: brew install herdr# or: mise use -g herdrOn Windows, run
powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex". herdr’s install docs describe Windows as generally available with documented platform limitations. -
Install the integration for each agent you use. Each tab below is the same
herdr integration installcommand with a different agent name:Terminal window herdr integration install claudeWrites
hooks/herdr-agent-state.shinto~/.claude(orCLAUDE_CONFIG_DIR) and adds aSessionStarthook tosettings.json. The directory must already exist, so runclaudeonce first.Terminal window herdr integration install codexWrites
herdr-agent-state.shinto~/.codex(orCODEX_HOME), updateshooks.jsonand sets[features] hooks = trueinconfig.toml. Codex asks you to trust new hooks before it runs them.Terminal window herdr integration install cursorAdds session identity for the Cursor Agent CLI. Start it in a pane with
herdr agent start <name> --kind cursor, which uses herdr’s canonical executable for that kind, so you do not need to know the binary name.~/.cursor(orCURSOR_CONFIG_DIR) must already exist. Restore after a server restart runscursor-agent --resume, so keepcursor-agentonPATH.For all three, the integration reports session identity, which lets herdr resume the right conversation after a server restart. It does not report lifecycle state. The next section explains why that matters.
-
Install the herdr skill, so that an agent running inside a herdr pane knows the CLI. The same command works for Claude Code, Codex and Cursor:
Terminal window npx skills add herdrdev/herdr --skill herdr -g-ginstalls it for every supported agent on the machine; omit it to install into the current project.herdr --skillprints the copy that matches your installed binary. -
Verify the setup:
Terminal window herdr integration statusherdrherdropens the TUI. Split a pane, runclaudeorcodexin it, and the sidebar shows the agent and its state.ctrl+b qdetaches the client; runningherdragain reattaches, and the agents keep running in between.
Context cost. herdr itself adds nothing to an agent’s context. The skill does: its SKILL.md was about 2,100 words (13.9 KB) on 2026-09-26. Its description tells the agent to use it only when you mention herdr explicitly, so it loads on demand. Run /context in Claude Code before and after a herdr task to see what it costs you.
How does herdr decide that an agent is blocked?
Section titled “How does herdr decide that an agent is blocked?”This is the feature you adopt herdr for, and the most common assumption about it is wrong. herdr gives each pane exactly one state authority:
| State authority | Agents (herdr 0.9.1 docs) |
|---|---|
| Lifecycle hooks or plugin, when the integration is installed | Pi, OMP, Kimi Code CLI, MastraCode, OpenCode, Kilo Code CLI |
| Screen manifest (the integration supplies session identity only) | Claude Code, Codex, Cursor Agent CLI, GitHub Copilot CLI, Devin CLI, Droid, Qoder CLI, Qwen Code, Letta Code, Hermes Agent, Grok CLI, Antigravity CLI |
| Screen manifest, no integration | Amp, Kiro CLI, Maki, Muse |
| Detected, less thoroughly tested | Gemini CLI, Cline |
A screen manifest is a set of rules that herdr evaluates against the live bottom of the pane’s buffer. herdr marks a pane blocked only when that snapshot matches a known approval, question or permission UI. When no rule matches, the pane falls back to idle, not blocked. So a new permission dialog, or an agent asking a free-text question in an unfamiliar layout, can show as idle while it waits for you.
Three consequences for anything you automate:
- A wait for
idlecan return while an agent is really waiting for an answer. Always read the pane before you act onidle. - Codex can fall back to
unknowninstead ofidle.unknownmeans an agent is present but herdr cannot classify it, not that the work succeeded. - herdr updates its detection manifests from herdr.dev in the background, so detection can improve without a binary update. Set
[update] manifest_check = falseif your policy forbids that.
When a pane shows the wrong state, ask herdr why:
herdr agent explain reviewerherdr agent explain --file screen.txt --agent codex --jsonThe output names the manifest, its version, the matched rule, and the fallback reason (default_known_agent_idle_fallback) when nothing matched. Live explain asks the running server, so after upgrading herdr, restart the server before using it; --file works locally without it. Attach the --json output to a bug report on herdrdev/herdr.
Start a reviewer next to your session
Section titled “Start a reviewer next to your session”The smallest useful automation: from the pane where you work, start a Codex agent named reviewer in a new split, give it the diff to review, and read what it says. Run this in a terminal pane inside herdr:
split=$(herdr pane split --current --direction right --no-focus)review_pane=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id')herdr agent start reviewer --kind codex --pane "$review_pane"herdr agent prompt reviewer "Review the current diff for missed error paths and missing tests. List findings as file:line - problem - fix." --wait --timeout 300000herdr agent read reviewer --source recent-unwrapped --lines 120What you see: a Codex pane opens on the right. agent start returns once herdr detects Codex and marks it ready. agent prompt --wait returns when the reviewer settles in idle, done or blocked, and agent read prints the last 120 rendered rows, unwrapped so long paths stay on one line. Creation commands print JSON, which is why you capture the pane ID with jq rather than guessing it.
Codex starts on its default model, GPT-6 Astra as of 2026-09-26 (see the models hub). Arguments after -- go to the agent unchanged, so -- -m <model> pins another one.
If the reviewer stops on an approval prompt, park until it does and then answer deliberately:
herdr agent wait reviewer --until blocked --timeout 120000herdr agent read reviewer --source recent-unwrapped --lines 80herdr agent send-keys reviewer escFan out three implementers and one reviewer
Section titled “Fan out three implementers and one reviewer”The workflow that justifies herdr: an orchestrator gives three independent tasks to three implementers, each in its own worktree, verifies each result by running the tests itself, and hands every passing branch to one reviewer. You only step in when an agent is blocked or a gate fails.
herdr 0.9.1 can create the worktrees itself: herdr worktree create makes a Git worktree, opens it as a workspace grouped under the repository, and returns the new root pane. Keep one owner for isolation. Either herdr creates the worktree (as below), or the agent does it with its own flag (claude --worktree <name>, codex --worktree), never both.
#!/usr/bin/env bash# bin/herdr-fanout - run from a herdr pane at the repository rootset -euo pipefail[ "${HERDR_ENV:-}" = 1 ] || { echo "run this inside a herdr pane" >&2; exit 1; }
TASKS=(rate-limit webhook-idempotency cache-invalidation)SETUP_CMD="npm ci" # a new worktree has no node_modules and no gitignored .envTEST_CMD="npm test"REPO=$(git rev-parse --show-toplevel)declare -A PANE WTSTARTED=() # only tasks whose agent started get supervised
# 1. One worktree and one implementer per task. Agent names must match# [a-z][a-z0-9_-]{0,31}; the "impl-" prefix keeps them valid and unique.for task in "${TASKS[@]}"; do name="impl-$task" WT[$task]="$REPO-worktrees/$task" # explicit path, reused by the gate below created=$(herdr worktree create --cwd "$REPO" --branch "agent/$task" --path "${WT[$task]}" --no-focus) PANE[$task]=$(printf '%s\n' "$created" | jq -r '.result.root_pane.pane_id') herdr pane run "${PANE[$task]}" "$SETUP_CMD; echo SETUP_EXIT=\$?" setup=$(herdr pane wait-output "${PANE[$task]}" --regex 'SETUP_EXIT=[0-9]+' --timeout 600000 \ | jq -r '.result.matched_line') || setup="SETUP_EXIT=timeout" [ "$setup" = "SETUP_EXIT=0" ] || { echo "!! $task: setup failed ($setup)"; continue; } herdr agent start "$name" --kind claude --pane "${PANE[$task]}" --timeout 60000 || { echo "!! $task: agent not ready" herdr agent read "$name" --source recent-unwrapped --lines 40 || true continue } herdr agent prompt "$name" "Task: $task (see docs/tasks/$task.md for the acceptance criteria).Stay inside this worktree. Write the failing test first, make it pass, run $TEST_CMD,then commit on this branch and stop. Do not touch code outside this task." # A prompt without --wait only acknowledges the keystrokes. Confirm the turn began, # or step 3 could see the old "idle" and gate unchanged code. herdr agent wait "$name" --until working --until blocked --timeout 30000 >/dev/null || { echo "!! $task: prompt not picked up" herdr agent read "$name" --source recent-unwrapped --lines 40 || true continue } STARTED+=("$task")done
# 2. A reviewer in a split of the primary checkout.split=$(herdr pane split --current --direction down --no-focus)herdr agent start reviewer --kind codex --pane "$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id')"
# 3. Supervise: park until each implementer settles, then gate and review.for task in "${STARTED[@]}"; do name="impl-$task" status=$(herdr agent wait "$name" --until done --until idle --until blocked \ --timeout 1800000 | jq -r '.result.agent.agent_status') || status=timeout if [ "$status" != done ] && [ "$status" != idle ]; then echo "!! $task: $status - needs a human" herdr agent read "$name" --source recent-unwrapped --lines 40 || true continue fi # Independent gate: run the tests in a fresh pane of the same worktree. gate=$(herdr pane split "${PANE[$task]}" --direction down --cwd "${WT[$task]}" --no-focus \ | jq -r '.result.pane.pane_id') herdr pane run "$gate" "$TEST_CMD; echo GATE_EXIT=\$?" result=$(herdr pane wait-output "$gate" --regex 'GATE_EXIT=[0-9]+' --timeout 900000 \ | jq -r '.result.matched_line') || result="GATE_EXIT=timeout" echo "== $task: $result" [ "$result" = "GATE_EXIT=0" ] || continue herdr agent prompt reviewer "Review branch agent/$task against main: git diff main...agent/$task.Report only defects as file:line - failing scenario - fix. Do not edit files." \ --wait --timeout 600000 || { echo "!! $task: reviewer did not settle - read the reviewer pane"; continue; } herdr agent read reviewer --source recent-unwrapped --lines 80 || truedoneFour details carry the design:
- Set up, then start. Each worktree runs
SETUP_CMDfirst, and a failed install or anagent startthat is not ready skips that task instead of stopping the script. - Prompt everyone, then wait. Step 1 prompts without
--wait, so all three implementers work at once. A prompt without--waitonly confirms that herdr typed the text, and a standaloneagent waitreturns at once if the status already matches. So step 1 waits--until workingbefore moving on. Otherwise step 3 could read the pre-promptidleand gate unchanged code. Step 3 then waits on the implementers in order, which costs nothing because they run in parallel. - The orchestrator does not trust the implementer’s own “tests pass”. The gate runs
TEST_CMDin a separate pane and reads the exit code from the pane’s output withpane wait-output, a command that knows nothing about agents. The split gets--cwdwith the worktree path, because without it the new pane follows the configurableterminal.new_cwdpolicy and could run the tests somewhere else. --timeouteverywhere, and an||after every wait.agent waitandpane wait-outputwait forever without a timeout. On timeout, and on errors such asagent_prompt_stalledoragent_blocked, herdr prints JSON to stderr and exits with status 1, whichset -ewould turn into an aborted run. So every wait, everyagent startand the reviewer prompt has an||branch that logs the task and moves on, and diagnosticagent readcalls end in|| true. The one exception is deliberate: if the reviewer does not start in step 2, the script stops before gating anything.
How do you verify fleet output without reading every diff?
Section titled “How do you verify fleet output without reading every diff?”The script above already separates the three checks that matter. Make them explicit before you scale beyond three workers:
| Gate | What proves it | Who owns it |
|---|---|---|
| Behaviour | Acceptance criteria written before the run, turned into a failing test the implementer must make pass | Whoever wrote the task; see acceptance criteria |
| Tests and build | TEST_CMD run by the orchestrator in its own pane, exit code checked, then the same suite in CI | The orchestrator locally, CI as the source of truth |
| Review | A reviewer agent from a different vendor than the implementer (here Codex reviewing Claude Code), findings as file, line and scenario | The reviewer agent, then a human who reads findings rather than the full diff |
| Merge | The pull request with its evidence: test run, reviewer findings, what changed | A human signs off; see the evidence bundle |
herdr’s part is narrow and useful: it tells you reliably when each gate can start, and agent read gives you the transcript to attach. It never proves the code is correct. That is the job of the tests, the reviewer and CI. For the review stage in depth, see AI review of agent pull requests.
Run agents on another machine or in a cloud sandbox
Section titled “Run agents on another machine or in a cloud sandbox”herdr keeps panes alive on a background server, so a closed laptop lid or a dropped SSH connection stops nothing. Two features extend that across machines:
-
Saved SSH machines. Add a machine once with
herdr machine add workbox --label "Build machine". Then prefix any API command with--machineto run it there, without an open herdr window:Terminal window herdr --machine "Build machine" agent listherdr --machine "Build machine" agent prompt w1:p1 "review this change"The label must match a saved machine exactly (case-sensitive). IDs and agent names belong to one server, so selecting a machine in the TUI does not retarget CLI commands running in an existing pane. Update herdr on both machines; a failed remote command never falls back to local.
-
E2B sandboxes. E2B publishes a herdr plugin that uploads your current checkout, uncommitted changes included, into a fresh E2B cloud sandbox, and can race several agents on the same task and grade them with one held-out check:
Terminal window herdr plugin install e2b-dev/herdr-e2b-sandboxIt needs herdr 0.7.0 or later, Node.js 22 or later,
jq, thee2bCLI and an E2B API key. In an interactive terminal,herdr plugin installshows a trust preview before it installs (--yesskips it); read it, because the plugin handles your agent credentials. See agent sandboxes for how E2B compares with other isolation options.
After a server or machine restart, herdr restores the saved layout and can resume supported agent sessions through their integrations. The original processes do not survive the restart. For phone access to the same fleet, see remote and mobile clients.
Script herdr through the socket API
Section titled “Script herdr through the socket API”The CLI is a client for a local socket API: newline-delimited JSON over a Unix domain socket (a named pipe on Windows), one request per line, responses matched by id. The default socket is ~/.config/herdr/herdr.sock; named sessions get their own. Reach for the raw API only when you need a long-lived event stream, for example to react to every blocked agent instead of polling:
{"id":"sub_1","method":"events.subscribe","params":{"subscriptions":[{"type":"pane.agent_status_changed","pane_id":"w1:p1","agent_status":"blocked"}]}}Generate the contract from your installed binary instead of copying method names from any article, this one included:
herdr api schema --json > herdr-api.schema.jsonClients should ignore unknown fields and treat an unsupported method as a normal error, because the server advertises what it supports.
What breaks when you run a herdr fleet?
Section titled “What breaks when you run a herdr fleet?”agent start fails with agent_pane_busy. The target pane is not at an idle shell prompt: a command, editor or agent owns the foreground. Start agents only in panes you just created, or stop what is running there (herdr agent send-keys <name> ctrl+c or herdr pane send-keys <pane> ctrl+c) and retry. agent start never creates layout for you.
agent start returns agent_not_ready. The agent opened on a trust or approval screen in the new worktree, and detection reported blocked during startup. Read the pane, answer with agent send-keys, then prompt. The script above treats this as a skipped task instead of aborting the whole run under set -e.
agent start fails with invalid_agent_name or agent_name_taken. Names must match [a-z][a-z0-9_-]{0,31} and be unique among live agents. Derive the name once from the task slug, as impl-$task does, and address every later command by that name. If a slug can contain uppercase letters, dots or slashes, normalise it before you start the agent, not after.
Your orchestrator waits forever. A wait without --timeout has no default limit. Put a timeout on every agent wait, agent prompt --wait and pane wait-output, and treat exit status 1 as “go and look”.
The prompt was sent twice. A timeout or agent_prompt_stalled does not prove the prompt was not delivered. Run agent read before you retry.
agent prompt returns agent_blocked. The agent is already on an approval or question screen, so herdr refuses to type into it. Read the pane, decide, and answer with agent send-keys, then prompt again.
An agent sat on a permission prompt while the sidebar said idle. That is the screen-manifest fallback. Run herdr agent explain <name> to confirm, then herdr server update-agent-manifests to fetch the latest rules. For a recurring prompt shape, report it upstream with the --json explain output.
agent read --lines 400 returns agent_not_idle. Claude Code and OpenCode keep history in the alternate screen, and herdr can page through it only while the agent is idle. Wait for idle, or read --source visible. For long answers, tell the agent to write the result to a file and read the file.
Ports, .env and the dev database collided anyway. Worktrees isolate files only, and a new worktree starts without installed dependencies or gitignored env files, so run your setup step (SETUP_CMD above) before the agent and before any gate, or a setup failure reads as GATE_EXIT=1. Three implementers running a dev server on the same port fight regardless of the multiplexer. Give each worktree its own port block, or use a sandbox per agent; see running agents in parallel.
The fleet outran your review. Three workers produce three branches at once. The accepted rate is what counts, so size the fleet to what your gates and reviewers clear; see team parallelism and cost optimization for the quota side.