Trying herdr instead of tmux
I have been using tmux for a long time -- see my earlier posts on tmux sessions and naming Claude Code windows. Lately I tried herdr, a terminal workspace manager built specifically for running AI coding agents, and this post collects my setup.
Why herdr
tmux is a general-purpose multiplexer; herdr is opinionated about agents. It detects which agent is running in each pane (Claude, Codex, pi.dev, ...), tracks whether a pane is working, blocked or idle, and groups panes into workspaces and tabs. That agent awareness is the reason to use it over plain tmux. On the left below, a workspace with its sidebar of spaces, the tab bar and a shell pane; on the right, the sidebar's agents panel listing every running agent and its live status:

One more thing tmux cannot do: herdr supports image paste, which doesn't work for me with tmux and Alacritty at all.
Configuration
herdr reads ~/.config/herdr/config.toml.
After editing it, the running server picks up changes without a restart:
$ herdr server reload-config {"result":{"diagnostics":[],"status":"applied","type":"config_reload"}}
A minimal config:
[theme] name = "catppuccin-latte" [keys] prefix = "ctrl+a" # cycle between agent sessions next_agent = "ctrl+alt+n" previous_agent = "ctrl+alt+p" # prefix+up/down switch workspaces previous_workspace = "prefix+up" next_workspace = "prefix+down" # line-by-line scrollback / copy mode copy_mode = "prefix+[" [ui.sound] enabled = false
Theming
The interesting part is not which theme I picked, but that herdr is themeable at
all. It ships with a handful of built-in themes -- catppuccin (the default),
tokyo-night, dracula, nord, gruvbox, one-dark, solarized,
kanagawa, rose-pine, vesper -- plus a terminal theme that just
inherits your terminal's own palette, and you can override individual color
tokens on top of any of them.
Prefix key
Like in tmux, I rebind the prefix from Ctrl-b to Ctrl-a. The reasoning is
the same one I described in my tmux sessions post:
keeping the local prefix on Ctrl-a avoids the clash when I SSH into another
machine, where the multiplexer is still on the default Ctrl-b.
Scrolling back through output
With herdr owning the mouse (mouse_capture = true, the default) the scroll
wheel scrolls the focused pane's scrollback. For keyboard-driven scrolling,
Page-Up / Page-Down work out of the box; for line-by-line I bind copy mode
(unbound by default):
That gives a tmux-style scroll/select mode -- navigate with j / k (the
arrows do not auto-repeat, see Downsides), search with /, Esc to leave.
There is also edit_scrollback (prefix+e), which dumps the pane's
scrollback into $EDITOR. It is great for a shell, but useless for an agent
panel: Claude's TUI is constantly redrawn, so the dump is a mess of repaint
escapes rather than a clean transcript.
Disabling the attention sound
herdr plays a sound when an agent becomes blocked and needs attention. I found
it annoying, so [ui.sound] enabled = false turns all sounds off.
Naming tabs after the running program
In my tmux setup I auto-renamed
windows based on the running command. herdr has no built-in automatic-rename,
but it exposes HERDR_TAB_ID inside every pane and a CLI to rename a tab, so
the same idea works from zsh hooks.
The trick: rename the tab to the foreground program on preexec, and back to
the shell name (zsh) on precmd. Using add-zsh-hook (instead of redefining
preexec/precmd) keeps it independent of any existing tmux hooks, so both
can run in parallel:
if [[ -n "$HERDR_TAB_ID" ]]; then autoload -Uz add-zsh-hook # name the tab after the running program (first word, no path) _herdr_preexec() { herdr tab rename "$HERDR_TAB_ID" "${${1%% *}:t}" >/dev/null 2>&1; } # back to the shell name when sitting at the prompt _herdr_precmd() { herdr tab rename "$HERDR_TAB_ID" "${SHELL:t}" >/dev/null 2>&1; } add-zsh-hook preexec _herdr_preexec add-zsh-hook precmd _herdr_precmd fi
The [[ -n "$HERDR_TAB_ID" ]] guard makes the whole block a no-op outside
herdr, so a machine that runs both tmux and herdr is fine.
Renaming a workspace
herdr names each workspace ("space" in the sidebar) after its folder -- the
basename of the directory it was opened in. When that is not descriptive enough
(several checkouts of the same repo, a generic src directory, ...), rename
it. There is a keybinding, rename_workspace (prefix+shift+w), which
prompts inline, or the CLI:
That sets a custom_name that overrides the folder-derived label in the
sidebar; the directory on disk is untouched, and the name persists across
restarts. ($HERDR_WORKSPACE_ID holds the current workspace id inside any
pane, so herdr workspace rename "$HERDR_WORKSPACE_ID" ... works from a
shell.) To go back to the folder name, just rename it the empty string.
Moving a tab to another workspace
There is no built-in keybinding to move a tab to another workspace, but herdr
has two pieces that combine nicely: custom command bindings ([[keys.command]])
and herdr pane move. A custom command runs with HERDR_ACTIVE_PANE_ID set
to the pane that was focused when the key was pressed -- so it can act on the
agent tab even though that tab is busy running an agent and you cannot type into
it.
I bind prefix+m to a small script that pops up an fzf
picker of workspaces and moves the focused tab to the chosen one:

