Herdr 和 tmux 经常被放在一起比较,因为两者都能让终端进程在客户端断开之后继续运行。但它们的内部结构并不相同:tmux 是一个通用的 Terminal Multiplexer,Herdr 则是在持久终端之上增加 Agent 语义的 Terminal Workspace Manager。
如果只比较功能清单,很容易得出「Herdr 就是带界面的 tmux」或者「Herdr 只是多了 Agent 检测」这类结论。把原理拆开之后,两者的边界会清楚很多。本文从 PTY、Server/Client 模型、持久化机制和 Agent 检测几个层面解释 Herdr 如何工作,并逐层和 tmux 对照。
结论速览:
- 两者都由后台 Server 持有 PTY,因此 Client 断开后进程仍可继续运行。
- tmux 管理通用终端;Herdr 在此之上增加项目 Workspace、Agent 识别、状态汇总与受支持 Agent 的对话恢复。
- Server 或 Host 停止后,两者都不能保留原来的操作系统进程;重启后的恢复本质上是重建。
- 需要标准、可移植的 Multiplexer 时选择 tmux;需要在多个项目间管理 Coding Agent 注意力时选择 Herdr。
本文讲的是两者「如何工作」,不是安装教程。安装和手机工作流见 Herdr 是什么 和 如何从手机运行 Codex。
一切从 PTY 说起
要理解任何 Terminal Multiplexer,先要理解 PTY(Pseudo-Terminal,伪终端)。
当你在图形终端里打开一个 Shell,终端应用并不会直接连到 Shell 的进程上。内核会在中间建立一对 PTY:
- Slave(从端) 连接 Shell 的 stdin、stdout、stderr,并成为 Shell 所在进程组的 Controlling Terminal。
- Master(主端) 由终端应用持有,用来读写这块「屏幕」。
这个结构解释了一个日常现象:直接关掉普通终端窗口,正在跑的 npm run build 或 Coding Agent 也会跟着结束。原因是 Master 端被关闭后,内核会向前台进程组发送 SIGHUP,进程默认会退出。
Multiplexer 的核心技巧,就是把这个只属于「你当前窗口」的 Master 端,交给一个长期运行的进程持有。只要 Master 端不关闭,前台进程就不会收到 SIGHUP,也就不会因为客户端离开而死掉。
tmux 和 Herdr 都建立在这个机制上。区别出现在 Master 端之上:这个长期进程还管理什么,暴露什么模型。
Herdr 的两层结构:Server 与 Client
Herdr 是单一 Rust Binary,但运行时分成两个角色。
Server 是一个后台长驻进程,它负责:
- 持有每个 Pane 的 PTY Master 端和其中的子进程。
- 保存终端屏幕状态,包括可见区域和历史 Scrollback。
- 维护 Workspace、Tab、Pane 的对象树和 Layout。
- 记录识别到的 Agent 及其状态。
- 在一个本地 Unix Domain Socket 上监听(按 Session 命名空间划分)。
Client 就是你在终端里看到的 TUI。它通过 Socket 连接到 Server,把按键和窗口尺寸变化发回去,接收屏幕更新并渲染。
这个划分带来几个直接结果:
- Pane 里进程的持有者是 Host 上的 Server,而不是你的 SSH 连接或手机。
- Detach 的本质只是 Client 关闭 Socket,Server 和其中所有进程继续运行。
- 同一个 Session 可以从不同 Client Attach;本地窗口和手机 SSH 看到的是同一份状态。
- Client 在 Host 上运行并连接本地 Socket;远程使用时,SSH 把 Client 的终端字节流传到你的设备。
Herdr 的对象模型
理解了 Server 之后,剩下的概念其实是同一棵树上不同层级的节点。
| 对象 | 作用 | 大致对应 tmux |
|---|---|---|
| Server | 持有 PTY 和进程的后台进程 | tmux server |
| Session | 一个 Server 命名空间、Socket 和运行状态 | tmux session |
| Workspace | 一个仓库、任务或 Investigation 的容器 | tmux 没有直接对应 |
| Tab | Workspace 内的一套 Layout | tmux window |
| Pane | 运行 Shell、Agent、测试或日志的真实终端 | tmux pane |
| Agent | Herdr 从 Pane 中识别出的 Coding Agent 进程 | tmux 不识别 |
Session 和 Workspace 的区别一开始最容易混淆。简单的理解是:Session 是「哪一台 Server 在跑」,Workspace 是「哪个项目」。默认 Session 里完全可以放多个项目 Workspace,只有确实需要独立 Runtime State 时才创建 Named Session。
持久化的原理:Detach 之后进程为什么还活着
把上面的机制串起来,就是完整的持久化过程:
- Server 创建一对 PTY,fork 出子进程,让子进程以 Slave 端作为 Controlling Terminal。
- Server 自己保留 Master 端,并持续读取输出、更新屏幕模型。
- Client Detach 时,关闭的是 Client 与 Server 之间的 Socket,不是 Master 端。
- 因为没有 Master 关闭事件,内核不会发送
SIGHUP,前台进程继续运行,输出继续被 Server 消费。 - 再次 Attach 时,新 Client 收到一份当前屏幕加 Scrollback 的快照,然后接入实时输出流。
这条链路也是 tmux 的行为。所以在「客户端断开、进程继续」这一层,两者本质相同。
差别在 Server 真正停止之后。此时 Master 端随进程消失,旧进程已经结束,没有任何工具能让它们复活。剩下的只有「重建」:
- tmux 核心并不保存跨 Server 的会话,通常依靠
tmux-resurrect、tmux-continuum这类 Plugin 保存 Layout 和命令,重启后再重新拉起。 - Herdr 可以恢复保存的 Workspace、Tab、Pane、Working Directory、Layout 和 Focus;安装了官方 Integration 后,还能用原生 Session ID Resume 一部分受支持的 Agent 对话。
两者都属于重建,不是进程永生。被恢复的 Codex 或 Claude Code 对话,不代表被打断的编译器、开发服务器或普通 Shell 进程也穿过了重启。
Agent 识别的原理
到这里为止,Herdr 和 tmux 的机制是同一套。真正分叉的地方,是长期进程在 Master 端之上「理解」了什么。
tmux 只知道 Pane 和前台命令。你可以用格式字符串拿到 #{pane_current_command},但它只是进程名,没有任务语义:它不知道这个 node 是开发服务器还是 Agent,也不知道对方正在工作还是在等人回答。
Herdr 会先判断 Pane 的前台进程,然后针对受支持的 Agent 组合多种信号:
- 前台进程:确定这个 Pane 里运行的是哪个 Agent Binary。
- Lifecycle Hook:Agent 或 Shell Integration 在关键节点上报事件。
- Screen Snapshot:把屏幕底部最近的输出,与 Detection Manifest 里已知的 Prompt Shape 做匹配。
识别结果会落到一组状态上:
| 状态 | 含义 |
|---|---|
working |
Agent 正在执行 |
blocked |
出现了已知的审批、提问或 Permission UI,正在等待输入 |
done |
后台任务已经完成,但还没有被查看 |
idle |
Agent 已经完成或可以接收输入,且状态已经被看过 |
unknown |
识别到了 Agent,但无法可靠判断状态 |
Herdr 对 blocked 的判断刻意保守。如果某个新版本 Agent 出现了没见过的 Prompt,它可能显示为 Idle 而不是 Blocked。这个误差只影响状态展示,不会让 Herdr 自动批准操作或发送破坏性输入。批准之前仍然应该查看真实 Pane。
状态如何汇总成注意力队列
Agent 检测本身只是输入。Herdr 真正和 tmux 拉开距离的地方,是把这些状态沿对象树向上汇总:某个 Pane 变成 blocked,对应的 Tab 和 Workspace 就会显示需要关注;done 则提示有结果可以 Review。
于是多个项目里的 Agent 不再是一排猜不出内容的名字,而是一个按项目组织的注意力队列。这也是它被称为 Terminal Workspace Manager 而不是 Multiplexer 的原因:它管理的不是「终端数量」,而是「哪个项目需要你」。
原生 Agent Session 恢复的原理
官方 Integration 的作用,是在 Agent 自己的 Hook 配置里记录原生 Session Identity。Agent 通常已经支持用自己的 Session ID 恢复对话(类似 --resume),Integration 只是把这个 ID 交给 Herdr 保管。
当 Herdr Server 重启后,它可以带着记录的 Session ID 重新拉起 Agent,从而恢复对话上下文。要强调的是:这是一个新进程,恢复的是 Agent 对话,不是原来的操作系统进程。被中断的构建、日志流或本地服务不会因此继续。
tmux 的原理与对象模型
tmux 也采用 Client/Server 模型。第一次运行 tmux 时,它会在后台启动一个 Server,并在类似 /tmp/tmux-<uid>/default 的 Unix Socket 上监听;之后的 tmux 命令其实都是 Client,把命令发给 Server 执行。
它的对象层级是 Server → Session → Window → Pane。每个 Pane 同样是一个带 PTY 的进程。交互方式则是 prefix key(默认 Ctrl+b)加命令语言:
tmux new -s work # 新建 session
tmux ls # 列出 session
tmux attach -t work # 重新 attach
tmux send-keys -t work 'npm test' Enter
几个 tmux 特有、值得单独理解的机制:
- 命令语言:几乎所有操作都可以脚本化,
send-keys、split-window、select-pane都可以从任意程序调用。 - Control Mode(
tmux -CC):输出变成机器可读的事件流,iTerm2 等应用借此把 tmux 会话映射成原生窗口。 - 配置文件与 Plugin:
.tmux.conf、Hooks、run-shell和社区 Plugin 提供了很强的可定制性。
成熟、可移植、依赖少,是 tmux 被广泛默认安装的原因。
逐层对比 Herdr 和 tmux
先看架构分层:
| 层面 | tmux | Herdr |
|---|---|---|
| 运行模型 | Client / Server + Socket | Client / Server + Socket |
| 终端机制 | PTY + 进程组 | PTY + 进程组 |
| 对象模型 | Session / Window / Pane | Session / Workspace / Tab / Pane / Agent |
| 交互方式 | Prefix Key + 命令语言 | Sidebar + 注意力队列 + 快捷键 |
| 跨客户端持久化 | 支持 Detach / Reattach | 支持 Detach / Reattach |
| Agent 语义 | 无 | 识别、状态、汇总、原生恢复 |
| 扩展方式 | Hooks、Plugin、Control Mode | CLI、Socket API、Integration、Plugin |
再看使用取向:
| 选择 | 更适合 |
|---|---|
| tmux | 少量持久终端;重视标准、可移植、可脚本化;已有成熟的 tmux 工作流;不需要 Agent 语义 |
| Herdr | 同时运行多个 Coding Agent;需要项目 Workspace、可见 Agent 状态、直接 Agent Attach、API 和原生 Session Restore |
一句话概括:tmux 管理终端,Herdr 管理项目里的 Agent。 当终端只是终端时,tmux 更简单;当终端里跑的是多个项目的 Coding Agent 时,Herdr 多出来的那一层才开始产生价值。
组合使用时要注意的坑
两者可以共存,但嵌套方向很关键。
- Herdr 跑在 tmux 里作为外层环境通常没问题。
- 不要在 Herdr Pane 内再用一层 tmux 包住 Codex 或 Claude Code。此时 Herdr 看到的前台进程是
tmux,而不是后面的 Agent,Agent 检测会失效。 - SSH → tmux → Agent 是正常的,因为 tmux 本来就不需要 Agent 检测。
- 同时启用两层持久化时,要清楚「谁负责恢复」。让 Herdr 管理 Agent Workspace,把 tmux 留给它真正擅长的通用终端场景,通常更清晰。
更聚焦的 tmux 手机配置见 如何从手机运行 Claude Code;两者区别的入门版本见 Herdr 是什么。
Redock 在这里做什么
Redock 不改变 Herdr 或 tmux 的内部机制,它处理的是「从手机回到这些持久层」这一段。
普通 SSH 加一条 herdr 命令已经能跑通整套流程,摩擦通常出现在手机成为常用控制端之后:选择正确的 Host、找到仓库入口、重新进入 Session,以及反复运行 Git、测试、构建和日志命令。
Redock 的做法是把 Session Persistence(Herdr 或 tmux)集成到 Host 和 Project 工作流里:Host 保存连接方式,Project 绑定仓库目录,Action 承担 Agent 周围那些可重复的命令,并把输出保留成 Run。Herdr 继续管理 Host 上的持久 Terminal Workspace,Redock 则让手机更容易回到正确的位置。
手机端的具体操作见 在 Redock 中深入使用 Herdr:对话视图与 Pane 切换。
原理层面的边界
- 不提供算力:Herdr 和 tmux 都不能让休眠或关机的机器继续执行任务。
- 不保证进程永生:Server 停止,旧进程就结束了,之后只能重建。
- Agent 检测是启发式的:Screen 匹配可能滞后于 Agent UI 更新,
blocked只在识别到已知 Prompt 时出现。 - 不解决并发修改:多个 Agent 并行改代码时,仍然需要 Branch 或 Worktree 做 Git 隔离。
- 安全仍由用户负责:限制 SSH 访问、优先使用 Key、保护 Host 账号,并把 Pane History 当作敏感数据处理。
FAQ
Herdr 和 tmux 的原理一样吗?
底层很像。两者都用 Client/Server 模型、Unix Socket 和 PTY 来保证 Detach 后进程继续运行。区别在持久终端之上:tmux 只管理 Session、Window、Pane,Herdr 增加了 Workspace 和 Agent 识别。
Herdr 可以替代 tmux 吗?
在 Agent 密集型工作流中可以。如果只是管理少量持久终端,tmux 更简单、更标准、可移植性更好。Herdr 额外提供 Workspace、Agent 状态汇总、API、Plugin 和原生 Agent Restore。
Detach 之后进程为什么还活着?
因为持有 PTY Master 端的是 Host 上的 Server,而不是你的客户端。Client 断开只关闭了 Client 与 Server 之间的 Socket,Master 端没有关闭,内核就不会向前台进程发送 SIGHUP。
为什么不要在 Herdr 里再套 tmux 运行 Agent?
Herdr 判断前台进程时只会看到 tmux,看不到它后面的 Agent,因此无法识别状态或进行原生 Session 恢复。
Herdr 支持哪些 Agent?
Herdr 可以自动识别 Claude Code、Codex、OpenCode、Pi、GitHub Copilot CLI、Cursor Agent CLI、Devin CLI、Grok CLI、Kimi Code CLI 和 Qwen Code 等常见 Agent。其他终端程序仍可正常运行,只是可能没有完整的 Agent 状态。
结语
Herdr 和 tmux 在终端机制这一层是同类:它们都把 PTY Master 端交给一个后台进程,让进程不再依附于某一次连接。分叉发生在这一层之上——tmux 把它抽象成 Session、Window 和 Pane,Herdr 把它抽象成项目 Workspace 和 Agent 状态。
理解了这一点,选择就变得简单:需要的是一个可靠的 Multiplexer,就用 tmux;需要的是让多个项目里的 Agent 保持可见并知道谁在等你,就用 Herdr。