ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

向Tmux要任务可见性:多开AI会话时识别等待输入状态的方案

2026/9/4 15:57:16 拓冰建站 浏览量
向Tmux要任务可见性:多开AI会话时识别等待输入状态的方案 最近在排查一个日志分析任务时我的终端又一次变成了一盘需要人肉维护的状态散沙窗口 1 里有个 AI 会话在帮我整理错误码窗口 2 里有一个 agent 正在补测试用例窗口 3 还把之前跑完的一份方案推进到一半。来回切了几次以后我停在一个窗口前问自己现在到底有几个会话已经停下来正在等我发下一句指令这个问题听起来很小但真正遭遇过的人会知道它足以毁掉整个“多开 AI 并行工作”的体验。你原本是想让几个不同方向的活同时推进结果大量精力被消耗在“谁在等我”的调度判断上。tmux 在这里表现得相当克制窗口都活着会话都没有丢进程也都在可你看不到它们各自处在什么阶段。这段经历让我对 tmux 的定位有了更具体的认知tmux 擅长的是让终端会话持久、可恢复、可切换它不是任务调度器。当你把一个 AI 会话装进一个窗口时它只保证“这个终端不会因为关掉 SSH 而断开”并不会回答“这个 AI 当前是在等待输入还是在持续处理还是已经跑完退出了”。多开 AI 以后tmux 并不是真的不够用它缺少的是任务级状态可见性。1. “tmux 不够用”的本质窗口活了任务状态是盲的这一节想先把问题定义清楚。我们常说的“tmux 不够用”并不是说 tmux 本身有什么功能缺陷而是它作为“容器”和人的使用预期之间出现了错位。1.1 tmux 的天花板不在终端而在任务语义tmux 在设计上考虑的是终端会话的可靠性。它会帮你把任务留在后台断开连接后不会主动杀掉进程重新进入会话后还能看到原来的界面这对运行长任务、远程开发、调试服务来说很关键。它不会主动建立某个“任务当前是什么状态”的概念。tmux 只知道当前 pane 里有没有新输出、进程有没有退出它不理解窗口里运行的是不是 AI不理解“这次输出结束后的停顿”是等待用户指令还是模型内部思考更不理解“最后一行出现一闪一闪的输入光标”意味着这轮对话已经回到了你手里。所以当你把多个 AI 会话放进不同的 tmux 窗口后出现的信息结构其实非常有限窗口有输出说明某个终端里有内容在动窗口没有输出说明终端已经安静了一段时间进程还在说明对应的命令还没退出进程退出说明窗口可能已经回到 shell。这些信息不足以回答“我现在该去处理哪个窗口”。它等于把一个任务调度问题退化成了一堆终端事件让人每次切窗口时都要靠“扫最后几行输出”来重建上下文。1.2 多会话的真正成本是切换后的恢复时间只开一个 AI 会话时tmux 通常不会带来困扰。你切进去看看输出发个指令很顺。但开会话数量达到三五个时问题就会从“工具有没有这个能力”变成“人类使用者能不能记住所有状态”。每个窗口都代表一条独立任务线。每切一次窗口你就要读取最后几行内容确认上一轮结论是什么、当前是否卡住、上下文是否还记得前面的要求。这个过程看起来只要几秒钟遇到复杂的需要追溯的会话恢复成本会被放大。当你有多个窗口同时在等你时你不仅要做任务还要做“任务状态扫描”。这种多会话工作流真正瓶颈往往不是 tmux不是 AI 的输出质量而是人对多个并发任务的跟踪能力。最直接的解法不是强迫自己变得更专注而是让每个会话显式暴露自己的状态。1.3 monitor-activity 和 bell为什么都只能算辅助tmux 自带几种提醒机制。很多人的第一反应会是用monitor-activity让窗口有活动时在状态栏高亮。但在 AI 会话场景里它很可能成为反向干扰。原因在于 AI 的输出往往是流式的。一个会持续打印日志的 agent会让窗口在很长一段时间里一直处于“活动”状态。你看到状态栏某个窗口亮起并不代表它正在等你很可能只是它正在吐字。而当你真正需要被提醒时——比如它已经输出完并在等待你的下一轮指令此时反而没有任何活动事件monitor-activity不会触发。终端响铃bell也不是一个可靠信号。响应式 AI CLI 在等待用户输入时并不会像传统编辑器那样主动响铃。想要用 bell 做提醒前提是程序自己实现了这个动作大部分终端 agent 并没有。倒是有个很少被注意到的配置值得单独说monitor-silence。它不是在窗口有输出时提醒而是在窗口超过指定秒数没有输出时给出提醒。这个语义和“某个 AI 可能已经等了一会儿”更接近实践里更像一个兜底信号适合用来发现“这个窗口已经安静得可疑了”。2. 先别急着写监控脚本要分清“等你”的四种形态在开始写脚本之前更值得做的一件事是把“正在等你”拆细。不同形态的“等你”对应的终端特征、提醒方式和处理动作完全不一样。如果不分清楚很容易写出一个“看起来很智能、实际上不断误报”的监控。2.1 四种常见状态不能用一个规则覆盖从实际使用经验看多个 AI 会话分散在 tmux 里时会出现这几种状态状态表面现象最容易误判的点等待你输入下一轮指令输出已经停止光标停在 AI 的输入框或提示符已经出现这种状态没有任何输出事件activity 不会触发正在生成/思考中输出可能暂时停止但进程活着过一会儿又继续输出LLM 经常有静默思考的现象安静不代表等你命令或工具卡死长时间无输出进程一直挂着既不退出也不继续和“思考中”难以区分需要超时阈值兜底会话已退出或已崩溃pane 内容停在末尾进程不存在可能回到了 shell如果最后没有明显的退出信息容易以为是还在运行这些状态如果混在一起处理监控逻辑就会失真。比如你把“窗口安静超过 30 秒”当成“它在等你”那遇到一个正在做复杂推理的模型每隔十几秒才输出一小段或者干脆静默一分钟再开始说话就会不断误报。一个可靠的方案应该对不同状态采用不同信号。比如“等待输入”应该靠 AI 输出中是否出现约定标记来判断“卡死”应该靠较长时间没有输出变化来判断“会话退出”则可以通过进程状态判断。先分好类后面写代码才不容易糊涂。2.2 肉眼扫描和可解析信号差距在哪里你手动切窗口时其实是靠阅读最后几行文本再结合自己对上下文的记忆才能判断当前是不是“该你说话”。这种判断方式有两个问题第一不是所有输出都能一望而知。有些 CLI 工具即使结束了任务也不会输出一个明确的“等待指令”提示只是光标在闪有时还会输出一大段统计分析最后才问你要不要继续需要人阅读完才能知道已经轮到自己。第二靠阅读意味着必须把注意力完全投入进去。当你开多个 AI 时扫描动作本身也在消耗你的注意力。更好的做法是给 AI 会话增加一个“可解析信号”。让它每次完成一个回合、进入等待用户输入状态时明确输出一行统一标记比如AI_WAIT。这个标记不是给人看的更多是给监控脚本看的。有了它监控就可以从“通过阅读人话来判断”变成“通过识别固定 token 来判断”。有人可能会觉得给 prompt 加这种标记很麻烦而且模型不一定每次都遵守。这是真实情况。它确实不是完美协议但比完全依赖人去读要可靠得多。这就是后面整套轮询方案的基础。3. 一个能跑起来的最小方案结束标记 轮询 通知先不追求完美的工程化你需要的是一个今晚就能试起来的路径。最小方案分三层一是给 AI 会话约定输出标记二是用 tmux 自身做静默提醒三是用 capture-pane 拉取内容判断标记再发桌面通知。3.1 给提示词加一个“回合结束标记”如果你在用命令行里的 AI 客户端并且有自定义系统提示词或任务说明的能力可以在提示词尾部加一条约定任务规则 - 当本轮任务已经结束并等待用户输入下一步指令时请在新的一行输出一行标记AI_WAIT - 不要在正文中间使用这个标记只有本轮真正结束时才输出它。这个做法的原理很简单你不再主观判断“它是不是在等我”而是让 AI 在下一次与你交互前主动给你一个结构化的状态字。脚本只要检测AI_WAIT是否出现就能知道这个窗口已经进入“等待输入”阶段。它有几个明显的边界要接受模型不一定会每次都遵循尤其是被复杂指令分散注意力时如果同一个会话里同时处理多条指令可能会出现提前标记或漏标记模型输出这个标记后如果网络中断或程序崩溃需要兜底判断。所以结束标记是“主要信号”但不是唯一信号。超时和静默提醒仍然是必要的兜底。3.2 先开启 monitor-silence让“安静很久”的窗口浮出来tmux 里有一组与“长时间无输出”相关的配置虽然不如 activity 常用但更适合 AI 会话场景。# 对指定窗口开启静默提醒 tmux setw -t work:0 monitor-silence 30 # 开启后视觉提醒会显示在状态栏 tmux set -g visual-silence onmonitor-silence 30表示如果这个窗口在 30 秒内没有任何输出状态栏会给一个提醒。它的价值在于提醒你“有个窗口已经安静了”这对发现“它可能已经等你一会儿了”很有帮助。代价是它不知道 AI 是在想你还是在等你。当模型进入长思考阶段也会出现持续没有输出的情况所以不建议把monitor-silence的阈值设得太低否则你会在每个窗口之间频繁被告知“它不说话了”。它适合当辅助提醒不适合当唯一答案。先跑通这个配置你至少能凭直觉从状态栏上看出哪个窗口安静异常。3.3 从手工切窗升级为一条捕获命令tmux 提供了一个很关键的能力capture-pane。它可以把某个 pane 当前屏幕的内容抓出来以纯文本形式交给其他命令处理。# 捕获 work 会话里 0 号窗口 0 号 pane 的最后 200 行 tmux capture-pane -p -t work:0.0 -S -200 | tail -n 30先手动跑一下看看你能不能看到刚才约定的AI_WAIT标记。如果能说明监控链路是通的。接下来可以把这个命令和 grep 组合起来做一轮最简单的检测tmux capture-pane -p -t work:0.0 -S -200 | grep -q AI_WAIT echo 这个窗口在等你这一步是整套方案中最核心的动作。之后不管是写循环、发通知还是做状态面板都是在这个捕获结果上做文章。3.4 持续轮询并发送桌面通知一个简化脚本示例下面的 Python 脚本并不复杂它做三件事每隔几秒捕获指定 pane 的文本检查是否出现结束标记如果出现且之前没有通知过就发一条桌面通知。#!/usr/bin/env python3 import subprocess import time import sys HOSTS sys.argv[1:] # 示例work:0.0 work:1.0 work:2.0 states {} def capture(host): out subprocess.check_output( [tmux, capture-pane, -p, -S, -200, -t, host], textTrue, ) return out def notify(host, message): print(f[notify] {host}: {message}) subprocess.run([notify-send, host, message]) while True: for host in HOSTS: body capture(host) if AI_WAIT in body: if states.get(host) ! waiting: states[host] waiting notify(host, AI 会话在等你输入) else: states[host] running time.sleep(5)命令示例python3 ~/bin/tmux_ai_watch.py work:0.0 work:1.0 work:2.0需要说明的是notify-send是 Linux 桌面环境的常见命令macOS 上需要换成osascript或terminal-notifier这类工具。如果你的环境没有桌面通知也可以先把打印信息写到一个汇总文件或者直接用 tmux 的display-message在状态栏里提示。逻辑是一样的。# 捕获几个窗口是否出现等待标记 for host in work:0.0 work:1.0 work:2.0; do if tmux capture-pane -p -t $host -S -200 | grep -q AI_WAIT; then echo $host 正在等你 fi done跑通之后你会明显感到一个变化你不再需要把每个窗口都看一遍只要等通知过来再切到对应窗口去看它接下来要你做什么。4. 提升可靠性历史回溯、状态去重和卡死兜底最小方案能解决“大部分时候哪个窗口在等我”但它离稳定好用还有几个坑要处理。如果跳过这节很容易在实际使用中觉得方案还是靠不住。4.1 capture-pane 默认只抓当前屏幕回溯行数别省tmux capture-pane默认捕获的是 pane 当前可见的那一屏内容。如果某段输出太长让标记滚到了可视区域之外你直接 grep 就会漏掉。因此前面的命令里用了-S -200表示回溯最近的 200 行。这个参数可以按实际情况加大比如你要处理很长的输出可以改成-S -1000。实操建议是先用一个小命令验证一下你捕获范围是否覆盖了标记可能出现的位置tmux capture-pane -p -t work:0.0 | tail -n 3 tmux capture-pane -p -t work:0.0 -S -200 | grep AI_WAIT如果第一条命令里已经能看到标记说明还在屏幕内否则就要检查回溯行数是否够大以及 pane 目标坐标是否正确。4.2 避免同一个状态反复通知如果脚本每 5 秒检查一次发现标记存在就通知一次那个 AI 会话等待你 3 分钟你就会被提醒几十次。所以需要给每个 pane 加一个状态去重逻辑。可以理解为状态从“running”翻转到“waiting”时通知一次进入 waiting 后不再重复触发直到下一次捕获不到标记才把状态重置回 running。上面脚本里的states字典就是这个作用实际使用时也可以改成状态文件方便多个脚本共享。这也改变了监控的语义。你收到通知的瞬间代表的是一个状态翻转事件这个会话刚刚进入等待输入。而如果它已经等了十分钟你之前已经收到过通知那就不需要再被轰炸。4.3 加入卡死判断但要留足“思考余量”等待标记解决的是正常回合的结束但有一种尴尬情况是AI 既没有输出结束标记也没有继续输出只是长时间停在那里。它可能卡在 API 请求上可能等某个子命令超时也可能只是模型进入了一段很长的静默推理。不同工具、不同模型静默行为的差异很大。有些本地模型的思考阶段确实可能十几秒甚至更久没有任何输出但这不叫“等你”也不一定是“卡死”。所以卡死判断必须用宽阈值。一个通用思路是维护每个 host 的“最后有输出时间”如果超过 N 分钟没有输出并且也没有出现等待标记才判定为“可能卡死需要人工检查”。N 的取值要结合场景不要盲目追求敏感。我一般会把“提醒可能卡死”的阈值放在 3 到 5 分钟以上太长会漏报警太短会把长思考误报为卡死。你可以先跑一周观察自己的工具典型静默时间再据此调整阈值。判断卡死的核心还是“有输出时间戳”。每次 capture 到的文本和上一次一样说明界面没有变如果连续多次都一样同时进程还活着那就要怀疑是不是真的卡住了。但要注意如果窗口里恰好有动画光标或进度条即使内容没变化也可能产生活动事件这种情况靠文本比对会更可靠。5. 再往前走一步与其不断盯窗口不如把工作流改成任务闸门到这里我们已经能知道“哪个 AI 在等你”。但如果你的工作流本身设计得不好解决这个问题的价值也会被抵消。比如同时开六个会话即使通知到了你依然会面临一堆待办判断这个会话的结果要不要采纳那个会话是不是改坏了代码另一个会话的结果和当前任务有没有冲突。这已经不是 tmux 能解决的问题而是任务调度思路的问题。5.1 先判断这些任务适不适合并行多个 AI 会话并行并不总是好事。最典型的反面场景是三个 agent 同时改同一个仓库。如果它们在同一个代码副本里写代码很容易产生覆盖、冲突、上下文互相踩踏的问题。你收到的可能不是“哪个在等我”而是“哪个把我的代码改成了一团乱麻”。真正适合在 tmux 里同时跑的通常是彼此边界清晰的任务不同模块的文档分析互相独立的日志排查不同目录下的测试代码生成可以用独立分支或不同目录隔离的实验。如果任务之间需要共享同一个工作目录那更适合让它们串行处理或者干脆用任务队列而不是让多个 agent 同时扑上去。多开 AI 的计划一定要在启动前先确认任务彼此独立。5.2 给每个任务一个独立目录让 AI 变成“结果生成器”一个能明显降低多会话管理成本的做法是为每个 AI 任务分配一个独立工作目录并把任务的最终输出写到固定的结果文件。work/ ├── job-01-log-analysis/ │ ├── prompt.md │ └── output.md ├── job-02-test-writing/ │ ├── prompt.md │ └── output.md └── job-03-doc-proofreading/每个窗口只负责进入对应目录跑对应任务。结束标记出现后你只需要切到那个窗口查看结果文件决定是否接受、修改或重跑。如果结果符合预期这个任务就可以关闭如果不符合把新的反馈追加到prompt.md再让 AI 继续。这种组织方式的好处是状态不只存在于 AI 会话的上下文里也以文件的形式留到了磁盘上。即使 tmux 窗口全部误删或者你隔了一天才回来检查只要任务目录还在你就能知道当时让它做了什么、目前输出到什么程度。这比靠一串会话记录更踏实。5.3 真正要管理的不是窗口而是“谁有权占用你的注意力”当你把多个 AI 会话变成并行任务后“哪个窗口在等你”这个问题会逐渐演变成“哪个结果值得我现在投入注意力”。这背后有一个不太容易被意识到的规律你同时能接收和处理的任务数不是无限的。如果你的工作流是让 5 个 AI 会话同时产出并且都等着你逐个人工确认那你就成了新的瓶颈。结果不是效率变高而是你被 AI 的通知牵着走。所以与其无限增加窗口不如先稳住节奏。更多实践里的体面做法是同一时间只保留一两个需要你交互的 AI 会话另一批可以批量化、不需要频繁判断的任务用脚本加队列去跑。tmux 在这里的价值不是替你调度任务而是提供一个不会丢失会话的稳定底座。真正的调度逻辑仍然需要你用目录结构、输出标记和轮询脚本来搭建。6. 排查链路、常见误判与适用边界这套监控方案并不复杂但它涉及 tmux、终端输出、桌面通知、提示词约定四个环节任何一环出问题都会导致“明明在等你却没提醒”或“根本没在等你却不断提醒”。遇到问题不能靠猜按链路一层层验证更高效。6.1 按这个顺序排查捕获、坐标、标记、通知我自己的排查顺序通常是这样的先确认tmux capture-pane -p -t host能正常输出内容再确认host写对了用tmux list-panes查完整坐标然后确认标记是否在捕获范围内必要时加大-S的行数接着检查标记字符是否完全一致注意大小写、空格、是否被终端转义影响最后再测试通知命令本身能不能弹出来权限是否允许桌面通知。如果脚本里用了grep -q还要注意它在没有匹配时会返回非 0 退出码。如果 shell 开启了set -e整个脚本很容易在半路退出看起来就像“没有发现问题”实际是脚本已经停了。6.2 常见误判对号入座我在使用中踩过比较典型的几个坑列出来供你检查monitor-activity在流式输出时会高频触发不适合当作“等待我”的信号monitor-silence能发现安静但会把长思考和卡死混在一起需要配合超时阈值结束标记离窗口末尾很远时只抓当前屏幕的 grep 会漏报模型有时不按提示词输出标记需要靠静默提醒兜底多个 pane 都在等通知时如果状态去重没做好会变成连环通知轰炸捕获文本里如果混入大量转义序列会干扰 grep可以先肉眼观察一段输出再判断。解决转义干扰的办法不是直接写复杂过滤器而是先看输出是什么样。如果tmux capture-pane -p得到的文本已经可读就不需要额外处理如果看到了一大堆\x1b[开头的颜色码说明你的终端工具在输出 ANSI 控制序列可能需要先清洗或者在客户端层面关闭彩色输出。6.3 这个方法适合谁不适合谁如果工作流主要是终端里的 CLI agent比如