Use this page to set up a machine where agents run long or recurring SRE jobs (triage sweeps, drift plans, report drafts) in the background, where a human can watch them and step in. herdr is a terminal workspace manager for AI coding agents: like tmux, a background server owns real terminals that survive detach and SSH drops, and it also detects agents in panes and shows whether each one is working, blocked, done or idle.
Run it on a machine your organization has approved for AI tooling and for the data the jobs will touch.
Install
curl -fsSL https://herdr.dev/install.sh | sh
herdr --version
Other install methods (Homebrew, mise, Nix, Windows) are on the install docs. Update a direct install with herdr update.
Install the Claude integration so sessions restore natively:
herdr integration install claude
herdr integration status
Learn the model in two minutes
| Concept | What it is | Use it for |
|---|---|---|
| Session | A persistent background server | Usually just the default one |
| Workspace | Project-level container | One per agent role or job family, for example jobs: triage |
| Tab | A layout inside a workspace | One per job run |
| Pane | A real terminal | The job's process |
| Agent | A recognised coding agent in a pane | Lifecycle state in the sidebar |
Run herdr to attach. Detach with ctrl+b then q; everything keeps running. herdr server stop stops everything. Config lives in ~/.config/herdr/config.toml; print defaults with herdr --default-config.
The CLI talks to the server over a local socket and returns JSON, so scripts read IDs from responses instead of guessing them. Discover commands with herdr pane, herdr agent, herdr workspace and herdr tab (each prints its subcommands). The full reference is the CLI docs.
Give every job its own tab and log
The pattern: one workspace per job family, one tab per run, the job runs through a small wrapper that lowers priority and tees output to a log file.
#!/usr/bin/env bash
# ~/bin/agent-job: run one low-priority job in a new herdr tab, logged to ~/agent-jobs/<id>/
set -euo pipefail
title="$1"; shift # usage: agent-job "<title>" <command...>
id="$(date +%Y%m%d-%H%M%S)-$RANDOM"
dir="$HOME/agent-jobs/$id"; mkdir -p "$dir"
printf '%q ' "$@" > "$dir/cmd"
cat > "$dir/run.sh" <<RUN
#!/usr/bin/env bash
cd "$PWD"
echo "=== $title \$(date -u +%FT%TZ) ==="
nice -n 19 ionice -c3 bash -c "\$(cat '$dir/cmd')" </dev/null 2>&1 | tee -a "$dir/output.log"
echo "\${PIPESTATUS[0]}" > "$dir/exit_code"
echo "=== finished exit=\$(cat "$dir/exit_code") ==="
RUN
chmod +x "$dir/run.sh"
ws="$(herdr workspace list | jq -r '.result.workspaces[] | select(.label=="agent jobs") | .workspace_id' | head -n1)"
if [ -z "$ws" ]; then
ws="$(herdr workspace create --cwd "$HOME" --label "agent jobs" --no-focus | jq -r '.result.workspace.workspace_id')"
fi
pane="$(herdr tab create --workspace "$ws" --cwd "$PWD" --label "${title:0:60}" | jq -r '.result.root_pane.pane_id')"
herdr pane run "$pane" "bash $dir/run.sh"
echo "{\"id\":\"$id\",\"pane\":\"$pane\",\"dir\":\"$dir\"}"
-
nice -n 19andionice -c3keep jobs from slowing the human's work. -
</dev/nullmeans a job can never sit waiting for keyboard input. - Output goes to a file, so results survive the pane being closed.
- Cap concurrency (for example three running jobs) and add quiet hours if the machine is shared.
Run a headless Claude job
agent-job "nightly drift summary" \
claude -p "$(cat ~/prompts/drift-summary.md)" \
--output-format text \
--permission-mode plan \
--max-turns 5 \
--disallowedTools "Bash(git push:*)" "Bash(terraform apply:*)" WebFetch
- Prefer
--permission-mode plan(read and propose only) or an explicit--allowedToolslist. Never use the skip-permissions flag on a machine with production credentials. - Keep prompts in files under version control so you can see exactly what each job was told.
- Secrets never go in the prompt. Redact inputs first; see Claude Code for SRE.
Watch and read jobs
herdr agent list # agents and their states
herdr pane read <pane-id> --source recent --lines 50
herdr pane wait-output <pane-id> --match "=== finished" --timeout 600000
tail -n 50 ~/agent-jobs/<id>/output.log
cat ~/agent-jobs/<id>/exit_code
For an interactive agent instead of a one-shot job, start it in an existing shell pane and prompt it:
pane="$(herdr pane split --current --direction right --cwd "$PWD" --no-focus | jq -r '.result.pane.pane_id')"
herdr agent start reviewer --kind claude --pane "$pane"
herdr agent prompt reviewer "Review the diff on this branch and list only actionable findings." --wait --timeout 300000
herdr agent read reviewer
A blocked state means the agent is waiting on an approval prompt. A human answers it; scripts should not.
Schedule recurring jobs
# m h dom mon dow command (crontab -e)
30 6 * * 1-5 $HOME/bin/agent-job "daily triage" $HOME/sre-automation/bin/daily.sh >/dev/null 2>&1
0 15 * * 5 $HOME/bin/agent-job "friday status" $HOME/sre-automation/bin/friday-status.sh >/dev/null 2>&1
Clean up
herdr tab list --workspace <workspace-id>
herdr tab close <tab-id>
find ~/agent-jobs -maxdepth 1 -mtime +30 -type d -exec rm -rf {} + # keep 30 days of logs
Pitfalls
- Running jobs at normal priority on the machine you work on. Use
nice. - Jobs that need a credential prompt. Load short-lived credentials before the job, or the pane hangs.
- Agents inside herdr controlling other panes without being asked. Check
HERDR_ENV=1and only control herdr when the task says so. - Logging secrets. Anything a job prints lands in
output.log.