I say I moved from tmux to herdr, and that is true for one kind of work. Agent sessions now live in herdr. tmux is still configured in my dotfiles (prefix ctrl+a, in nix/home/terminal.nix), and my own guide, HERDR.md, says tmux stays for servers, SSH and regular work. So this post is about a split, not a replacement. I introduced herdr briefly in the lightning round. Here is what came after.

What tmux did not solve for parallel agents

An agent session is a process I want to leave running and check on by state. I do not want to watch it. tmux keeps the process alive, which is half of that. The other half is knowing which of three OpenCode instances is waiting for an approval, and tmux only gives me window names and an activity flag. To find the blocked one I had to cycle through windows and read screens.

herdr is built around that question. It shows each agent as idle, working, blocked or done in a sidebar, rolls those states up to the workspace, and can resume the exact conversation after a restart. The thesis I am testing is that a multiplexer organised around agent state fits this work better than one organised around windows and panes.

The mental model

My tmux habits map over directly, which is why the move was cheap.

herdr tmux What I put there
Workspace Session One per repo
Tab Window agents, shell, logs
Pane Pane One OpenCode instance per pane
herdr workspace, tab and pane layout with sidebar statesA workspace named after a repo contains three tabs: agents, shell and logs. The agents tab holds two OpenCode panes. A sidebar lists each agent with its state, working or blocked, and the workspace row shows the worst state.Workspace: one repoSidebarworkspace row: blockedagent 1: workingagent 2: blockedA workspace shows theworst state inside it.Tabsagentsshelllogs1Pane: opencodeticket Astate: working2Pane: opencodeticket Bstate: blocked
Layout from HERDR.md. The sidebar reports per-pane state, so I do not have to open the tab to see it.

The OpenCode plugin

herdr integration install opencode writes ~/.config/opencode/plugins/herdr-agent-state.js. It does two things. It reports idle, working, blocked or done to the sidebar, and it records the OpenCode session id so herdr can run opencode --session <id> on restore.

Two properties matter for keeping it working. First, the plugin is generated by the herdr CLI, so it is not repo-owned. My repo does not contain the file. In nix/home/ai-tools/opencode.nix an activation step installs it only if the file is missing and the herdr binary exists at the Homebrew path. Second, that means nothing in my repo pins its content to a version of OpenCode. HERDR.md also warns that an opencode started outside a herdr pane sends no state and will not auto-restore.

The migration risk is OpenCode v2. My handoff plan lists the herdr plugin as generated by herdr v0.9.1 and says to check for a newer herdr and re-run the install if it supports v2. I have not done that yet, so I do not know whether the current plugin works on v2.

The config and the prefix conflict

Homebrew installs herdr ("herdr" in nix/hosts/mac/default.nix), and home-manager writes the config inline in nix/home/mac-extras/platform-tools.nix. A file at config/herdr/config.toml is mentioned in older notes, but it does not exist in the repo now. The inline block is the only source.

The prefix changed twice in the history:

  • 340084f added herdr with ctrl+space, chosen because tmux already owns ctrl+a.
  • 53f792d moved it to ctrl+b. The commit message says ctrl+space collides with the macOS input-source switcher shortcut.

ctrl+b is tmux's stock default and nothing else here uses it, so it stopped fighting the OS. The stale part is documentation. HERDR.md and the lightning round post still show ctrl+space. Below I use the current ctrl+b from the config.

One small fix from the first week was 1776081, which changed split_vertical from pipe to v because herdr does not accept pipe as a key name.

A normal day

cd ~/IdeaProjects/<repo> && herdr
ctrl+b c            # new tab
opencode            # give it the task
ctrl+b d            # detach, the agent keeps working

Then I go back to whatever I was doing. When the sidebar shows blocked or done, I run herdr to reattach and use ctrl+b alt+1 to jump to agent 1. For review, ctrl+b alt+g opens lazygit as a popup and ctrl+b t opens a scratch terminal. ctrl+b alt+n opens the notification feed I built earlier. For parallel work I split with ctrl+b v or ctrl+b minus and start another opencode. ctrl+b g switches workspaces, and the switcher shows the rolled-up state per repo.

What still lives in tmux

Servers, SSH sessions and general shell work. The nix config for tmux is untouched, and the herdr config header says it mirrors tmux habits and does not replace it. I have also not measured how much time this saves, so the claim is about how the work feels, not a number.

Limits and unknowns

  • Session restore is configured (resume_agents_on_restore = true), but my August session notes say I never tested a restart end to end. I have not recorded a test since.
  • The OpenCode v2 plugin question above is open.
  • The state labels come from the plugin. If it stops reporting, the sidebar stops being useful and I would not notice from inside a pane.
  • The docs disagree with the config on the prefix, and I have not fixed them yet.