Skip to content

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.

  • A working install with the per-agent integrations and the herdr agent skill
  • An accurate model of when blocked is 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

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 needUseWhy
One or two Claude Code sessionsClaude Code’s own --worktree and agent viewNo new binary; see running agents in parallel
A mixed fleet (Claude Code, Codex, Cursor) in one terminal, scriptableherdr24 agent kinds, one CLI for all of them
Zero new dependencies, or a security team that will not approve a new binarytmuxSee tmux for agent fleets
A desktop app with a diff and review UI per taskConductor, Orca and similarSee 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.

herdr is one Rust binary. Install it from the official script or a package manager, then add the integration for each agent you run.

  1. 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 herdr

    On 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.

  2. Install the integration for each agent you use. Each tab below is the same herdr integration install command with a different agent name:

    Terminal window
    herdr integration install claude

    Writes hooks/herdr-agent-state.sh into ~/.claude (or CLAUDE_CONFIG_DIR) and adds a SessionStart hook to settings.json. The directory must already exist, so run claude once first.

    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.

  3. 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

    -g installs it for every supported agent on the machine; omit it to install into the current project. herdr --skill prints the copy that matches your installed binary.

  4. Verify the setup:

    Terminal window
    herdr integration status
    herdr

    herdr opens the TUI. Split a pane, run claude or codex in it, and the sidebar shows the agent and its state. ctrl+b q detaches the client; running herdr again 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 authorityAgents (herdr 0.9.1 docs)
Lifecycle hooks or plugin, when the integration is installedPi, 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 integrationAmp, Kiro CLI, Maki, Muse
Detected, less thoroughly testedGemini 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 idle can return while an agent is really waiting for an answer. Always read the pane before you act on idle.
  • Codex can fall back to unknown instead of idle. unknown means 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 = false if your policy forbids that.

When a pane shows the wrong state, ask herdr why:

Terminal window
herdr agent explain reviewer
herdr agent explain --file screen.txt --agent codex --json

The 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.

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:

Terminal window
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 300000
herdr agent read reviewer --source recent-unwrapped --lines 120

What 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:

Terminal window
herdr agent wait reviewer --until blocked --timeout 120000
herdr agent read reviewer --source recent-unwrapped --lines 80
herdr agent send-keys reviewer esc

Fan 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 root
set -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 .env
TEST_CMD="npm test"
REPO=$(git rev-parse --show-toplevel)
declare -A PANE WT
STARTED=() # 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 || true
done

Four details carry the design:

  • Set up, then start. Each worktree runs SETUP_CMD first, and a failed install or an agent start that 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 --wait only confirms that herdr typed the text, and a standalone agent wait returns at once if the status already matches. So step 1 waits --until working before moving on. Otherwise step 3 could read the pre-prompt idle and 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_CMD in a separate pane and reads the exit code from the pane’s output with pane wait-output, a command that knows nothing about agents. The split gets --cwd with the worktree path, because without it the new pane follows the configurable terminal.new_cwd policy and could run the tests somewhere else.
  • --timeout everywhere, and an || after every wait. agent wait and pane wait-output wait forever without a timeout. On timeout, and on errors such as agent_prompt_stalled or agent_blocked, herdr prints JSON to stderr and exits with status 1, which set -e would turn into an aborted run. So every wait, every agent start and the reviewer prompt has an || branch that logs the task and moves on, and diagnostic agent read calls 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:

GateWhat proves itWho owns it
BehaviourAcceptance criteria written before the run, turned into a failing test the implementer must make passWhoever wrote the task; see acceptance criteria
Tests and buildTEST_CMD run by the orchestrator in its own pane, exit code checked, then the same suite in CIThe orchestrator locally, CI as the source of truth
ReviewA reviewer agent from a different vendor than the implementer (here Codex reviewing Claude Code), findings as file, line and scenarioThe reviewer agent, then a human who reads findings rather than the full diff
MergeThe pull request with its evidence: test run, reviewer findings, what changedA 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 --machine to run it there, without an open herdr window:

    Terminal window
    herdr --machine "Build machine" agent list
    herdr --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-sandbox

    It needs herdr 0.7.0 or later, Node.js 22 or later, jq, the e2b CLI and an E2B API key. In an interactive terminal, herdr plugin install shows a trust preview before it installs (--yes skips 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.

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:

Terminal window
herdr api schema --json > herdr-api.schema.json

Clients should ignore unknown fields and treat an unsupported method as a normal error, because the server advertises what it supports.

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.