A multiplexer backend is the surface that hosts an agent session -- the terminal multiplexer that owns the pane an AI tool runs in. By default Open Orchestrator uses tmux. You can opt in to herdr as an alternative backend, in which case owt becomes the orchestration brain and herdr becomes the rendering surface.
herdr support is purely additive: tmux remains the default, and nothing changes for users who never opt in.
tmux vs herdr
| Aspect | tmux (default) | herdr backend |
|---|---|---|
| Default UX | Unchanged | Unchanged for everyone else |
| Multiplexer UI | tmux conventions | herdr's window manager + sidebar |
| Mouse-native | No | Yes |
| Agent state in UI | Status DB only | Status DB + sidebar via pane.report_agent |
| Remote attach | tmux attach | herdr --remote |
| Install footprint | tmux only | tmux or herdr (still optional) |
When to use each
- Use tmux for the default experience, and for CI and headless runs.
- Use herdr when you want a mouse-native window manager with a live agent sidebar, or a richer remote-attach experience for
owt newworktrees.
Installing herdr
herdr is not bundled with Open Orchestrator -- install it yourself, then opt in. tmux remains the default until you do.
# Install herdr
curl https://herdr.dev/install.sh | sh
# Verify the daemon is running and the socket exists
herdr status
ls $XDG_CONFIG_HOME/herdr/herdr.sockWhen [backend] mode = "auto", owt probes for herdr with which herdr plus a socket ping and silently falls back to tmux if it is not reachable -- so installing herdr is the only step needed to "turn it on" in auto mode.
Opting In
One-off, per invocation:
owt new "Refactor billing" --herdr
owt attach my-feature --herdrForce tmux on a single command (overrides config):
owt attach my-feature --tmuxProject-wide via .worktreerc.toml:
[backend]
mode = "auto" # tmux | herdr | auto
herdr_session = "default" # named herdr session (selects which socket)
# herdr_socket = "/custom/path/to/herdr.sock" # only if you've moved the socketThe named session resolves to a socket path:
default->$XDG_CONFIG_HOME/herdr/herdr.sock- any other name ->
$XDG_CONFIG_HOME/herdr/sessions/<name>/herdr.sock
Selection Precedence
owt resolves the backend in this order:
--herdr/--tmuxon the command line (per invocation)[backend] modein.worktreerc.tomltmuxas the safe default
mode = "auto" reaches for herdr first (via which herdr plus HerdrClient.ping()) and falls back to tmux when herdr is not installed or its socket is not reachable.
You can also set OWT_BACKEND_MODE=tmux in the environment to force tmux for a single shell.
What owt Sends to herdr
Each owt worktree maps to one herdr workspace; the agent runs in that workspace's root pane.
| owt action | herdr RPC |
|---|---|
owt new --herdr | workspace.create + pane.send_text |
owt send | pane.send_text (per worktree's recorded backend) |
owt attach --herdr | herdr agent attach <pane_id> (exec) |
| status update (hooks) | pane.report_agent (non-fatal) |
owt delete | pane.close + workspace.close |
Status DB Is the Source of Truth
owt writes the canonical agent state into SQLite (~/.open-orchestrator/status.db). When herdr is enabled, the tracker also forwards each write to the backend via report_agent_state(session, state, message) so herdr's sidebar reflects the same picture.
Status forwarding is best-effort. If the herdr call fails, the SQLite write is unaffected and the control plane (owt) keeps working -- the sidebar just falls behind.
Per-Worktree Backend Records
Each status row carries four fields, written at create-time, so later commands route correctly without re-passing flags:
| Field | Meaning |
|---|---|
session_type | "worktree" (default) or "branch" (in-place branch session) |
backend_kind | "tmux" or "herdr" -- picked by the launcher |
backend_session_id | tmux session name OR herdr pane id |
backend_meta | JSON with workspace_id, socket, and herdr_session (herdr) |
Once a worktree is created with --herdr, follow-up commands (owt attach, owt send, owt switch, owt delete) read this row and dispatch to the right adapter -- including the exact herdr socket path for custom deployments. You only need --herdr / --tmux again when you want to override what was recorded.
Forcing a Backend on owt attach
When the forced backend (--tmux / --herdr) differs from the recorded one, owt re-resolves the session via backend.session_for(name) on the forced backend rather than coercing the recorded id. tmux session names and herdr pane ids are different shapes, so coercing them would silently misroute. If the forced backend has no session for that worktree, owt errors clearly:
No herdr session for 'my-feature'. Recorded as tmux.Headless Behavior
Headless launches (owt new --headless) skip backend resolution entirely -- the detached subprocess never touches a multiplexer. This means a CI configuration with [backend] mode = "herdr" keeps working even when herdr is not installed:
# Works even with no herdr installed.
owt new "Run my batch job" --headlessNote that --herdr is incompatible with --headless (there is no terminal to host); use tmux (the default) for CI.
TUI Prompt Submission
herdr's pane.send_text types text into a pane as text -- it does not synthesize a real Enter key event. TUI agents (pi, claude in TUI mode, droid) read raw stdin and treat a literal \n as "newline in input", not "submit". So owt routes every agent-facing message through a single chokepoint (HerdrBackend._send_line()) that delivers Enter as a carriage return (\r by default).
If your herdr build needs a different terminator, override it per shell with OWT_HERDR_SUBMIT:
# CRLF terminator embedded in pane.send_text.
export OWT_HERDR_SUBMIT='text:\r\n'
# Body via pane.send_text, then a real key event via pane.send_keys.
export OWT_HERDR_SUBMIT='keys:Enter' # most TUIs
export OWT_HERDR_SUBMIT='keys:Return' # some keymaps name it Return
export OWT_HERDR_SUBMIT='keys:C-m' # the literal Enter byteUnknown values (for example OWT_HERDR_SUBMIT=foo:bar) log a warning and fall back to the default. \r / \n escapes are expanded so the variable stays readable in a shell.
Architecture
Call sites depend only on the MultiplexerBackend protocol. AgentLauncher, commands/agent.py (send), commands/worktree.py (new, switch, attach, delete, list), and commands/doctor.py resolve a backend through core/backend_factory.py and never touch TmuxManager directly.
┌─────────────────────────────┐
│ commands/ (owt CLI) │
└──────────────┬──────────────┘
│
┌───────────────▼─────────────────┐
│ MultiplexerBackend (protocol) │
└───────────────┬─────────────────┘
│
┌───────────────────┴─────────────────────┐
│ │
┌──────────▼──────────┐ ┌────────────▼───────────┐
│ TmuxBackend │ │ HerdrBackend │
│ → TmuxManager │ │ → HerdrClient (RPC) │
└─────────────────────┘ └────────────────────────┘The concrete adapters live behind core/tmux_backend.py (wraps TmuxManager) and core/herdr_backend.py (wraps HerdrClient, JSON-RPC over a Unix socket). The factory at core/backend_factory.py is the single resolution point.
Known Limitations
- Plan mode and automated mode are honored only by tmux. They are propagated via the agent command and the
OWT_AUTOMATED=1env var, which herdr'spane.send_textdoes not currently set on the pane shell.
The two flows that drive agent sessions are the standard owt new worktree (fully herdr-aware) and the Control Plane (owt); both respect the configured backend.
Troubleshooting
herdr is not installed or its socket is not reachable
- Install it:
curl https://herdr.dev/install.sh | sh - Verify the daemon is running:
herdr status - Confirm the socket exists:
ls $XDG_CONFIG_HOME/herdr/herdr.sock - If you use a named session, point
[backend] herdr_sessionat it
--herdr is incompatible with --headless
- Headless mode has no terminal to host. Use
--tmux(the default) for CI.
Multiple herdr daemons (for example, work and personal)
[backend] herdr_session = "work"selects~/.config/herdr/sessions/work/herdr.sock.
Falling back to tmux temporarily
- Pass
--tmuxon any command to override config for that invocation. - Or set
OWT_BACKEND_MODE=tmuxin the environment for a single shell.
See Also
- Tutorial: herdr Backend -- A hands-on walkthrough of the herdr workflow
owt attach-- Hand off to an agent session via the active backendowt new-- Create a worktree (add--herdrto host it in herdr)- Configuration -- The
[backend]block and other defaults - Branch Mode -- Run an agent without creating a worktree