Parity with herdr¶
tmux-agent-wizard targets feature parity with herdr v0.9.3
(socket protocol 22, 2026-09-29) for people who stay on tmux, plus two files from herdr
master (d6b40d4, 2026-10-01): the Codex and Pi manifests, where a Codex at rest reads idle
again; and four manifests from master at 5608de0608 (2026-10-23), see "herdr v0.9.4 and
master's detection changes" below. The reference copies of herdr's own files under
third_party/herdr/ are v0.9.3's, with manifest.rs and the Pi manifest from master
(d6b40d4), and the Claude, Codex, Grok and Antigravity manifests from master
(5608de0608).
taw session snapshot reports protocol 22, as the vendored schema does.
[plugins] compat_herdr_version is 0.9.3. A row labelled with a v0.9.2 change may have
taken it from master (0d5d6f1, 2026-09-27) before the release.
herdr is a standalone multiplexer that owns agent terminals; this project is a tmux
plugin plus one binary, taw, that observes and drives the tmux you already run.
Anything herdr can do that tmux genuinely cannot is listed here as โ, not silently
dropped.
The rule is: parity first; a refinement beyond herdr is made when users need it or it is a simple fix, and every one of them is listed in the README's Beyond herdr and in the popup ledger below.
Rows below describe herdr-equivalent behaviour. Where that behaviour is exposed as a
tawCLI verb, seetaw --help(ordocs/ARCHITECTURE.md's module map) for the exact command.
This file has eight parts. Parity is what matches, and how. Popup ledger maps each herdr and tmux-session-wizard interaction onto the tree popup. herdr v0.9.0 is the status of each item in that release's port set. herdr v0.9.1 to v0.9.3, and master lists what has been taken from later than that. herdr v0.9.4 lists what has been taken from that release. Known divergences is where an implemented verb behaves differently from herdr's. Structural gaps is the short list of things tmux cannot express, each with why and the closest thing you can do instead. Pinned is what is deliberately not being worked on.
Parity¶
| herdr | tmux-agent-wizard | status | how |
|---|---|---|---|
detection: manifests, remote updates, local overrides, agent explain |
same engine, the same 22 bundled manifests, ported unit tests | โ (plus supplements) | herdr's manifest engine (src/detect/) is a faithful, function-by-function port; identity (every name herdr's identity table knows, opencode2 included, OpenCode 2's preview binary, which no manifest lists, so identity.rs names it itself), regions, rules, min_engine_version, and remote-update precedence all match, and herdr's own manifest tests are ported with it, down to the three engine-contract tests that v0.9.2 added. On top of it, and never inside the vendored files, src/detect/supplements/<agent>.toml appends taw's own rules after the upstream ones, so at equal priority upstream still wins: six for Claude Code and one for Codex, listed in README "Beyond herdr". [detection] supplements = false turns them off; taw manifest show <id> tags every rule source: "manifest" or "taw". The watcher's state arbitration follows herdr's pane detection task as v0.9.1 and v0.9.3 have it (v0.9.3 adds the completion guards, see the next row, and a startup check for a Codex that reads unknown, which taw does not take): a 300 ms read cadence (100 ms during a hold), output only making a pane eligible for a read, the hold on the way from working to plain idle (3 confirmations or 700 ms), the 3 s startup grace, exit-idle-first and 5 identity misses; an agent that takes the pane does not read the title or OSC progress the last one left, and a name goes with the agent it was given to. Identity also reads a container runtime's arguments, beyond herdr: when the pane's foreground command is docker, podman, nerdctl, Apple's container or sbx, the watcher reads its exact arguments once for each process (from the system, ps only when that fails) and takes the agent it names, marks the pane @agent_container, and reads the state from the screen as usual; an agent taw started in a container through a profile is marked with the profile (@agent_sandbox), and restore brings it back through that profile, with the id its integration reported from inside; one taw did not start is not resumed, never outside its container (README "Agents in a sandbox"). done and seen follow herdr's completion rule (next row) |
| an agent started inside a container from a pane | recognised from the container runtime's arguments | beyond herdr | when the pane's foreground program is Docker, Podman, nerdctl, Apple's container or sbx, taw reads the arguments it was started with, once for each process that comes to the front of the pane (exactly as the system has them on macOS and Linux, ps's line where it will not say), and takes the agent as the program the container runs: the word after the image, the word after run for sbx, or a command of the script a shell in the container runs. It has to be the agent's own executable, so an image, a container, a directory or a flag value that happens to be called claude is not the agent. The pane is then that agent's: its state is read from the screen with the agent's own rules, @agent_container names the runtime for a status line or a pane border, and the mark goes when the runtime does. The arguments can hold credentials, so nothing of them is kept or logged. Three limits: an agent in a container taw did not start (one you typed by hand) comes back from session restore as a plain shell, its conversation not resumed, and never outside its container (one taw started through a profile does come back, see the next row); only the command line is read, so an agent typed inside docker exec -it box bash is not found; and a container whose script carries on after its agent quits keeps reading as that agent, idle, until the runtime exits (README "Agents in containers") |
| an agent in a sandbox (herdr has none) | taw sandbox, typed in a pane, runs any agent in a container there through a profile, a plain script (taw ships one, default); taw sandbox new and taw sandbox list make and list profiles; taw agent start --sandbox does it for an agent or a script |
beyond herdr | herdr has no sandbox of its own. An agent run in a container of a shell function's making has no mark, and its conversation's id stays inside the container, where no restore can read it. Here ๐ฆ marks the agent in the popup, in a container that taw started, or saw start, and restore brings one taw started back inside its sandbox, never outside, and in its conversation once the agent's hook is installed (README "Agents in a sandbox") |
| states working/blocked/idle/done/unknown, seen semantics | @agent_state + @agent_seen + @agent_acquiring |
โ | five states, and done vs idle follows herdr's completion rule with the guards herdr v0.9.2 added to it (#4457; src/app/actions.rs and src/terminal/state.rs, v0.9.3): a completion (idle after working or blocked) is done when you are not looking at the pane as it happens and idle when you are, and a glance earlier in the turn does not count. unknown to idle is not a completion, and neither is the first idle of an agent taw has just taken (a new agent the watcher finds, one agent start launched or a restore resumed, or one whose session another replaced) until it has been seen working, unless it exited: a trust question asked at startup and answered later is the agent becoming ready. A change of session (Claude's /clear and /resume, Pi's /new) or of agent, whatever the pane's state, marks the pane seen, turns a done back into idle, and drops a notification still waiting out its delay (the row of herdr v0.9.2's #4457 below, and "Known divergences" for where the timing differs). Looking is herdr's active tab with an outer terminal not reported unfocused: the pane's window is the active window of a session with a client attached, and not every terminal attached to that session has reported losing focus. A terminal reports its focus to tmux only when focus-events on asks it to, and taw reads the answer only then, on tmux 3.6+ (verified on 3.6b); otherwise nobody's focus is known, and that counts as looking, as herdr counts a terminal that never reports its focus. done is herdr's unseen idle written as a state of its own, and tmux's focus hooks, or the next read that finds you looking at the pane, are what mark a pane seen |
| sidebar (agents, spaces, rows config, sort) | popup TUI + opt-in status token | โ (translation) | herdr's always-visible sidebar becomes a display-popup (see "Structural gaps") plus the opt-in #{@agent_wizard_status} token for a permanent-but-compact summary. Its two panels become one tree (sessions, then the panes under each, then closed worktrees) in tmux's order; herdr's own flat agent list is f, ordered by [ui] agent_panel_sort. Row templates, rows_by_agent and row_gap are unchanged, and [ui.sidebar.sessions] is accepted as an alias of [ui.sidebar.spaces]. See the popup ledger below for the per-interaction mapping |
| goto / workspace pickers, help, settings overlays | in the popup | โ | one / filter covers both pickers (fzf syntax over the whole tree, matching agents, sessions, paths, branches and each agent's state word and title as its row draws them); ? is help, o is the settings overlay, now called options |
agent view queries (agent.view.set/clear) |
taw agent view set/clear, over the global @agent_wizard_view option |
โ | the whole declarative query: all/any/not over eq/in/exists, the same fields and limits, context values for the current workspace and tab, and sort, which replaces [ui] agent_panel_sort for as long as the view lasts without rewriting the config. --params takes the entire herdr params object for a caller that already has one. It projects the Agents panel and the goto picker's agent rows only: agent list, notifications and detection keep seeing every agent, exactly as in herdr. The agent order a view installs is the Agents panel's, which taw already owns; it is not the workspace ordering under "Pinned" |
toasts (off/herdr/terminal/system), delay_seconds, background-only |
display-message / OSC / OS notifier |
โ (position โ) | all four delivery modes and the background-only gate, which counts a pane whose terminal has lost focus as background (tmux 3.6+ with focus-events on; see the states row) for every mode, where herdr still keeps its own in-app toast off the active tab and sends only its OS and terminal notifications; screen position has no tmux equivalent (see "Structural gaps"). [ui.toast] done_delay_seconds (default 5) adds a separate, longer wait for done toasts; 0 restores herdr's single-delay timing. The popup is sized to leave the status line visible, so herdr delivery lands there with the popup open exactly as it does with it closed |
| sounds, per-agent mute | โ (no bundled sounds) | โ | afplay/paplay/aplay; herdr ships sound files, this project asks for paths (see "Structural gaps") |
open_notification_target |
prefix + O (configurable) |
โ , extended | taw notification open-target, bound to @agent-wizard-notification-key, and reachable inside the popup through the prefix chord. herdr's key focuses the notification that is on screen; taw walks "next up" instead, the same rule as the popup's Tab: the next agent that needs you after the pane you are in, in tmux order, else, with none that does, the next agent of any kind, and it reports not_found (taw: no agents on the status line) only when no pane holds an agent. The latest notification is deliberately not a step of its own: a request is an agent that needs you, so it is in the first group anyway |
| CLI verbs and record shapes | the taw argv CLI, same JSON records |
โ CLI ยท โ socket | most of herdr's socket methods are reachable over argv, with herdr's own field names: 81 of v0.9.3's 102 have a verb, and the other 21 are listed, each with its reason, in docs/COMPAT.md, "Socket methods taw has no verb for" (among them server.ssh_agent.register, which keeps a forwarded ssh agent working in herdr's remote panes, and is not ported). pane.clear has a verb that is taw's own, taw pane clear, which clears less (the row of v0.9.2's #4383 below, and "Structural gaps"). herdr removed pane.graphics.* in v0.9.2. The transport is the gap (see "Structural gaps") |
| plugin lifecycle events (19 names) | tmux hooks + CLI verbs + the watcher | โ | every name in third_party/herdr/reference/event-names.txt fires; docs/COMPAT.md has the table of what triggers each |
| a command of your own on an event (herdr runs a command on an event only through plugins) | [hooks] in config.toml; taw hooks list, test and log |
beyond herdr | herdr tells its events to plugins, which are packages with a manifest, and its toasts stay on the machine you are on. Under [hooks] each key here is an event name and each value a command of yours, or a list of them, run through sh -c in the background with the details in TAW_* environment variables and the event's JSON on standard input. The events are the 19 a plugin hears, agent.blocked, agent.working, agent.idle and agent.done (the moments an agent goes into that state, whether the watcher read it off the screen or its hook reported it) and notification, which fires exactly when a toast would, after the same delay and never for a pane you are looking at, so a push to a phone is one line (README "Notifications on your phone"). pre_close and pre_worktree_remove can say no: taw waits for them, and a non-zero exit, or running past [sessions] hook_timeout_seconds, refuses the close or the removal with the error code hook_refused. A close taw is not asked about is not seen: tmux's own prefix + x and prefix + &, and a process that ends by itself. README "Hooks" has the event table, the variables and taw hooks test |
client.window_title.set / clear |
taw terminal title set / clear |
โ | tmux's set-titles/set-titles-string on the attached client |
session.snapshot |
taw session snapshot |
โ | one bootstrap read of every workspace, tab, pane, layout and agent record, in herdr's own shape, the CLI equivalent of herdr api snapshot. A one-time read in both: herdr's socket page (v0.9.2 on) tells a client to subscribe to events first and then reconcile through authoritative reads such as this one; here the events are the plugin event hooks |
layout.export / apply / set_split_ratio |
taw layout export / apply / set-split-ratio |
โ | tmux's own window_layout string is the export format, select-layout applies it, and resize-pane sets a split ratio |
popup.close |
taw popup close |
โ | the popup is a display-popup, so this closes it on the client the way herdr's method does |
config check, config reset-keys |
taw config check, taw config reset-keys |
โ | reset-keys removes the [keys] table from config.toml and leaves every other byte alone, so the popup falls back to its preset |
[keys] bindings |
[keys] for the popup's own keys |
โ (translation) | herdr binds its whole UI; tmux owns every key outside a popup, so [keys] here binds the 18 actions inside it, and the two prefix keys stay @agent-wizard tmux options. preset = "modal" \| "search" picks the base map, per-action entries override it, and herdr's own action names (goto, workspace_picker, new_workspace, rename_workspace, close_workspace, next_workspace/previous_workspace, settings) are accepted as aliases. Key specs are herdr's grammar. prefix+โฆ is refused, since that chord is tmux's, and open_notification_target's key is read from @agent-wizard-notification-key rather than from this table, so it works inside the popup and out, after either of tmux's two prefixes, prefix and prefix2 |
server reload-config, reload-agent-manifests, update-agent-manifests |
taw config reload, taw manifest reload, taw manifest update |
โ | there is no server holding config, but the pipe-pane watchers are long-lived and each holds its own copy, so these re-read the file in every live watcher rather than restarting anything. The popup re-reads on its next tick regardless |
| agent skill | taw skill |
โ | prints the adapted SKILL.md (include_str!), guarded on $TMUX instead of HERDR_ENV |
| integrations (18 agents) | 18 hook targets | โ | the same 18 targets herdr installs into (Letta, which herdr lists last and calls experimental, is last here too), each writing a taw hook script and registering it in that agent's own config; README "Integrations" has the per-agent table. herdr now has two kinds of support, by herdr (these 18) and by the agent (Command Code, Crush, Kiro CLI, Muse Code and Prime Agent report to herdr themselves, where HERDR_ENV=1 and HERDR_PANE_ID and HERDR_BIN_PATH are set): @agent-wizard-herdr-env, off by default, gives tmux's global environment those three (HERDR_PANE_ID=%calling, a word taw's pane verbs read as $TMUX_PANE), so an agent that follows herdr's guide reports to taw from any pane created after the plugin loads. It is off because HERDR_ENV=1 tells every herdr-aware program it runs inside herdr. No HERDR_SOCKET_PATH (taw has no socket: an agent that reports through the socket reports nothing), and a sandboxed agent cannot reach the host's taw; README "Agents that report to herdr themselves" |
| plugins (manifest, actions, events, panes, startup, trust preview) | โ CLI-driven plugins ยท โ raw-socket plugins | partial | same manifest format, env vars, and trust-preview gate; see docs/COMPAT.md. herdr removed pane.graphics.* in v0.9.2, so a plugin no longer has graphics methods to miss here |
plugin discovery (plugin search, the marketplace page) |
โ | โ | herdr's discovery is a web marketplace listing GitHub repositories tagged with the topic herdr-plugin (third_party/herdr/reference/plugins.mdx); herdr ships no CLI verb for it, so there is no command shape to port and no output to mirror. Searching GitHub from taw would be a new feature rather than parity, and would add a gh dependency (taw goes online only for the reasons the README's "What goes online" lists). Install by spec instead: taw plugin install <owner>/<repo>[/<subdir>][@<ref>]. taw plugin list covers the installed side |
| worktrees | โ | โ | git worktree plus one tmux session per worktree, under [worktrees] directory. --trust-repository (v0.9.0) scopes -c safe.directory=<root> to one request. A worktree whose session is not running is a โ row under its parent session: Enter restarts it, x removes it |
| restore after restart + native agent resume | snapshots + resume | โ | taw session save/restore (a plain restore after a tmux server change also finds the previous server's last layout: README "Beyond herdr"; in a saved session that is open it adds the windows whose agent conversation runs nowhere in the server and changes nothing already open, and with no server it starts one), and [session] resume_agents_on_restore for every integrated agent whose own CLI has a resume flag (all 18, Letta included). pi and omp resume from their saved session path when no session id was reported; herdr prefers the path outright, taw prefers a valid id and falls back to the path, which never changes a pane that already resumes. A command an agent reports for itself wins over taw's own for that agent, each session is resumed in one pane of a restore, a pane whose saved directory is gone is not resumed and keeps its directory and session in later saves: the rows of v0.9.1 and v0.9.2 below. A Claude pane that ran under another profile (CLAUDE_CONFIG_DIR, read from its saved transcript path) is typed CLAUDE_CONFIG_DIR=<profile> claude --resume <id>, with the flags it was started with that set how the session runs (--effort, --model, --permission-mode, --agent, --dangerously-skip-permissions) after the command, with --effort at the level its last turn ran at when its transcript says, and the dry run lists each line it would type |
| a crash of the multiplexer itself | n/a: herdr's server is its own program | taw only (macOS) | when the plugin loads on a new tmux server and the last one on the same socket is gone, taw server started copies that tmux's crash report from ~/Library/Logs/DiagnosticReports into <state>/tmux-crashes/ and tells you once where it is and that taw session restore brings back what it had. It waits up to two minutes for the report, matches it by the dead server's pid, never overwrites a file, and says nothing for a server that was killed or ended with its last session. README "Save and restore" and "Beyond herdr" |
| starting an agent in a pane, and a restore's resume commands | taw agent start, taw session restore |
โ , plus a wait | herdr types into a shell here too: agent start wants the pane at its interactive shell prompt and answers agent_pane_busy at once when it is not, and a restore spawns a shell and sends the resume command without waiting for a prompt. taw waits first, because a shell that is still starting hands what is typed to whatever reads the terminal first, and a startup question, mise's "Trust them?" for one, would be answered by the keys of a command. It waits until the pane's shell is at its prompt by herdr's own idle-shell test (the one the watcher uses: the terminal's foreground group is led by the pane's process, holds nothing else, and the process has a shell's name), then for the screen to stay unchanged for 300 ms, for 15 seconds at most. A shell that does not come to its prompt is left alone: agent start fails with shell_not_ready and types nothing, and a restore says in its plan, and in not_resumed, which pane to run which command in by hand. A pane in a tmux mode (copy mode, the clock, a chooser) or with its input off takes none of what is typed, so both give it up at once, with the reason, and leave it in its mode; agent prompt and agent send-keys refuse it the same way. --timeout is one budget for the whole verb. A restore also reports which windows it made and which it did not, each by the number it had when saved, selects windows by their own ids so that a pane, a window or a session closing under it stops only what it cannot finish, and holds autosave off while it runs (@agent_wizard_restoring) so the tmux hooks cannot save a half-made layout over the snapshot it reads. A pane whose saved directory is gone is typed nothing at all (tmux opens it in your home folder, where herdr starts no shell), and the plan says which directory, with the command to run once it is back; README "Save and restore" |
pane screen history across a restart ([experimental] pane_history) |
opt-in, same key | โ (opt-in) | capture-pane output saved beside the session snapshot and replayed into the restored pane. Off by default in both, for the same reason: a saved screen can hold secrets, tokens and command output, so treat the state directory the way you treat shell history |
| themes (12) + custom tokens | โ | โ | all 12 built-in themes plus [theme.custom] token overrides; auto_switch follows the terminal's light or dark mode on tmux 3.6 and later, with a terminal that reports it (see "Structural gaps") |
| named sessions, detach/attach, ssh attach | tmux itself, and taw app for the machines you name |
โ | sessions, detach and attach are tmux's own job; taw app lists every machine's sessions in one list and puts the terminal on that machine's own tmux, over your ssh (README, "Remote machines") |
Saved machines (herdr machine add) |
taw machine add and taw machine remove, which write [machines] in config.toml: one name = "ssh destination" per line, or [machines.name] with ssh and taw |
โ | taw machine add and taw machine remove edit the config file, and taw machine list and taw machine check read it back |
| Running a command on a machine | taw --machine NAME <verb> |
โ | the verb runs on that machine over ssh and its answer comes back as it was given; the ids are that machine's, and machine_unreachable says when no answer came |
Popup ledger¶
Each interaction herdr's sidebar (or tmux-session-wizard's popup) offers, and what it is here. "kept" means the capability survived the redesign unchanged in substance.
| herdr / session-wizard capability | here | note |
|---|---|---|
| goto picker, workspace picker | / filter |
kept; herdr's Go To shows every agent and terminal as a row of its own since v0.9.2 (#4384), as the tree always has. Go To's state keys (b, w, i and d, with a for every agent) are the state word typed into the filter here, /blocked, since a to e name rows. Go To's Left and Right, which jump between workspaces, are not taken ("Known divergences") |
| navigate workspace up/down | J / K |
kept |
| new / rename / close workspace, close pane | n, r on a header, x on a header, x |
kept |
| rename agent | r on an agent row, then a |
kept |
| new worktree, open / remove worktree | w; Enter / x on a closed-worktree row |
kept |
| settings, help | o, ?; the search preset binds neither, so there they are reached only through [keys] |
kept |
| open notification target | prefix + O inside the popup and outside |
kept, extended: walks next up, the same rule as Tab (the next agent that needs you, else the next agent of any kind, in tmux order), and says taw: no agents when there is none |
| pane menu: split right / down, new tab, close tab, rename | r and the right-click menu (go, rename, close, new worktree); splits, new tab and close tab are tmux's own keys, not the popup's |
kept in part |
agent_panel_sort = spaces / priority |
flat list: spaces (and workspaces, sessions) = tmux order; priority = alias of attention |
semantic difference: herdr's priority (not its default, which is spaces) ranks blocked, done, working, idle, unknown, the newest state change first within each; its key is the state and the time it changed, so it does not tell a blocked agent you have looked at from one you have not. taw's attention (the default for the flat list) also ranks done above working, but ranks idle (a finished agent once looked at, or a quiet one) above working too, and puts a blocked agent you have not looked at ahead of one you have. A finished agent you have looked at does not count as needing you |
herdr: an agent's sidebar row names its kind (its terminal_title token adds the title, which the default rows leave out) |
rows say the agent's pane title, state and elapsed time | kept, extended: falls back to the agent's kind when it has not set a title |
| Spaces panel beside Agents | session headers in one tree; [ui] depth = "sessions" shows the headers alone |
layout change, no capability lost |
| toast in herdr's own window | the tmux status line, which shows taw's bar while the popup is open; a toast lands in it as always | n/a |
| a key that shows or hides the preview | [ui] preview only |
no key: the config turns the preview on or off, and it is drawn while the popup is 100 columns or wider |
session-wizard: sessions or windows list, zoxide, create-or-switch, zoxide add |
[ui] depth; the n picker's three sources; Enter |
covered. Projects that are not running stay out of the main tree, which shows what runs; they are in the picker, and with [ui] projects_in_filter on / also lists them just under the sessions |
| session-wizard: naming modes | [sessions] name, applying tmux's ./: rule instead of session-wizard's lowercasing; running-ness decided by path |
covered, deliberately less strict: lowercasing merges directories tmux would keep apart |
| session-wizard: pre/post create hooks | [sessions] pre_create_hook / post_create_hook, run the way session-wizard runs its own: the value is a command line that may carry flags, the same two arguments, the pre hook runs on every open before a running session is looked for and may print a name and a directory, the post hook runs only for a session just made; no fallback to session-wizard's own options. One addition: a hook that runs longer than [sessions] hook_timeout_seconds (10 by default, 0 for no limit) is stopped, where session-wizard waits as long as the hook takes. taw open and taw session open run the hooks as the picker does, so a pre hook that refuses (a non-zero exit) stops the open with nothing created: taw open says the hook's last line in one line and exits 1, and taw session open and taw open --json fail with the error code hook_refused |
covered |
session-wizard: ctrl-x, preview, t <path> |
x; the picker's candidate list is the directory listing as you type; taw open <path or zoxide term> from a shell, and taw session open <path> for an agent or a script |
covered. The directory sources, project_dirs, session open and taw open are beyond herdr |
session-wizard: Enter on the first row goes back where you were |
the cursor opens on your own row; Enter there just closes the popup (Enter always goes to the row under the cursor); prefix + O goes to what needs you; prefix + L is tmux's own |
kept in part, extended: prefix + O tries what needs you first, never falling back to the previous session; prefix + L remains the way back. Enter on your own row does not try that fallback either: it is one rule, always the row under the cursor |
herdr v0.9.0¶
The port set taken from herdr v0.9.0, the first step past the v0.8.2 baseline. What v0.9.1 to v0.9.3 and master added is in the next section.
| v0.9.0 item | Status | Note |
|---|---|---|
Bundled manifests refreshed (claude, codex, grok) and muse.toml added |
ported | 20 to 21 bundled manifests; identity.rs also ports is_muse_versioned_binary, since muse's launcher execs muse-bin-<version>, which carries no bare muse alias for muse.toml to declare. Provenance notes carry @v0.9.0 |
[ui.sound.agents] muse muted by default |
not needed | herdr's own default config at v0.9.1 mutes only droid (third_party/herdr/reference/default-config.toml), and so does taw's, so there is nothing to defer |
Claude hook template: exit on CURSOR_VERSION / cursor_version |
ported | TAW_INTEGRATION_VERSION went to 2 (3 since v0.9.1) |
| opencode plugin template: child sub-agent sessions report for their root | ported | taw's OpenCode plugin is a port of herdr's own server plugin (version 3), which carries herdr's child-session handling as herdr wrote it; see the next table |
omp template: willContinue guard |
ported | the version went to 2 (3 since v0.9.1) |
worktree create\|open\|remove --trust-repository |
ported | -c safe.directory=<repo root> on every git command of that one request. open makes no git calls at all and accepts the flag for CLI parity |
Config compat: accept and ignore terminal.kitty_graphics, ui.pane_borders, theme.custom.light.*, theme.custom.dark.* |
ported | parsed without warning; see "Structural gaps" for why each has nothing to drive |
pane report-agent / report-agent-session accept options before the pane id, and --option=value |
verified | clap's behaviour already; pinned with a test |
agent explain --file <unreadable> gives agent_explain_file_read_failed |
ported | was the generic not_found |
agent prompt --wait requires an observed working/blocked within 5000 ms, else agent_prompt_stalled |
ported, with two differences | herdr's prompt_agent (src/api/wait.rs at v0.9.3), ported as prompt in src/cli/agent.rs: a prompt sent to an agent that is not working must be followed by working, or a blocked it was not already in, within five seconds, or the verb fails with agent_prompt_stalled (timeout when --timeout is the shorter); an idle or done it was already in does not count. taw reads the agent four times a second where herdr watches its event stream, so a turn that began and ended between two reads is seen only through the @agent_turn stamp of an agent whose hook reports its turns (the first difference: "Known divergences", "agent prompt --wait and a short turn"); and a prompt to an agent in blocked is sent, not refused (the second: "Known divergences", "agent prompt to an agent in blocked") |
agent prompt and agent send-keys refuse a pane whose agent is not there before they send anything: agent_not_found for a target that resolves to no pane, agent_not_ready for a pane that hosts no agent or whose agent has gone from it |
ported | queue_agent_prompt and handle_agent_send_keys (src/app/api/agents.rs at v0.9.3), with herdr's messages ("is no longer the pane foreground process" for a prompt, "is not an active named agent" for keys). runtime_hosts_agent asks of every pane that the live foreground job identifies as the agent, and taw asks the same of every pane with what tmux and the process table give it: the process in front (a name that is this agent's, or another's); for any pane no kind hint alone names, the processes of the foreground job read with their arguments as herdr's identify_agent_in_job reads them (a port, src/detect/job.rs), which have to name the recorded agent (node /path/to/bin/codex is codex; a shell at its prompt, a shell started inside the pane, a pager, an editor and cat are no agent); and for an agent only its hint names, the pid agent start or a session restore recorded, which stands for the agent only while its process is in the foreground of the pane's terminal (a dead pid, a suspended process, a job in the background and a pid the system has given to a program of another pane do not), and otherwise the identification above. taw reads four things beyond herdr's: bash's --rcfile and --init-file take a value, a shell's -O, +O and +o take the name of an option (herdr reads that name as the script), and the npm, pnpm and bun entries of Claude Code (@anthropic-ai/claude-code/cli.js) and Gemini CLI (@google/gemini-cli/dist/index.js) are those agents (README, "Beyond herdr"). The waits ask less, since ending a wait for an agent that is there is the worse mistake: for an agent only its hint names, the recorded pid and the pane's own shell; for any other, only that the pane's own shell is at its prompt and nothing in front identifies as the agent, so a wait for an agent started by hand ends after it exits with no watcher to take its record off. The send is guarded in the tmux invocation that makes it, on the command in front of the pane being the one the check saw, which herdr does not do (its check and its write are two steps with nothing between them). The two codes are new here, and for these two verbs only: the other agent verbs still say not_found. agent_not_running is herdr's code for a wait that ends, and stays that |
herdr v0.9.1 to v0.9.3, and master¶
Items taken from herdr v0.9.1 to v0.9.3 and from master (0d5d6f1, 2026-09-27, and d6b40d4,
2026-10-01) after the v0.9.0 set above. Only what has been ported is listed.
| Item | Status | Note |
|---|---|---|
Bundled manifests: cline, pi, letta (v0.9.1), grok, kiro (v0.9.2), codex, pi (master d6b40d4) |
ported | cline 2026.09.11.1 (blocked on its inline permission prompt and question, working on a braille turn or "Thinking...", idle at its composer); letta.toml is new, which makes 22 bundled manifests; grok 2026.09.18.2 (the engine-3 copy herdr compiles in, not the engine-2 variant it publishes) and kiro 2026.09.19.1, which are v0.9.2's and v0.9.3's too, taken from master before the release; and, from master at d6b40d4 (2026-10-01), pi 2026.10.01.1 (working on its spinner border or on a bare spinner line) and codex 2026.10.01.1. Each is byte for byte its copy under third_party/herdr/manifests/, which a test checks, and herdr's tests for them are ported: the 17 added since v0.8.2 for muse, claude and codex, three for Grok and three for Letta, with a test written for each Kiro rule and for each rule Codex and Pi gained on 2026-10-01. herdr's Codex manifest reads a Codex idle again through its terminal title (any title that is not a spinner or "Action Required"), and reads the trust dialog of newer Codex versions ("Folder access", "Trust this folder?") as blocked. Where the title says nothing, a Codex whose process names it reads idle through the known-agent fallback: the program tmux names, or, for npm's Codex, which runs as node, the job in front of the pane (README "Integrations"). One that only its launch hint names, such as an npm Codex before the job in front has been read, reads idle at its empty composer through taw's own taw_composer_idle supplement rule (README "The detection supplements"), where a hint with no rule behind it would read unknown |
| The reference copies of herdr's files (v0.9.3) | refreshed | third_party/herdr/reference/ holds v0.9.3's keyboard, session-state and socket pages, its agent skill, its socket schema (protocol 22, with pane.clear and the resume fields and without the graphics methods) and detect/mod.rs, the pages herdr published for 0.9.3 on agents and on adding herdr support (new), master's manifest.rs (the same engine as v0.9.3's without its Codex fallback), and herdr's default config, cut out of its source by third_party/herdr/extract-default-config.sh. The help of the two report verbs, which take a resume command after --, is edited in. manifest-tests.rs stays v0.9.1's, since v0.9.3 dropped 39 of its 57 tests. Nothing a user runs changes with them |
| Engine-contract tests (v0.9.2) | ported | the three tests herdr added to its manifest tests in v0.9.2: a rule reads the OSC title and the OSC progress as inputs of their own, which share priority with the screen's rules; a rule that skips a state update leaves no visible idle, working or blocked evidence; and each screen region (the bottom lines, the text before or after the current prompt, the prompt block, the prompt box, the last horizontal rule) cuts structure out of the screen without deciding what state the agent is in. They pass against taw's engine as it stood, so nothing a pane reads changed. They are here so that a later change of the engine cannot change it unseen |
Cline's native .cline binary (v0.9.1) |
ported | a pane whose foreground program is .cline is Cline |
| Letta detection (v0.9.1) | ported, but not its one-shot runs | a Letta pane reads working (its OSC spinner, status line or a running tool row), blocked (a command approval, an OSC "Action Required" or progress 4;3), idle (an empty composer) and unknown (typing, its agent selector), and taw agent start --kind letta works. A one-shot letta -p ... run is not told from a session: see "Known divergences" |
| Letta integration and restore, experimental (v0.9.1) | ported | taw integration install letta adds a SessionStart hook to ~/.letta/settings.json. It refuses when ~/.letta is missing or settings.json is not JSON, and publishes nothing then. The hook reports Letta's conversation id and prints nothing, because Letta puts a hook's output into its next message. taw session restore resumes letta --conversation <id>, or letta --conversation default --agent <agent-id> for the default conversation, which is kept as default:<agent-id>. taw integration status lists it last, marked experimental, as herdr does. herdr's rollback of the hook script when the config write fails after every check has passed (for Letta only) is not ported: every check that can refuse the install runs before anything is written |
Claude's hook is scoped to SessionStart sources (v0.9.1) |
ported | the hook registers for startup, resume, clear, compact and fork only, and its script refuses any event that is not SessionStart. Grok imports Claude's hooks, so a Grok pane was relabelled claude with Grok's session id and later restored as claude --resume <grok id>. Reinstalling over an older * entry takes only taw's command out of its group, as herdr does, and leaves a hook of the user's in it |
| Grok's session start source, a nested OMP (v0.9.1) | ported | Grok's hook passes its new or load on as --session-start-source when the payload has one, and nothing visible changes. An omp started from a shell OMP spawned (OMPCODE=1) reports nothing, so its short-lived session does not replace the pane's own |
| A resumed Codex keeps its session id from the start (v0.9.1) | ported | after taw session restore, or taw agent start --kind codex -- resume <id>, the pane already holds that id, so a save before Codex's first hook reports, or a second restart, still resumes the conversation. Only Codex's exact resume <id> launch is recognised |
| Writes to an agent's config keep the old file when one fails (v0.9.1) | ported, trimmed | an install or an uninstall stages the new file beside the old and renames it over, so a failed write leaves the original; a symlinked config stays a symlink and keeps its permissions; a hard-linked config and a read-only one are refused; and every edit is planned in memory before the hook script is written. herdr's copying of extended attributes, access lists and ownership is not ported: one user, one platform |
| Hermes plugin lists written inline (v0.9.1, #3839) | ported | enabled: [a, "b"] # note is edited in place: each item keeps its quoting, the comment stays, and uninstall removes only taw's entry |
| The plugin registry keeps a symlink (v0.9.1) | ported | installing a plugin writes through a symlinked registry.json and keeps its permissions, rather than replace the link with a file; a hard-linked one is refused, which herdr's does not |
| A config with a byte-order mark loads (v0.9.1) | ported, by another route | a config.toml saved with a UTF-8 mark loads, a mark an edit left in the middle of the file is dropped, and one inside a multi-line string is kept; config reset-keys neither adds nor removes one. herdr asks the TOML error for the exact span it stumbled on, and the toml crate does not give one that exact, so taw compares the parse before and after dropping each mark at the start of a line |
| An unreadable saved session is kept before a save replaces it (v0.9.1) | ported | when <state>/sessions/<name>.json cannot be read (a newer taw wrote it, or it is damaged) the next save copies it to session-backups/ first, keeping the three newest per name, and skips the save when the copy fails. session restore refuses a snapshot a newer taw wrote, with the error code unsupported. Backup names are scoped per snapshot name, because this crate keeps more than herdr's one session file |
session delete needs the exact name (v0.9.1, #3819) |
ported, with one difference | taw session delete Default does not delete default on a case-insensitive disk, and ../x cannot leave the sessions directory. A name that is not saved is not_found and names the spelling that is, where herdr treats an absent name as a successful no-op |
| Linux notifications name taw (v0.9.1) | ported | notify-send --app-name tmux-agent-wizard -- <title> [<body>], so a title that starts with - is shown and an empty body is left off. herdr's check for $DISPLAY or $WAYLAND_DISPLAY and its silent skip when notify-send is missing are not ported: here a failed delivery is an error |
workspace close --group (v0.9.1, #2874), the verb |
ported, the popup half is "Group close" below | closing a session whose worktree sessions are still open is refused with workspace_group_close_required, which names them, and --group closes them and then the session, with one workspace.closed each. The group comes from the worktree records taw worktree create keeps, where herdr derives it from the repository ("Known divergences") |
pane move --focus (v0.9.1) |
ported | --focus takes your client to the moved pane, as agent focus does, and --no-focus leaves the client, the destination tab and its selected pane alone (the pane joins with join-pane -d). A bare pane move focuses, as herdr's does |
| New panes do not inherit an outer agent's session, or the terminal that started the server (v0.9.2, #4104) | ported, in the plugin script | CODEX_THREAD_ID, OMPCODE, CLAUDECODE, CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_SESSION_ID and CLAUDE_CODE_MESSAGING_TOKEN are flagged removed in tmux's global environment, so a server started from inside one of those agents gives its new panes none of them. So are the terminal's own handles, ITERM_SESSION_ID, LC_TERMINAL, LC_TERMINAL_VERSION, WEZTERM_PANE, KITTY_WINDOW_ID, WT_SESSION, STY, ZELLIJ, ZELLIJ_SESSION_NAME and ZELLIJ_PANE_ID: update-environment governs only the names it lists, so every other variable of the terminal that started the server reached every new pane, even one viewed from another terminal later (verified on 3.6b, with a server started from invented values). herdr removes them from the command it spawns for each pane, and tmux spawns the panes itself. herdr's list has TMUX and TMUX_PANE too, which tmux sets for every pane itself, and its TERM_PROGRAM=herdr is tmux's own TERM_PROGRAM=tmux here. A name you list in update-environment still reaches each session from the terminal that creates it or attaches to it, because a session's own value is laid over the global removal, and a value set for one session with set-environment -t does as well. A set-environment -g of one of the ten, made before the plugin loads or before it is sourced again (a reload of tmux.conf, prefix + I), is dropped with the rest. A pane that already exists keeps what it started with, and every other variable is left alone |
| A pane's screen and scrollback can be cleared (v0.9.2, #4383) | ported, in part | herdr's pane.clear and its keys.clear_pane, which is unbound until it is set. taw pane clear [pane] is the verb (herdr has the socket method and no command line verb for it) and @agent-wizard-clear-key the key, empty by default, which binds it on the prefix for the pane it is pressed in. On the alternate screen nothing changes, history included, as in herdr. Anywhere else the pane's history is cleared. Where bash, zsh or fish is at its prompt, that shell being the pane's own and its screen held still (the test agent start uses), the shell is sent Ctrl-L: it clears its screen and draws its prompt and the line being typed at the top, the line herdr keeps, and the history the redraw pushed up is cleared once it has drawn, because tmux moves a cleared screen into the history. A screen that is clear already, the prompt at the top and nothing else on it, is not asked for anything. A program in front, an agent or an editor, keeps its screen and is sent nothing, as herdr sends it nothing, and a plain sh is sent no key, nor is a pane in a window with synchronize-panes on, where tmux copies a key sent to one pane to all of them. What tmux cannot do is in "Structural gaps". The limits: a shell waiting in a builtin of its own (read, zsh's select) passes the prompt test and is sent the key as input, and a bash in readline's vi mode takes Ctrl-L as a character, so the verb, seeing a ^L on the screen that was not, wherever it landed, takes it out with one Backspace and leaves the screen alone (screen_cleared is false, with a note). fish is matched by name and sent the same key; its Ctrl-L has not been tried. On macOS /bin/sh is a bash and tmux names it so, so it is sent the key |
| A shutdown keeps the full layout (v0.9.2, #4320) | ported, with one structural gap | an autosave never writes an empty layout, and the two hooks that fire when something closes wait five seconds first and do nothing if tmux has gone by then. herdr's history of 48 snapshots is ported (taken from master's #4400, which v0.9.2 shipped as #4320): every save, and a session delete, copies the file it replaces or removes into session-snapshots/, one copy per name per 15 minutes at most, never one equal in layout to the newest, the 48 newest per name kept, a failed copy logged with the save going ahead. Recovery of an older copy is by hand (README, "Save and restore", "Older copies"), as in herdr; taw adds the one case herdr never meets, a new tmux server: a save that replaces a layout taken before the server started copies it first with no wait, and a plain session restore goes back to the newest layout taken before the server started when default.json has nothing to make, one layout and never further (README "Beyond herdr"). Screen history is replayed only onto the layout it was taken with. herdr's save before a Linux shutdown is a structural gap (below) |
| Restored agents start apart (v0.9.2, #4102) | ported | [session] startup_per_agent_delay_ms (default 100, 0 turns it off): the first resume starts at once and then one every that many milliseconds. A restore here is one blocking call, so the spacing is a plain sleep across every session one restore recreates, where herdr's is an asynchronous scheduler |
| OpenCode V2 reports from the pane that shows the conversation (v0.9.1, #3757) | ported | herdr's pair. A plugin loaded into each OpenCode TUI (taw-tui-session.js, registered in tui.jsonc or tui.json for OpenCode 1 and in cli.json for OpenCode 2) reports the session that TUI shows for its own pane, so OpenCode 2's one shared server cannot mix panes up, and a server plugin (plugins/taw-agent-state.js) is left for opencode run and Mini, which have no TUI plugin. Completed and interrupted turns return to idle; pending permissions, forms and failed runs stay blocked. Both are taw's ports of herdr's files: the logic is herdr's, and what differs is the transport (a taw pane report-* run in place of a socket request), the environment gate ($TMUX_PANE), the names, and permission.updated, which older OpenCode sends and taw's plugin also reads, all of it pinned by tests/vendored_herdr.rs. herdr's plugin tests run under node. OpenCode 1 stays supported. Restart OpenCode after installing or updating, and after updating taw run taw integration install opencode again (taw integration status --outdated-only lists it) |
OpenCode tui.json registrations are respected (v0.9.2, #4240) |
ported | a plugin already registered in tui.json is left there; otherwise tui.jsonc is edited or created, and tui.json is never created. OpenCode reads both and merges their lists. The lists are edited in place, so comments, layout and unknown keys stay, through the comment-keeping JSONC tree herdr uses (jsonc-parser), and uninstall gives each file back |
| OpenCode sub-agent status is tracked per pane (v0.9.2, #1362) | ported | in the TUI plugin, herdr's logic unchanged: a child agent's permission or question blocks the pane of the TUI that shows its root, and its end brings the pane back |
| A reported agent that herdr cannot identify is kept until its shell is idle (v0.9.2, #4687) | ported, with taw's own reading of which agents | herdr keeps an agent it cannot identify by its process, once its hook has reported it, until the pane's shell is idle (report_self_reported_agent_shell_return, available_pane_shell_from_job). taw keeps every agent whose hook reports for it (pane report-agent-session, or pane report-agent for the ones that report their state) that no process in front of the pane has named at a read while its record stands: the Letta an npm install runs under node, an agent run by a wrapper script, and any agent the identity table does not match. herdr marks a pane that way when the hook holding it names an agent herdr does not know (self_reported_agent_active, src/terminal/state.rs:1879-1883, read at src/app/api.rs:465, v0.9.3). taw decides by the process instead, so it also keeps an agent herdr knows that taw cannot name by its process, the Letta under node for one, which herdr names by its arguments and ends by the ordinary rules. Such a record keeps its session id, whatever runs in front of it, until the pane's own process is a shell alone at the head of the terminal's foreground group (asked of ps and pgrep at most once a second, the pane's process and the members of its terminal's foreground group, with herdr's list of shell names). The limit, as in herdr: a shell started inside the pane (nix develop, a bash typed at the prompt) is not the pane's own, so quitting such an agent inside one keeps its record until that shell exits. An agent a process does name (Claude, Codex started from a prompt) follows the ordinary exit and miss rules whatever its hook reported. An agent whose integration reports its state (OpenCode, kimi, pi, omp, kilo, mastracode) is not ended by another program in front of it, only by a shell coming back. A pane whose state a hook reports is released by the same test, where it kept the last state reported until taw pane release-agent before |
| A hook's report notifies like any detected change (v0.9.2) | ported, through the watcher | herdr sends the state an agent's integration reports through the same state machine as a state it detected, so the completion rule, the status event, the toast and the agent events apply to it: an OpenCode user is told when it finishes and when it needs them. taw does the same for the integrations that report a state (OpenCode, kilo, pi, omp, kimi, mastracode). taw pane report-agent writes the state and nudges the pane's watcher (one SIGURG to @agent_watch_pid), and the watcher announces the change by the steps a change read off the screen takes, once: an idle that ends a turn behind your back is done, the same report again is nothing, and an idle over an unseen done leaves it done. What differs is where the state is written: by the reporter, before any read, so the watcher tells a change from the state its last read left; and the done it writes over a report keeps the hook as the source |
| No false done at startup or on a new session (v0.9.2, #4457) | ported | herdr's three completion rules, and its eight completion_guard_* tests with the two headless ones of the same change, as fake-clock scenarios. unknown to idle was no completion here already. An agent taw has just taken is being taken until it is seen working or reports work: one the watcher takes as a new agent, one taw agent start launched or a session restore resumed (each marks the pane @agent_acquiring before the agent runs, herdr's managed launch), and one whose session id or path another replaced. Its first idle before that is no completion, so a trust question asked and answered in a pane out of view is a request and then idle, with no done notification, and an agent that exits is done all the same. The mark is a pane option, so a watcher started again (taw watch attach-all --restart) keeps it, as herdr keeps the acquisition across a live handoff, and it holds the turn stamp the agent was taken at, so that the first read of a watcher started while the agent works counts a reported working as work only when the stamp has moved since, as herdr's report of working ends the acquisition whenever it comes. A new session of the same agent, or another agent in the pane, whatever the pane's state, marks the pane seen and turns a done back into idle (announced as that change of status, with no notification), and a notification still waiting out its delay is dropped when it comes due, as the pane holds another agent or session by then. An agent that reports for itself goes through the same rules: its first reported idle after it is taken, or after a new session, is no completion, and its own idle after a working it reported is. herdr's completion_seq on agent records is not added |
Text field keys (v0.9.1, keyboard.mdx, "Editing text fields") |
ported, the search preset left to fzf | The filter, the rename and branch prompts and the new session picker take herdr's whole text key set: ctrl-b and ctrl-f move a character, ctrl-d deletes the next one, alt-d cuts to the end of the next word and leaves the space after it, alt-b and alt-f move by the same boundary, alt-backspace and ctrl-backspace cut the previous word, and ctrl-y puts back the last cut, besides the keys they had. The last cut is kept per field and never reaches the clipboard. tmux's prefix keeps its meaning, prefix2 included: the filter checks the chord before it reads a key, and a prompt or the picker lets a prefix or prefix2 bound to one of the added keys have it, so with tmux's default ctrl-b that key moves no cursor. Where a word ends is herdr's word_boundary in the filter, the prompts and the picker (a run of letters, digits and _, or of punctuation, after any space), and fzf's in the search preset's query line (whatever is not a letter or a digit is skipped, then a run of letters and digits, so tmux-agent-wizard is cut as tmux, -agent, -wizard). The search preset is a pure fzf search and binds nothing new: there ctrl-backspace deletes one character, as fzf's does where herdr's cuts a word, and ctrl-d on an empty query quits, as fzf's end of input does (in every other field ctrl-d closes nothing). The cursor is drawn where it really is, in the filter too, so the keys that move it show |
Row token rules and hide (v0.9.1, config/sidebar/rules.rs) |
ported | A styled token in [ui.sidebar.agents], [ui.sidebar.spaces] (and its alias [ui.sidebar.sessions]) and the per-agent overrides can carry up to 16 rules. The first rule the token's text meets lays its fg, bold and dim over the token's own, or, with hide = true, takes the token out of the row together with the space that separated it, and a row left with no token is not drawn. A rule has exactly one of equals, contains or starts_with (ignore_case = true folds ASCII case), or gt or lt (a finite number, compared strictly, the whole text being the number: 90 is over gt = 80, 90% is not). state_icon and git_status take no rules. A rule that is wrong is an error in taw config check, which says how, and herdr's rules tests are ported. A key a token does not know is ignored by the popup, where herdr refuses the token, and taw config check warns about it by name. The filter matches what is drawn, so a hidden token is not matched. One fix on the way: herdr writes colours as #f55, which the popup's colour parser read only as six digits, so such a token was drawn in the row's own colour; three digits work now, for a token's fg and [theme.custom] alike |
| Removing a worktree git refuses, and trying a removal that stopped partway again (v0.9.1, #3766, #3314, #1797) | ported | In the popup, x on a worktree git will not remove (modified or untracked files, submodules) says why and asks remove anyway? uncommitted changes are lost, with y / N under it. Only y runs the removal again with --force; Enter, n, q and Esc keep the worktree, and a y typed in the moment after the removal that raised the question is ignored, so a double tap loses nothing. It is asked with [ui] confirm_close = false too, since what it puts at risk cannot be put back. A removal that failed partway leaves git letting go of the worktree while its folder stays: the next x finds that folder, asks the same question about what is left and names the folder, and y clears it, but only when its own .git file points into this repository's worktrees directory, as in herdr, so an unrelated folder is never deleted. [hooks] pre_worktree_remove is asked before the forced removal as before every other, and can still refuse it. git runs with LC_ALL=C for every worktree call, because its refusals are told apart by their English words. taw worktree remove fails as it did, and finishes a removal whose worktree is already gone |
| Group close in the popup (v0.9.1, #2874) | ported | x on a session that has worktree sessions open asks Close them too?, naming up to three of them (the group taw workspace close --group closes): y closes the session and all of them, Enter and n just this session, which is what x always did, and Esc and q nothing. x on the last pane of a session's last window asks the same, since closing that pane closes the session, and Enter or n then closes just the pane. [hooks] pre_close is asked about each session that would close before any of them does, and one refusal keeps them all open. From a popup opened inside one of those sessions, that session closes last and all of them go in one tmux command, so tmux's detach-on-destroy, which takes the client and the popup down with the session, cannot cut the close short. With [ui] confirm_close = false nobody is asked and just the session or pane closes. Closing sessions removes no worktree: each comes back as a parked row |
After an update, the servers still on the old version are listed with the commands to restart each (v0.9.2, herdr update, 9dc3a1df2b) |
ported, by another route | herdr's update says which running servers still run the old version and gives the exact command to restart each. taw has no server to update, but each pane's watcher keeps running the taw it started with after a rebuild or a reinstall, which is the same need. taw watch status marks each live watcher whose taw is not the one installed (binary is old, binary_note says why in words, old counts those a restart would start and restart names the command), and taw watch attach-all --restart starts each of them again, from the file it started from. A watcher writes down what it runs as it starts, in @agent_watch_build: the file at its path (device, inode, size and the time of its last write) and the path, as it was started. It is old when the file at that path is another one or none, and when it wrote nothing, which only a taw from before the record does, so the first watch status after an update names every watcher. herdr lists servers and names a command for each, where one command covers every watcher here. Nothing is ported from herdr's code, so no file carries herdr's header |
| Agents report their own resume command (v0.9.2, #4687) | ported, with two differences | taw pane report-agent and report-agent-session take the command that resumes the agent's session after --, as herdr's take resume_argv (README "Report an agent of your own", taw's version of herdr's guide). It must start with a plain command name, have at most 64 words and 8 KiB, and hold no control character or apostrophe, or the report fails with invalid_resume_argv and nothing of it is applied; taw refuses a command too long for one tmux command once quoted with the same code, and reads a -- that nothing follows as no command, which herdr refuses as an empty one. Only a reporter that holds the pane may set it: its hook:<source> is the pane's source for that agent (a report-agent takes the pane, so its own command is always kept), or no hook holds the pane and the agent taw names from the process in front of it is that agent; any other gets resume_not_accepted, and the rest of its report is applied. The pane keeps it in @agent_resume_source, @agent_resume_agent and @agent_resume_argv while the same source and agent hold it, and it goes when the source releases the pane, when another agent or another source takes it, when the pane's shell comes back, when an agent taw names by its process exits, and when the process a restore started for an agent only its kind hint names exits. A report out of order brings none. The watcher's clear of an agent that has left, its drops of what the last agent reported (when another agent takes the pane, and at the exit of an agent only its hint names) and its write of the state an agent leaves with land only while the pane holds the stamp of the last report its read saw (@agent_report_stamp, for herdr's observed_at), so a report made after that read, by an agent that claimed the pane, is not undone with the agent that left. A save keeps the command as agent_resume (an older snapshot loads with none), and session restore types it in place of taw's own, in the saved directory, quoted POSIX-style, and puts it back on the pane; [session] resume_agents_on_restore = false turns it off. A restored pane is the reporting agent's again, as herdr's restore makes it: with the command, the restore puts back the agent's word that it is there (@agent_reported_kind), and the watcher keeps an agent so marked, command and all, until the pane's own shell is back at its prompt, whether or not taw has detection for it. The differences: a session report names the pane's agent here, as taw's reports already did, so another agent's session report takes the last agent's command away where herdr's changes nothing; and with [session] resume_agents_on_restore = false a restored pane gets neither the command nor taw's own session back, where herdr puts the command back on the pane and does not run it. herdr's six tests of src/app/actions.rs (3601 to 3760), its two headless tests of 2552d101af, pane_restore_startup_prefers_reported_resume_argv and capture_contract_includes_reported_agent_resume are ported as real-tmux and unit tests |
Report order by --seq (v0.9.2) |
ported | herdr's rule (hook_report_is_newer), which its guide makes the contract: a report whose --seq is not higher than the last one its source sent for the pane is ignored, and so is a report with none from a source that has sent one; each source has its own numbers, a release is a report like the others, and OpenCode's TUI saying which session the pane shows is taken with no number after its numbered reports, as herdr takes it. An ignored report changes nothing and answers ok with in_order: false. The numbers are kept in @agent_report_seq and compared by taw, as 19-digit nanosecond stamps are more than tmux's floating-point format arithmetic can tell apart; the check and the write are one tmux call, so two reports that race are taken in their numbers' order. herdr's tests of the rule in src/terminal/state.rs are ported as real-tmux tests (tests/e2e_report_order.rs) |
| Ctrl+Z keeps the agent (v0.9.4, #4964) | taken | herdr's rule: an agent suspended with Ctrl+Z keeps its name, its session and its hook's authority while the exact process stays alive behind the shell. The watcher notes the process it finds the agent running as in front of the pane, and while that process is alive on the pane's terminal and stopped, or run on in the background, the pane keeps its kind, name, session id and resume command, at idle with the rule suspended and no notification (a turn under way ends there, as nothing finished); a pane its hook reports for keeps the hook as its source. The popup's row says suspended where it says the state, and a taw session save taken meanwhile keeps the session, so a restore resumes it. After fg it is the same agent, with no startup grace and no new acquisition. The record goes when the process dies (the pane is then the shell, as for any exit) or when another agent reports another session in the pane (tests/e2e_suspend.rs) |
A session is resumed in one pane of a restore (v0.9.1, pane_restore_startup) |
ported | herdr reserves each agent session for the first pane of a restore that saved it, and so does taw: a session saved in two panes is typed into the first only, and the plan says which pane has it. Its key is the kind with the saved id or path, and for a command an agent reported, herdr's key of source, agent, directory and command, so the same command saved in two projects is two sessions |
| Several prefix keys (v0.9.2, #4653) | ported, as tmux's prefix2 |
herdr takes prefix = ["ctrl+space", "ctrl+s"] and every entry enters the same prefix mode. tmux has two, prefix and prefix2, and its own key tables already treat both as the prefix outside the popup. Inside it the popup's chord takes both: either one, then @agent-wizard-notification-key, goes next up, from the tree, the filter and the search preset's query. None, which is what an unset prefix2 reads, is no key; a prefix the popup cannot read is dropped and the other kept, and with neither readable the chord is off (herdr refuses an empty list and keeps its default prefix; a chord with no prefix has nothing to fall back on). A prompt and the picker yield prefix2 to the keys the text fields added exactly as they yield prefix, and the filter hands every key of it to the chord. herdr's help line that lists every prefix has no counterpart: the popup's help says prefix+O whichever keys it is |
| Closing the last pane says what closes with it (v0.9.2, #4379, #4409) | ported, as the wording | herdr asks before it closes the last tab of a workspace. x on a pane asks here while [ui] confirm_close is on, and on the last pane of a window it says the window closes too. On the last pane of a session's last window, when no worktree session is open, it says It is the session's last pane, so the session closes with it., which is what tmux closes and what the group question already said. In a tmux session group (new-session -t) the sessions share their windows, so closing the last pane closes every one of them, and the sentence says It is the session's last pane, so the session and the sessions grouped with it close.; a session left alone in its group says the plain one. A close from the CLI or the socket asks nobody here, as in herdr |
| A restored pane whose saved directory is gone (v0.9.2, #4320) | ported, with two differences | herdr keeps such a pane in the layout with restore_error ("Saved directory is unavailable. Restore the directory and restart this session."), starts nothing in it, resumes its agent nowhere else, and keeps its saved directory and agent session for the next start. taw does the same with what tmux gives it: tmux has already opened the pane in your home folder, so a restore types nothing into it (and gives it no saved screen), says in its plan which directory is gone and the command to run once it is back, with the pane in not_resumed, and marks it with herdr's words (@agent_restore_error, shown by pane get and pane list as restore_error). Every save keeps the pane as it was saved, its directory, agent, session and reported command (@agent_restore_saved), until the pane is closed or has an agent of its own: then the error and the keep go, and the pane is saved as it is. The differences: herdr's pane has no shell and shows the error in its place, and its record's cwd is the saved directory, where taw's pane is a shell in your home folder and its cwd is where that shell is; and herdr keeps the error and the saved directory until the pane is closed (see Known divergences). herdr's second case, a shell that cannot start, is tmux's here. taw also leaves alone, the same way, a pane that tmux opened somewhere else though its saved directory is there (one with no permission to go into it, which tmux opens your home folder for, or one tmux read as another), which herdr, which opens no shell there, does not have; the plan says where tmux opened it. With it: taw worktree open on a worktree whose checkout is gone refuses with not_found, naming the folder, where tmux would make the session in your home folder (herdr answers such a worktree worktree_not_found). Tests: herdr's failed_deferred_restore_keeps_session_reference_without_retrying_elsewhere and failed_cold_restore_preserves_panes_and_saved_directories against a real tmux, a restore through the built binary followed by an autosave, and restores whose tmux opens a pane elsewhere (a folder read as a format, a folder with no permissions) |
Agents that report to herdr themselves (v0.9.2, agents.mdx "Supported by the agent") |
ported, behind @agent-wizard-herdr-env |
Command Code, Crush, Kiro CLI, Muse Code and Prime Agent follow herdr's guide: report only where HERDR_ENV=1, HERDR_PANE_ID and HERDR_BIN_PATH are set, with "$HERDR_BIN_PATH" pane report-agent "$HERDR_PANE_ID" .... With the setting on the plugin puts the three in tmux's global environment (HERDR_PANE_ID=%calling, the word taw's pane verbs read as the pane the process runs in, from $TMUX_PANE), so a pane created afterwards reports to taw. Off by default, since HERDR_ENV=1 tells every herdr-aware program it runs inside herdr. A plugin process or a [hooks] command never inherits the pane ids from it. An agent that also requires HERDR_SOCKET_PATH reports nothing, as taw has no socket, and a sandboxed agent cannot reach the host's taw (README "Agents that report to herdr themselves") |
herdr v0.9.4 and master's detection changes¶
Items taken from herdr v0.9.4 (2026-10-13), v0.9.5 (2026-10-14) and master up to 5608de0608
(2026-10-23). Only what has been ported is listed; the rest of the window is not taken, or taw
already had it.
| Item | Status | Note |
|---|---|---|
Bundled manifests: claude 2026.10.11.1, codex 2026.10.11.1, grok 2026.10.05.1, antigravity 2026.10.05.1 (master 9c38bbc64e, 6d697e1e75, fabcab108f) |
ported | each is byte for byte herdr's compiled-in copy at 5608de0608, in src/detect/manifests/ and in third_party/herdr/manifests/, which a test checks. Claude reads the question form of a named session (/rename, --name) as blocked: the form's footer with the name's divider under it, and an optional counted task panel, is live_blocked_form_named_session (priority 980, herdr issue #4573, fixed by pull request #5192, 9c38bbc64e); none of taw's six Claude supplements is a blocked rule, so none of them duplicates it. Codex 0.159 and later's update picker ("Update available ยท" with "enter continue ยท esc skip") reads blocked, as the older dialog still does. Grok and Antigravity read idle at their prompt while background commands, monitors, loops or subagents run (herdr removed background_work_chip_working and background_status_working from Grok, and the task rule from Antigravity; #4959), so taw agent wait and the notifications fire as they do for Claude's background shell. Antigravity's rules are rewritten from CLI 1.2.17: permission_prompt, question_prompt and trust_prompt read blocked from their key-hint rows, esc_cancel_footer_working and a braille spinner_working read working. herdr's two engine tests that came with the claude and codex changes are ported; taw's tests for Grok's idle background rows and for each new rule are written from the rule's own strings, since herdr has none |
Hermes started by its installer's Python launcher (v0.9.4 e35f3937b0, #4911; master 43b36b68ca, #5518) |
ported | a Hermes whose foreground process is python -I -c <bootstrap> is named hermes from its arguments, without the Hermes integration. The two bootstraps herdr captured are recognised (the short one from #4910, the longer one with the recovery helpers from #5068), and only the installation root may differ; source that merely quotes the bootstrap, a root with a quote or an escape in it, and the launcher's own helper modes (--run-module, --print-runtime-command) are no Hermes. It takes the exact argument list of the process, so a process known only by its ps line is not named this way. herdr's fixtures and tests are ported with it |
Codex turn hooks dropped, Codex state from the screen only (v0.9.5, 287da8072f, #5278) |
taken, nothing to change | taw's Codex hook has registered SessionStart alone since it was written (taw integration install codex adds that one event, and its script exits on any other), so no install carries a turn hook to remove, and the state comes from the manifest and the title |
Grok and Antigravity idle at the prompt with background work (v0.9.4, fabcab108f, #4959) |
taken | the manifests row above; taw agent wait and the notifications fire for them as they do for Claude's background shell |
| Docs: an idle agent is not permission to approve, and a wait watches the agent's state (herdr's agents page, master) | taken | one sentence where the README documents taw agent wait and agent prompt --wait |
plugin update for GitHub-installed plugins (#4245) |
taken | taw plugin update [id|source...] [--yes] takes plugin ids or sources (owner/repo[/subdir]) as herdr does, asks the remote for each plugin's commit at its recorded ref and fetches only when it differs, leaves a linked plugin alone, and reports each plugin as updated, unchanged, linked or failed, exiting 1 when one failed. Like herdr, each commit is installed into a folder of its own and a built tree is never moved: herdr numbers its folders under github-installations/<id>/, taw names them for the commit (<repo>@<12-char sha>). The new commit is built there under the same trust gate as plugin install, the registry then switches to it in one write, and the old folder is removed after that, so a decline, Ctrl-C or a failed fetch or build changes nothing live. taw keeps no plugin process, so it has no lease on a folder in use as herdr does. Whether herdr also exits nonzero on a failure is not known here: its source for this change is not vendored |
Known divergences¶
Implemented, reachable, and not byte-identical to herdr. Recorded rather than fixed, each with the reason.
| Where | herdr | here | Why it stands |
|---|---|---|---|
agent prompt with --until or --timeout but no --wait |
a usage error | the flags are silently ignored, and the prompt is sent | prompt checks wait before it looks at either flag (src/cli/agent.rs). Rejecting them would be a new failure mode for a call that otherwise does exactly what it was asked; pinned with a test that documents the current behaviour rather than left to drift |
agent prompt to an agent in blocked |
refused | the text is sent | a blocked agent is showing an approval prompt, and typing into it is often precisely what the caller wants: answering the prompt. Refusing would make the blocked case unreachable through the agent surface and push callers onto pane verbs, which skip agent identity checks entirely |
agent prompt --wait and a short turn |
herdr's wait is told of every state it detects through its event stream, so a prompt sent to an agent at rest is answered by the working or blocked that follows within five seconds, however briefly the turn lasts, or the call answers agent_prompt_stalled |
the same gate, five seconds, the same agent_prompt_stalled; but taw's wait reads the agent's record four times a second, so a turn that begins and ends between two reads is seen only for an agent whose hook reports its turns (the @agent_turn stamp), and an agent read off its screen alone answers agent_prompt_stalled though it did the work |
taw has no event stream ("Structural gaps", the socket API); the error says to read the agent before sending the prompt again |
| A request for a pane you are looking at | herdr plays the request sound whenever an agent becomes blocked, even in the active workspace (its changelog: "Request alerts now play even when the agent is in the active workspace"); only its toast looks at the view again when the delay ends |
no sound and no toast for a pane in view: sounds follow the same background-only gate as toasts, and a request is checked again when its delay ends | nothing here alerts for what you are already looking at. A stricter rule was tried, no toast at all for a prompt that appeared in front of you even if you leave within the delay, and not taken, because herdr's reminder for a blocked agent you walked away from is intended |
A one-shot letta -p ... run |
herdr reads the program's arguments and ignores a non-interactive Letta run | the run reads as a Letta pane until it exits | tmux reports only the program's name, and taw reads arguments only to look inside a container runtime (README "Agents in containers") |
An agent that runs under node, bun or another runtime |
herdr reads the arguments, so a node- or bun-wrapped Cline, Letta or Kimi is recognised by what it runs | not recognised by its process, whose name is the runtime's. It is named by its own report instead: the hook its integration installs, or the kind taw agent start was given. An agent so named is kept, with its session id, until the pane's own shell is back, so npm's Letta under node stays in the popup (the row on a reported agent above). An agent with no hook that runs under a runtime, started by hand, is not recognised |
tmux gives the name of the program in front of the pane and nothing of its arguments |
Which worktrees workspace close --group closes |
herdr derives the group from the repository each workspace is in | the group is the worktree sessions that taw worktree create recorded as cut from the session (source_workspace_id), running, and in the same repository, so a stale id after a tmux restart cannot link an unrelated session. A worktree made outside taw is not in a group, and after a tmux restart the primary may not be recognised, so the close behaves as it did before |
tmux keeps no repository for a session, and taw's own records are the only link it has. As in herdr, only a session that is not itself a worktree has a group |
| A Codex at rest | herdr 0.9.2 and 0.9.3 read a Codex that no rule matches as unknown, paired with its hook state. herdr master went back to idle on 2026-10-01 (its codex.toml 2026.10.01.1 and the engine's fallback), and herdr v0.9.4 (2026-10-13) and later carry it |
idle, as master reads it: the bundled codex.toml is 2026.10.11.1, so a Codex whose title is not a spinner or "Action Required" is idle by herdr's osc_title_idle rule, one that no rule matches is idle through the known-agent fallback when tmux names its process, and taw's composer rule reads a Codex at its composer where the title says nothing, which is what keeps one that only its launch hint names from reading unknown. tmux gives a pane no program has titled the host name for its title, and taw reads that as no title wherever it shows or passes one (the watcher, the popup, the JSON verbs, hooks and plugins; a pane made before the host name changed keeps the old name and reads as titled), as herdr's panes, which start with none, have none |
taw follows master's return rather than the two releases: reading unknown would end Codex's done toasts, and herdr went back on it two days after it released it |
| How a pane is told who it is | herdr puts HERDR_PANE_ID in the environment of the panes it manages and trusts it |
taw uses tmux's own $TMUX_PANE and, for a plugin's caller pane, confirms it is live on the server it addresses, since it may come from another server. Every hook script does nothing outside tmux, and a verb that acts on the pane it is run from needs --pane there |
tmux owns the panes, so the id tmux gives them is the one that cannot be wrong (docs/ARCHITECTURE.md, "The caller pane"). With @agent-wizard-herdr-env on, panes also get HERDR_PANE_ID=%calling, which taw's pane verbs read as $TMUX_PANE |
| Completion after a new agent or a new session | herdr (#4457, v0.9.2) applies each report and each detected change as it comes: a new session resets the pane's completion the moment it is reported, and a question and its answer reported one after the other are two changes | the guards are ported (the row of #4457 in "herdr v0.9.1 to v0.9.3, and master"), and the watcher applies them at its reads, at most one a tick (300 ms). A report of a session nudges no watcher, so a new session turns a done back into idle at the watcher's next read, which the agent's own redraw brings (at the latest the identity recheck, five seconds on); a notification waiting out its delay is dropped by then all the same, since it reads the agent and the session the pane holds when it comes due. An agent that reports for itself, sitting at its prompt, that asks a question (blocked) and has it answered (idle) inside one tick is taken to have worked, as a turn shorter than a tick is (@agent_turn), so that idle is a completion even of an agent being taken. And the other way: an agent being taken that reports its first work (working) and then a question (blocked) inside one tick is read at blocked, with no report of work seen, so it is still being taken, and if the answer ends its turn with no later report of work, that end is idle with no done notification, where herdr counts the turn |
the watcher reads what tmux holds on a tick, and the turn stamp a report moves says that a report of work or of a question came, not which; a stamp moved under blocked is taken for the question alone, the cautious reading, since taking it for work would count every question answered as a finished turn |
| A second agent started in a pane whose agent died | once the new agent's 3 s grace is over, herdr reads the whole screen, which still holds the frame the last agent left (a permission prompt read as the new agent's blocked, with a request toast), until the new agent draws over it |
taw agent start records the line it typed the command on (@agent_typed_line), and the verb and the watcher read only the rows below it while it is on the screen, so the pane says unknown until the new agent draws there, and an install's progress or a transient prompt's rewrite counts as nothing of the agent |
a person told that an agent that is only starting needs them is the worse mistake, and what the command started can draw nowhere else. An agent started by hand gets herdr's reading |
Report order (--seq) |
herdr keeps the numbers a source sent for a pane until the agent's process exits, and, for an agent it cannot name by its process, after the pane's shell has come back too; its metadata reports are ordered by a sequence of their own, with expiry | taw orders pane report-agent, report-agent-session and release-agent by herdr's rule (the row of v0.9.2 above) and forgets a source's numbers whenever the pane's agent leaves it, whichever way it leaves. report-metadata accepts --seq and ignores it: a later report replaces an earlier one |
taw keeps the numbers with the agent's record, which is what it clears when an agent leaves, and its metadata options have no expiry or layers of sources for an order to be kept between |
| A restored pane whose saved directory is gone, once an agent comes to it | herdr keeps the pane's restore_error and its saved directory until the pane is closed; nothing can start in the pane, which has no shell, and a session an agent reports for it replaces the one it saved, in that saved directory |
taw takes the error off, and saves the pane as it is (its folder now, its agent and session) once an agent of its own is recorded in it: the watcher names one in front of it, taw agent start starts one, or an agent reports its state or session |
a taw pane is a shell, in which an agent can start in another folder, and kept as it was saved, its new conversation would be written over at every save by the one the pane had before, and lost to the next restore. A shell that goes to another folder, or a program that is no agent, ends nothing |
| Left and Right in the popup | herdr's Go To jumps to the first row of the previous or next workspace with Left and Right, without wrapping, except while a search is being edited (v0.9.2, #4384) | in the modal preset Left and Right do nothing; J and K move between sessions, and [keys] can bind the same moves (next_workspace, previous_workspace) to Left and Right |
not ported: J and K already do it, and the search preset's query and the filter keep Left and Right for the cursor |
| A new session reported by an agent already in the pane | herdr takes a different session id from the same source for the same agent only when the report's session start reason is one that source may replace a session with (session_report_allows_session_replacement, src/terminal/state.rs, v0.9.3): Claude's clear, resume or compact, Codex's startup, clear, resume or compact, Pi's new, resume or fork, Grok's new, OpenCode's select, and so on per source; any other reason is dropped |
every session report replaces the pane's session, whatever its start source, which is recorded on the pane (@agent_token_session_start_source). The one place the start source is read is an OpenCode select report with no --seq, which skips the order check (unsequenced_selection, src/cli/pane/report.rs, herdr's is_unsequenced_opencode_selection); herdr's rule that each source may replace a session only for some start reasons is not ported |
not ported: a Claude startup or fork report over a session the pane already holds, which herdr drops, moves the pane to the new session here |
completion_seq on agent get and agent list |
an agent record carries completion_seq, a number set while the agent's current idle completed work, whoever has looked at it since, and null otherwise |
not in taw's agent records | not ported: done and @agent_seen answer whether a turn ended unseen, and a client that needs to tell two completions apart reads state_change_seq (the time of the last state change here) |
Structural gaps¶
One row each: what herdr does, why tmux cannot express it, and the closest thing you can do instead. None of these is a missing implementation; the Linux shutdown save and the bundled sound files are choices taw made, said as such.
| Gap | Why tmux cannot | Workaround |
|---|---|---|
Socket API (HERDR_SOCKET_PATH, JSON-RPC), events.subscribe/events.wait streaming, terminal.attach/observe |
herdr is a server with a live control channel. taw is a binary that runs, reads tmux, and exits: there is no process to hold a subscription open, and adding a daemon would mean a second source of truth, which is exactly what the "tmux is the database" rule exists to prevent |
81 of herdr's 102 socket methods are CLI verbs returning the same JSON (docs/COMPAT.md lists the other 21 and why), so call or poll them. For pushed events, plugin [[events]] hooks carry the same 19 event names, delivered as a process launch instead of a stream. For raw terminal bytes, taw pane read (capture-pane) and tmux's own pipe-pane |
| Always-on sidebar, floating toasts | herdr is the renderer, so it can keep a panel and a toast drawn over live pane content. tmux below 3.8 has no surface that stays drawn over a pane; display-popup is modal: a key opens it, and it closes again |
The popup for the whole sidebar, plus #{@agent_wizard_status} in your status line as the small always-visible summary. tmux 3.8's floating panes are the surface this actually needs; revisit when they are broadly available, not before |
Toast screen position (ui.toast.herdr.position) |
tmux display-message draws on the status line, wherever that is, and takes no position argument |
Move the status line itself (set -g status-position top), or pick ui.toast.delivery = "terminal"/"system", where the terminal or the OS notifier owns placement |
| Kitty graphics in a pane | herdr renders the standard Kitty graphics an app writes to its terminal (since v0.9.2, which removed its own pane.graphics.* methods). tmux multiplexes a pane's bytes and reflows its cells, and does not draw Kitty graphics itself |
set -g allow-passthrough on lets an agent's own escape sequences reach a graphics-capable outer terminal, so an image a tool prints can still appear, but tmux does not track it with the pane's cells |
Clearing a pane's screen (pane.clear) |
herdr owns the terminal, so it drops a pane's screen and scrollback and keeps the cursor's line, for any program on the main screen, sending it nothing. tmux owns it here and has no command that clears a pane's screen: clear-history drops the scrollback only, and send-keys -R resets the pane's terminal, which undoes the modes the program set |
taw pane clear clears the history of any pane off the alternate screen, and asks the pane's own shell for the screen: Ctrl-L to a bash, zsh or fish at its prompt, which clears it and draws its prompt and the line being typed at the top. Another program keeps its screen: clear it from inside the program |
| Remote attach with local keybindings, image paste | herdr's client speaks its own protocol, so it renders and intercepts keys on whichever machine you are sitting at, and carries a pasted image. taw closes both halves differently: the machine's own tmux gets the terminal, so its keys are its own, and ctrl-v on an agent's row of the list in the default preset, or paste image in the row's right-click menu (or taw image paste --pane <id> --machine <name>) sends the image on this machine's clipboard over that machine's ssh, where it is written to a file whose path is pasted into the agent as a bracketed paste (Claude Code and Codex attach it); an agent that is waiting on a question is asked to be answered first, and leaving the screen never waits for a paste under way. What is left is a gap: an ssh'd-in tmux client is a plain terminal on a pty, so the agent's own image paste key there reads that machine's clipboard, a machine's keys are not forwarded into the outer app, and a machine is saved in config.toml, not by a command |
Map the chords in your local terminal emulator, before they leave the machine: WezTerm's cmd_to_tmux_prefix, for instance, turns local cmd chords into prefix sequences. For an image, ctrl-v on the agent's row in the popup or the app (or paste image in its right-click menu) |
| Mouse-first layout (dragging tabs and workspaces to reorder them) | the same client-rendering gap. herdr resolves every click and drag against its own layout, so it reorders tabs and workspaces when you drag them (its 0.4.7 and 0.4.0 releases). tmux's mouse keys are a table of bindings with a small default set: click a status-line window to select it, drag a border to resize, right-click for a menu, and in tmux 3.8 Alt-drag a pane to move it. A drag on the status line does nothing until you bind it | tmux's own mouse mode for select, resize, menus and (3.8) moving a pane, and taw layout apply / taw pane swap / taw pane move for the rearrangement itself |
theme.auto_switch / dark_name / light_name |
closed on tmux 3.6 and later, with a terminal that reports its light or dark mode: tmux's #{client_theme} says it per client, and the popup and taw app inside tmux draw dark_name or light_name from it. What is left is a gap only where the mode is unknown: an older tmux, a terminal that does not report it, or a client attached inside another tmux, which never gets the answer; the theme is then name |
On such a setup let your OS appearance hook write [theme] name into the config file (taw config path locates it). The popup and taw app read the config when they start, so a changed theme shows in the next popup you open (and the next taw app). taw config check warns when auto_switch is on and the tmux is older than 3.6 |
| Live update handoff | herdr can hand its live panes to a new server as it updates (herdr update --handoff, opt-in and experimental) |
n/a: tmux keeps panes alive independently of taw, so there is nothing to hand off. Replace the binary whenever you like. The pane watchers keep running the one they started with until they are started again: taw watch status marks them and taw watch attach-all --restart does it |
update.channel / update.version_check |
herdr updates itself from a release channel | n/a: TPM updates the plugin, and a taw the plugin downloaded follows it (the version that goes with the new checkout, fetched in the background); one you build with scripts/install.sh is yours to rebuild. Accepted and ignored so a herdr config parses |
[remote] / [server] (manage_ssh_config, server lifecycle) |
these configure herdr's own daemon and its ssh multiplexing | n/a: the tmux server is the server, and other machines are listed under [machines] (see "Saved machines" above). Accepted and ignored: taw config check names their keys as unknown |
| Save before a Linux shutdown (logind's delay lock) | herdr takes a logind delay lock, so its save runs before the system goes down. taw has no process that outlives the tmux server: a save is a taw call, and the hooks that make one stop with the server |
The autosave hooks and the 5 second wait on close keep the layout from the last change, and the copies in session-snapshots/ hold older ones. taw session save before you shut down, or a systemd unit of your own that runs it |
| Bundled sound files | herdr plays its own completion chime and request alert, and takes your mp3 files as overrides | [ui.sound] takes paths to your own files, played through afplay/paplay/aplay. Nothing is bundled, so a sound that is configured but missing is a no-op rather than an error |
Pinned¶
Neither parity nor a gap: recorded so nobody reopens them by accident. A row that has since been resolved stays here, marked, rather than disappearing, since the reasoning is what stops it being reopened.
| Item | Why it is pinned |
|---|---|
Workspace ordering (workspace.move, workspace.move_block) |
Closed: not needed. tmux has no session-order primitive: list-sessions order is not settable, and a session's numeric id is fixed at creation, so a taw-owned order would be one only the popup honoured while tmux list-sessions and the status line kept the real one. The question it would answer is answered another way. The tree is tmux's order, always, and "which one first" is instead answered by attention: Tab walks what needs you, across every session, and the cursor opens on the pane you came from. There is nothing left for a manual ordering to do, so this is closed rather than pinned |
| Windows | tmux does not build or run on Windows, so there is no tmux there for taw to observe. Out of scope, not a defect |