现在我的 tmux 里常年开着好几个 AI Agent:这个 session 里 Claude Code 在改 bug,那个 session 里 pi 在整理笔记,另一个窗口里还有一个在跑长任务。Agent 干活的时候人可以去做别的事,但问题也随之而来——我得隔一会儿就把窗口挨个切一遍,看看谁跑完了、谁正被权限确认框卡住干等着我。窗口一多,这件事就变得又烦又容易漏。

我真正想要的东西很简单:一眼看到所有 Agent 的状态。在干活的、等我输入的、已经闲下来的,各是哪几个,在哪个 pane 里。

通知解决不了本质问题

在这之前我试过一些别的办法。比如用 Stop hook 在 Agent 结束时发系统通知,也试过让 Agent 往 IRC 频道里发消息。它们都能用,但都没有解决本质问题:我要知道的是当前状况,而通知只能反映某个时间点的状况

脱节是这样发生的:通知说「任务完成了」,但如果我当时没有理会,而是后来直接切到对应终端继续发消息,Agent 就又跑起来了——通知列表里躺着的那条「已完成」从此与现实脱节。事件流没有「撤回」和「更新」,它只会越积越多,而我永远无法确定哪条还作数。

IRC 的问题还要多一层:消息要 Agent 自己发,这意味着上报这件事本身要挤占它的上下文——要在提示词里交代清楚、要在恰当的时机想起来发、发的内容还会混进后续对话里,造成上下文污染。为了旁观 Agent 的状态而去打扰 Agent 本身,这个方向从根上就不太对。

所以结论是:我需要的不是事件推送,而是一个随时反映现实的状态视图——它应该从外部观察得来,不依赖 Agent 的自觉,也永远不会过期。

Herdr 是怎么做的

这个需求已经有人解决过了:Herdr(在新标签页打开) 是一个专门为 coding agent 设计的终端运行时,它的侧边栏就能显示每个 pane 里 Agent 的状态。我翻了它的源码,发现状态检测的实现相当讲究,是一套「证据驱动」的机制:

  1. 进程识别:从 pane 的前台进程组出发识别出正在运行的是哪个 Agent,能穿透 node/python/shell 这类包装进程、符号链接甚至 Nix 的 wrapper。
  2. 屏幕检测:周期性读取终端底部缓冲的文本,用每个 Agent 一份的 TOML 规则清单(manifest)去匹配——比如 Claude Code 的权限弹窗里一定有「Do you want to proceed?」加编号选项,空闲时提示框里有 ,工作时终端标题会出现盲文旋转符。规则带优先级、区域切分和布尔组合,还处理了大量边角情况(比如浏览历史记录的界面要冻结状态,不能误判)。
  3. Hook 上报:对支持的 Agent 安装生命周期钩子,让 Agent 自己通过 socket 汇报状态,比屏幕检测更精确。

这套机制在实践中被打磨得很成熟,尤其是那些 manifest 规则,全是踩坑踩出来的。

但我不想换掉 tmux

试用下来,Herdr 本身却没能留住我,原因说起来有点微妙:操作手感。它的快捷键和 tmux 不完全一样,长期养成的肌肉记忆时不时就会按错;想完全退出它也不太方便。这类终端复用器是典型的「手感产品」——每天要摸几百次的东西,别扭一点点都会被无限放大。

于是想法就变成了:机制是好机制,但我不需要换一个运行时。Agent 就跑在我现有的 tmux 里,我只需要一个「旁观者」——把 Herdr 的检测机制搬出来,做成一个独立的只读监控器。

技术上这条路出乎意料地顺:Herdr 检测所需的三样输入,tmux 全都有现成的原语可以对应。它读的「终端底部缓冲」就是 tmux capture-pane -p 给出的可见屏幕(而且不受用户在 copy-mode 里翻页的影响);它用的 OSC 终端标题就是 #{pane_title};进程识别从 #{pane_pid} 出发用 macOS 的 proc_pidinfo 一路就能查下去。Herdr 是 Apache-2.0 的,19 份 manifest 规则文件可以原样拿来用,只要自己实现一个语义兼容的规则引擎。

全程交给 Claude Fable 5

这个项目从头到尾由 Claude Fable 5(Claude Code)完成,整个过程在同一个会话里:

  • 先让它研究 Herdr 的源码,把状态检测机制的来龙去脉讲清楚;
  • 问它这套手段能不能脱离 Herdr 用在现有 tmux 上,它给出了逐条映射分析;
  • 进入计划模式,它派子 agent 把规则引擎的精确语义(区域切分、布尔门、优先级仲裁)和 macOS 进程识别的系统调用细节全部核实了一遍,问了我四个决策问题(项目名、规则覆盖范围、要不要 hook 通道、要不要交互跳转),然后拿出完整方案;
  • 按五个里程碑实施:tmux 发现层 → 进程识别 → 检测引擎 + 调试工具 → TUI → 防抖与文档,每步都有单元测试,最后 51 项测试全绿;
  • 顺手建了 GitHub 仓库、写了 README 和设计文档、补了带 PREFIX 的传统 Makefile。

中途还有个意外收获:它跑第一版进程识别时发现我的 tmux pane 里全是识别不出 Agent 的 koshell——我的 shell 包装器会再分配一层嵌套 PTY,Agent 实际运行在内层 tty 上。它当场定位了原因,加了「沿不同 tty 的子进程下钻」的逻辑才解决。这种环境特有的坑,事先做计划时根本想不到。

成品就是 tmux-agent-watch(在新标签页打开):一个 Rust 写的 TUI,每两秒轮询一次,以 session → window → pane 的树状结构只展示包含 Agent 的分支,用红绿灯标注状态——🔴 被卡住等输入、🟢 正在干活、⚪ 空闲。它对 tmux 严格只读(只用 list-panescapture-pane),不改任何 Agent 的配置。检测不准的时候,还有个 --explain %N 模式能打印出引擎看到的屏幕内容和每条规则的求值过程,排查误判非常好用。

对我来说,这件事最有意思的地方在于分工:Herdr 的作者积累了「怎么从屏幕上认出 Agent 状态」这个真正难的知识,开源协议允许这份知识被复用;而把它移植到另一个形态的工程活,一次会话就让 AI 干完了。我出的是需求、四个决策和验收,剩下的——包括读懂别人的源码——都不用自己动手。