[[keys.command]] key = "prefix+m" type = "pane" # opens a temporary pane so fzf has a TTY command = "~/.local/bin/herdr-move-tab"
#!/bin/sh [ -n "$HERDR_ACTIVE_PANE_ID" ] || { echo "no active pane" >&2; sleep 2; exit 1; } ws=$(herdr workspace list \ | jq -r '.result.workspaces[] | "\(.workspace_id)\t\(.label // .workspace_id)"' \ | fzf --prompt="move tab to workspace > " --with-nth=2 --delimiter='\t' \ | cut -f1) [ -n "$ws" ] || exit 0 # herdr refuses to move a pane out of a zoomed tab, so un-zoom it first. herdr pane zoom "$HERDR_ACTIVE_PANE_ID" --off >/dev/null 2>&1 herdr pane move "$HERDR_ACTIVE_PANE_ID" --new-tab --workspace "$ws" --focus
The one gotcha worth highlighting: herdr pane move rejects the move with
reason: zoomed_tab if the source tab is zoomed, hence the pane zoom --off
line before the move.
Downsides
It is not all rosy. The things that bit me, roughly in order of annoyance:
Mouse: paste versus clean copy. This is the big trade, and I went back and
forth on it. herdr defaults to mouse_capture = true so it can do
click-to-focus, drag-to-resize, sidebar clicks, wheel-scroll a pane and --
crucially -- pane-aware selection that copies just that one pane. The cost:
the terminal no longer runs its own primary-selection paste, so plain
middle-click stops pasting. You paste with Shift+middle-click instead
(Alacritty bypasses the application's mouse grab while Shift is held), and
decades of plain-middle muscle memory take a bit to retrain.
I spent a couple of days on [ui] mouse_capture = false, which hands the
mouse back to the terminal and restores plain middle-click paste. It turned out
to be the worse side of the trade: you lose click-to-focus, drag-resize, sidebar
clicks and wheel scrolling -- and, the dealbreaker, selection then runs on the
terminal's whole grid, so dragging across a pane also grabs the sidebar, the
│ / ▕ separators and any neighbouring pane. Copying a clean block of
output became impossible. So I settled back on mouse_capture = true .
Idle CPU. The client redraws whatever the focused pane emits. A Claude Code pane animates (spinner, token counter) even while waiting for input, so the renderer sits at a few percent CPU rather than zero.
No automatic tab renaming. Unlike tmux's automatic-rename, herdr keeps a
tab's name once set, so the zsh hooks above are doing the work tmux would do for
free.
No full-screen / zen mode. toggle_sidebar (prefix+b) only collapses
the left sidebar to a roughly one-character sliver -- it never fully disappears
-- and the top tab bar is always on. There is no keybinding or config to show
only the pane content. zoom (prefix+z) maximizes a pane within its tab,
so with one pane per tab it does nothing visible.
Arrow keys do not auto-repeat. Holding an arrow key in a scroll/resize mode
does nothing past the first press. herdr enables the kitty keyboard protocol,
under which a held key is reported as a Repeat event -- and herdr only acts
on the initial Press, dropping the repeats. There is no way to turn the
protocol off in herdr (it pushes the enhancement flags unconditionally) or in
Alacritty. Plain character keys are unaffected, so the quick workaround is to
use j / k (and h / l) in copy mode instead of the arrows.
If you want the arrows themselves to repeat, you can override them in Alacritty so they emit the legacy escape sequences -- those arrive as plain presses that herdr does honour:
# ~/.config/alacritty/alacritty.toml [[keyboard.bindings]] key = "Up" chars = "\u001B[A" # and [B / [C / [D for Down / Right / Left
Modified arrows (Shift/Ctrl/Alt) are left alone, so the protocol still applies where it matters.
Prompt occasionally drops to the bare default. Every so often, inside
herdr, my oh-my-zsh prompt renders correctly and then,
after a command, collapses to the plain host% default. It is intermittent,
which smells like a race: oh-my-zsh's async prompt computes segments in the
background and calls zle reset-prompt from a file-descriptor handler, and
that redraw seems to occasionally collide with herdr's own rendering. Disabling
the async prompt inside herdr (add-zsh-hook -d precmd _omz_async_request,
after sourcing oh-my-zsh) makes the prompt synchronous and should remove the
race.
Conclusion
Seeing every active agent at a glance, while keeping most of what I relied on in tmux, is what convinced me to stick with herdr. It comes close enough to my old tmux setup, and the rough edges above are either worked around or minor enough to live with.