圈内内参|crystl.dev 那份用 tmux 跑 Claude Code 的指南,最该读的不是怎么配,是它最后自己承认的那句:你真要同时管几个等人工决策的 agent,应该考虑更专门的工具。这句话和它前面几千字拧螺丝的内容摆在一起,正好是这期要记的东西。结论先放:tmux 靠伪终端会话附着和那点外部可编程性,成了多代理编排最不性感的底座;但你越把它往代理控制层拧,越能看见它缺的那块正是 agent 本身。信源只有一份公开文档,可查的地方标(确认),我自己的判断标(分析)。这期没有匿名消息源。
tmux 不是给 agent 设计的。这个不用展开讲。2007 年的工具被拿来管 2026 年的 agent,本身不稀奇;稀奇的是它最老的几个部件刚好对上了代理工作流最硬的需求。指南说得很直接:tmux 服务器持有伪终端,所以关掉终端窗口、断 SSH、电脑休眠,代理进程照跑,重连后还能附着回去。这是它最硬的一块骨头,别的可以都先不管。远程机器上跑 Claude Code,光这一条就很难绕开 tmux。
但真正值得记的不是“该怎么用”,而是这份指南把一个接一个 tmux 原语拧成代理控制层时,在边角料里露出的全部错位。我一条条拆开记。
会话附着这个底座,命名规则本身就在替 tmux 说真话。
指南建议按代码仓库建会话,用仓库名命名,别按任务、别按 agent。理由是仓库是稳定单位,任务和 agent 会结束,仓库不会。这条看着像工程直觉,其实是 tmux 给的硬约束:会话是你断开之后附着回去的锚,锚必须长在项目结构上,否则重连的时候自己都找不到该进哪个 session。名字里还不能有句号和冒号,因为点号和冒号在 tmux 的目标定位语法里是分隔符——这是确认的原始规范。我很喜欢这个细节:两个标点符号直接把命名空间给你划好了。
更外露的是 --worktree 和 --tmux 这对组合。较新版本的 Claude Code 给单分支任务提供这两个选项,--worktree 创建 git worktree,--tmux 创建对应会话。但 --tmux 必须跟着 --worktree,单独用直接报错。这一点太能说明问题了:它不是一个通用会话开关,是 worktree 功能的附赠品。官方给你开的口子,开在那个特定形状的洞上。分析一句:这说明工具方真正想推的工作方式是分支隔离,不是会话编排。tmux 在这里是被搭上的。
还有 iTerm2 那个细节。--tmux 在 iTerm2 里默认用原生窗格,不是真 tmux 会话;要拿来做脚本编排,得写 --tmux=classic 才拿到真正的 tmux 会话。一个选项在终端模拟器里要换两套行为,指南里就这么一句带过,没多解释。这个细节本身就是这套工具链拧巴程度的读数。
send-keys 和 capture-pane,是外部可编程性的两个螺丝,也是两个漏水的点。
指南花了不小篇幅讲这两件东西能干什么——shell 可以向运行中的代理发按键、cron 或 git hook 能驱动一个 agent、另一个 agent 能读这个窗格的屏幕。这是用 tmux 做代理编排的原始接口:写进窗格,读出窗格。但一旦落到具体实现,满地都是小刺。
send-keys 的裸参数会被当成按键名。Enter、C-c、Escape、Space 这些都会按按键解释,要发字面文本得加 -l。文本里的分号被 tmux 当成命令分隔符,得转义成 \;。这两个规则加起来是什么效果?你想给一个 agent 发段 prompt,得先想清楚这段文本里哪个字符像按键、哪个符号会拆命令。一个标点符号级别的分号,就够把你从“编排”拉回“终端转义”的地面。
更微妙的是粘贴。很多 agent 输入框在粘贴后有防抖或重绘,指南给的建议是:先发字面文本,等约 0.3 秒,再单独发 Enter。0.3 秒这个数值不是 tmux 的规范,是 TUI 的脾气。这个建议能进指南,意味着写文档的人已经踩过“一口气发文本加回车结果 agent 没吃进输入”的坑。分析:连编排脚本都要按人的输入节奏去迁就 TUI,说明这套东西的输入层没有给程序一个干净的进入点,只能靠 fake key 去挠。
capture-pane 那一侧还要更脆。它读出来的是渲染后的屏幕内容,不是结构化输出。旋转指示符、框线、截断列、ANSI 转义序列,全都混在一起。文档说 -J 可以把自动换行的行重新拼起来,-S -200 读最近 200 行,-S - 读全部回滚缓冲。听着很完整,但你想一下你真正要的东西:这个 agent 现在是在等我确认,还是干完了?capture-pane 给你的是一帧字符画,你得在字符画上做正则。这相当于你想知道程序线程状态,结果每次都要去截图然后人工 OCR。工具层给的不是状态,是渲染。
状态监控那节最诚实,也最让人泄气。
指南给了一套方案:用 #() 在状态栏每 5 秒跑一次脚本,抓每个窗格最近几行做字符串匹配,判断代理是“等待确认”“工作中”还是“空闲”。这个方案本身就是一排红字:它依赖 TUI 的渲染帧文本,轮询间隔 5 秒,会随代理版本或界面变化失效。文档自己把这些都写出来了,不是我在拆台。一个监控机制居然要靠解析屏幕上的字符串来工作,还明确写了“会坏掉”——这已经不是监控,这是祈祷。
轻量替代是 monitor-silence:窗格连续若干秒没输出时触发提醒,配合 alert-silence 钩子执行通知命令。这个靠谱一点,但你得到的信号是“它没在说话”,而不是“它在等你”。“沉默”和“等待”之间隔着一整个代理状态模型,而 tmux 从来没有那个模型。它能告诉你一个窗格有没有在流水,不能告诉你那个 agent 是停在确认页还是在想。
这份指南的结构本身就是一个反讽。
这是我最在意的地方。它先花几千字教你怎么把 tmux 的历史行数调到 100000、开 256 色、开着鼠标、关掉 automatic-rename 防止代理 TUI 改你窗口名、把 escape-time 设成 10 毫秒减少方向键延迟感、用 main-vertical 布局给主窗格加侧边堆叠去同时看两三个 agent——全是在教你怎么用一件 2007 年的工具去撑一个它不知道是什么的工作负载。然后它在结尾说:tmux 无法判断代理是在等待批准、思考还是已完成,也没有代理完成事件通知;当你需要同时管理多个等待人工决策的代理时,应考虑更专门的工具。
它是在教你怎么用,结论是“你真要干这个的时候别用 tmux”。我不认为指南作者精分,我觉得这是当下多代理基础设施的准确快照:我们手里最好的通用件,是一个已经能干活但还没长出代理感知的终端复用器;而我们所有“专门工具”,又都还停在要让你先把工作流搬进某个特定终端的地步。两头都不够。
capture-pane 只能拿渲染后的屏幕,这个局限没救。不同 tmux session 之间代理没有共享状态,想让它们读彼此转录或交接工作,得自己用文件、锁和轮询搭一层。为多个流式输出的代理保留 100000 行历史,内存按窗格吃,服务器一停就全没。pipe-pane 能持续写文件,但写出来的是带转义序列的原始输出,清洗又得自己来。这些东西单看都是老常识,叠在一起时那条线就变得非常直:tmux 给你的持久化是终端字节流的持久化,不是 agent 工作状态的持久化。代理作为一等对象,在 2007 年的信息模型里根本不存在。
再补两个 SSH 侧的细节,因为它们也指向同一件事。远程连 agent 的标准姿势是 ssh 主机 -t tmux new-session -A -s agents,一条命令附着或创建。链路差的时候指南说可以配 mosh。本地和远程同时跑 tmux 会抢前缀键,解决办法是远程换个前缀,或者用 bind C-b send-prefix 按两次。这些都是老玩家的常规操作,但放在这份“面向 AI 代理的 tmux 配置”里,你能感觉到一件事:从物理键盘到 SSH 到本地 tmux 到远程 tmux 再到 agent TUI,这个栈每一层都是人肉时代的东西,agent 不过是坐在最上面一层的新租客。
几个值得盯的信号,我不下注,只列出来一起看。一、Claude Code 下个版本里 --tmux 是会长大成通用会话开关,还是继续绑在 --worktree 上——这决定工具方对“会话作为一等公民”的态度。二、crystl 这类 macOS 多代理终端,谁先提供一个公开的代理完成事件 API,把“等待确认/思考中/已完成”从脚本正则里解放出来。三、tmux 3.x 上游会不会做哪怕一点点面向 agent 的原生适配,比如状态栏里一个结构化的 pane 状态字段。这三条都能在公开 changelog 或官方文档更新里直接对上。本期到这,有在跑多 agent tmux 配置的同学愿意补充内部实践的话,欢迎匿名聊。
