可見性:多開AI會話時識別等待輸入狀態(tài)的方案)
最近在排查一個日志分析任務(wù)時我的終端又一次變成了一盤需要人肉維護的狀態(tài)散沙窗口 1 里有個 AI 會話在幫我整理錯誤碼窗口 2 里有一個 agent 正在補測試用例窗口 3 還把之前跑完的一份方案推進到一半。來回切了幾次以后我停在一個窗口前問自己現(xiàn)在到底有幾個會話已經(jīng)停下來正在等我發(fā)下一句指令這個問題聽起來很小但真正遭遇過的人會知道它足以毀掉整個“多開 AI 并行工作”的體驗。你原本是想讓幾個不同方向的活同時推進結(jié)果大量精力被消耗在“誰在等我”的調(diào)度判斷上。tmux 在這里表現(xiàn)得相當克制窗口都活著會話都沒有丟進程也都在可你看不到它們各自處在什么階段。這段經(jīng)歷讓我對 tmux 的定位有了更具體的認知tmux 擅長的是讓終端會話持久、可恢復(fù)、可切換它不是任務(wù)調(diào)度器。當你把一個 AI 會話裝進一個窗口時它只保證“這個終端不會因為關(guān)掉 SSH 而斷開”并不會回答“這個 AI 當前是在等待輸入還是在持續(xù)處理還是已經(jīng)跑完退出了”。多開 AI 以后tmux 并不是真的不夠用它缺少的是任務(wù)級狀態(tài)可見性。1. “tmux 不夠用”的本質(zhì)窗口活了任務(wù)狀態(tài)是盲的這一節(jié)想先把問題定義清楚。我們常說的“tmux 不夠用”并不是說 tmux 本身有什么功能缺陷而是它作為“容器”和人的使用預(yù)期之間出現(xiàn)了錯位。1.1 tmux 的天花板不在終端而在任務(wù)語義tmux 在設(shè)計上考慮的是終端會話的可靠性。它會幫你把任務(wù)留在后臺斷開連接后不會主動殺掉進程重新進入會話后還能看到原來的界面這對運行長任務(wù)、遠程開發(fā)、調(diào)試服務(wù)來說很關(guān)鍵。它不會主動建立某個“任務(wù)當前是什么狀態(tài)”的概念。tmux 只知道當前 pane 里有沒有新輸出、進程有沒有退出它不理解窗口里運行的是不是 AI不理解“這次輸出結(jié)束后的停頓”是等待用戶指令還是模型內(nèi)部思考更不理解“最后一行出現(xiàn)一閃一閃的輸入光標”意味著這輪對話已經(jīng)回到了你手里。所以當你把多個 AI 會話放進不同的 tmux 窗口后出現(xiàn)的信息結(jié)構(gòu)其實非常有限窗口有輸出說明某個終端里有內(nèi)容在動窗口沒有輸出說明終端已經(jīng)安靜了一段時間進程還在說明對應(yīng)的命令還沒退出進程退出說明窗口可能已經(jīng)回到 shell。這些信息不足以回答“我現(xiàn)在該去處理哪個窗口”。它等于把一個任務(wù)調(diào)度問題退化成了一堆終端事件讓人每次切窗口時都要靠“掃最后幾行輸出”來重建上下文。1.2 多會話的真正成本是切換后的恢復(fù)時間只開一個 AI 會話時tmux 通常不會帶來困擾。你切進去看看輸出發(fā)個指令很順。但開會話數(shù)量達到三五個時問題就會從“工具有沒有這個能力”變成“人類使用者能不能記住所有狀態(tài)”。每個窗口都代表一條獨立任務(wù)線。每切一次窗口你就要讀取最后幾行內(nèi)容確認上一輪結(jié)論是什么、當前是否卡住、上下文是否還記得前面的要求。這個過程看起來只要幾秒鐘遇到復(fù)雜的需要追溯的會話恢復(fù)成本會被放大。當你有多個窗口同時在等你時你不僅要做任務(wù)還要做“任務(wù)狀態(tài)掃描”。這種多會話工作流真正瓶頸往往不是 tmux不是 AI 的輸出質(zhì)量而是人對多個并發(fā)任務(wù)的跟蹤能力。最直接的解法不是強迫自己變得更專注而是讓每個會話顯式暴露自己的狀態(tài)。1.3 monitor-activity 和 bell為什么都只能算輔助tmux 自帶幾種提醒機制。很多人的第一反應(yīng)會是用monitor-activity讓窗口有活動時在狀態(tài)欄高亮。但在 AI 會話場景里它很可能成為反向干擾。原因在于 AI 的輸出往往是流式的。一個會持續(xù)打印日志的 agent會讓窗口在很長一段時間里一直處于“活動”狀態(tài)。你看到狀態(tài)欄某個窗口亮起并不代表它正在等你很可能只是它正在吐字。而當你真正需要被提醒時——比如它已經(jīng)輸出完并在等待你的下一輪指令此時反而沒有任何活動事件monitor-activity不會觸發(fā)。終端響鈴bell也不是一個可靠信號。響應(yīng)式 AI CLI 在等待用戶輸入時并不會像傳統(tǒng)編輯器那樣主動響鈴。想要用 bell 做提醒前提是程序自己實現(xiàn)了這個動作大部分終端 agent 并沒有。倒是有個很少被注意到的配置值得單獨說monitor-silence。它不是在窗口有輸出時提醒而是在窗口超過指定秒數(shù)沒有輸出時給出提醒。這個語義和“某個 AI 可能已經(jīng)等了一會兒”更接近實踐里更像一個兜底信號適合用來發(fā)現(xiàn)“這個窗口已經(jīng)安靜得可疑了”。2. 先別急著寫監(jiān)控腳本要分清“等你”的四種形態(tài)在開始寫腳本之前更值得做的一件事是把“正在等你”拆細。不同形態(tài)的“等你”對應(yīng)的終端特征、提醒方式和處理動作完全不一樣。如果不分清楚很容易寫出一個“看起來很智能、實際上不斷誤報”的監(jiān)控。2.1 四種常見狀態(tài)不能用一個規(guī)則覆蓋從實際使用經(jīng)驗看多個 AI 會話分散在 tmux 里時會出現(xiàn)這幾種狀態(tài)狀態(tài)表面現(xiàn)象最容易誤判的點等待你輸入下一輪指令輸出已經(jīng)停止光標停在 AI 的輸入框或提示符已經(jīng)出現(xiàn)這種狀態(tài)沒有任何輸出事件activity 不會觸發(fā)正在生成/思考中輸出可能暫時停止但進程活著過一會兒又繼續(xù)輸出LLM 經(jīng)常有靜默思考的現(xiàn)象安靜不代表等你命令或工具卡死長時間無輸出進程一直掛著既不退出也不繼續(xù)和“思考中”難以區(qū)分需要超時閾值兜底會話已退出或已崩潰pane 內(nèi)容停在末尾進程不存在可能回到了 shell如果最后沒有明顯的退出信息容易以為是還在運行這些狀態(tài)如果混在一起處理監(jiān)控邏輯就會失真。比如你把“窗口安靜超過 30 秒”當成“它在等你”那遇到一個正在做復(fù)雜推理的模型每隔十幾秒才輸出一小段或者干脆靜默一分鐘再開始說話就會不斷誤報。一個可靠的方案應(yīng)該對不同狀態(tài)采用不同信號。比如“等待輸入”應(yīng)該靠 AI 輸出中是否出現(xiàn)約定標記來判斷“卡死”應(yīng)該靠較長時間沒有輸出變化來判斷“會話退出”則可以通過進程狀態(tài)判斷。先分好類后面寫代碼才不容易糊涂。2.2 肉眼掃描和可解析信號差距在哪里你手動切窗口時其實是靠閱讀最后幾行文本再結(jié)合自己對上下文的記憶才能判斷當前是不是“該你說話”。這種判斷方式有兩個問題第一不是所有輸出都能一望而知。有些 CLI 工具即使結(jié)束了任務(wù)也不會輸出一個明確的“等待指令”提示只是光標在閃有時還會輸出一大段統(tǒng)計分析最后才問你要不要繼續(xù)需要人閱讀完才能知道已經(jīng)輪到自己。第二靠閱讀意味著必須把注意力完全投入進去。當你開多個 AI 時掃描動作本身也在消耗你的注意力。更好的做法是給 AI 會話增加一個“可解析信號”。讓它每次完成一個回合、進入等待用戶輸入狀態(tài)時明確輸出一行統(tǒng)一標記比如AI_WAIT。這個標記不是給人看的更多是給監(jiān)控腳本看的。有了它監(jiān)控就可以從“通過閱讀人話來判斷”變成“通過識別固定 token 來判斷”。有人可能會覺得給 prompt 加這種標記很麻煩而且模型不一定每次都遵守。這是真實情況。它確實不是完美協(xié)議但比完全依賴人去讀要可靠得多。這就是后面整套輪詢方案的基礎(chǔ)。3. 一個能跑起來的最小方案結(jié)束標記 輪詢 通知先不追求完美的工程化你需要的是一個今晚就能試起來的路徑。最小方案分三層一是給 AI 會話約定輸出標記二是用 tmux 自身做靜默提醒三是用 capture-pane 拉取內(nèi)容判斷標記再發(fā)桌面通知。3.1 給提示詞加一個“回合結(jié)束標記”如果你在用命令行里的 AI 客戶端并且有自定義系統(tǒng)提示詞或任務(wù)說明的能力可以在提示詞尾部加一條約定任務(wù)規(guī)則 - 當本輪任務(wù)已經(jīng)結(jié)束并等待用戶輸入下一步指令時請在新的一行輸出一行標記AI_WAIT - 不要在正文中間使用這個標記只有本輪真正結(jié)束時才輸出它。這個做法的原理很簡單你不再主觀判斷“它是不是在等我”而是讓 AI 在下一次與你交互前主動給你一個結(jié)構(gòu)化的狀態(tài)字。腳本只要檢測AI_WAIT是否出現(xiàn)就能知道這個窗口已經(jīng)進入“等待輸入”階段。它有幾個明顯的邊界要接受模型不一定會每次都遵循尤其是被復(fù)雜指令分散注意力時如果同一個會話里同時處理多條指令可能會出現(xiàn)提前標記或漏標記模型輸出這個標記后如果網(wǎng)絡(luò)中斷或程序崩潰需要兜底判斷。所以結(jié)束標記是“主要信號”但不是唯一信號。超時和靜默提醒仍然是必要的兜底。3.2 先開啟 monitor-silence讓“安靜很久”的窗口浮出來tmux 里有一組與“長時間無輸出”相關(guān)的配置雖然不如 activity 常用但更適合 AI 會話場景。# 對指定窗口開啟靜默提醒 tmux setw -t work:0 monitor-silence 30 # 開啟后視覺提醒會顯示在狀態(tài)欄 tmux set -g visual-silence onmonitor-silence 30表示如果這個窗口在 30 秒內(nèi)沒有任何輸出狀態(tài)欄會給一個提醒。它的價值在于提醒你“有個窗口已經(jīng)安靜了”這對發(fā)現(xiàn)“它可能已經(jīng)等你一會兒了”很有幫助。代價是它不知道 AI 是在想你還是在等你。當模型進入長思考階段也會出現(xiàn)持續(xù)沒有輸出的情況所以不建議把monitor-silence的閾值設(shè)得太低否則你會在每個窗口之間頻繁被告知“它不說話了”。它適合當輔助提醒不適合當唯一答案。先跑通這個配置你至少能憑直覺從狀態(tài)欄上看出哪個窗口安靜異常。3.3 從手工切窗升級為一條捕獲命令tmux 提供了一個很關(guān)鍵的能力capture-pane。它可以把某個 pane 當前屏幕的內(nèi)容抓出來以純文本形式交給其他命令處理。# 捕獲 work 會話里 0 號窗口 0 號 pane 的最后 200 行 tmux capture-pane -p -t work:0.0 -S -200 | tail -n 30先手動跑一下看看你能不能看到剛才約定的AI_WAIT標記。如果能說明監(jiān)控鏈路是通的。接下來可以把這個命令和 grep 組合起來做一輪最簡單的檢測tmux capture-pane -p -t work:0.0 -S -200 | grep -q AI_WAIT echo 這個窗口在等你這一步是整套方案中最核心的動作。之后不管是寫循環(huán)、發(fā)通知還是做狀態(tài)面板都是在這個捕獲結(jié)果上做文章。3.4 持續(xù)輪詢并發(fā)送桌面通知一個簡化腳本示例下面的 Python 腳本并不復(fù)雜它做三件事每隔幾秒捕獲指定 pane 的文本檢查是否出現(xiàn)結(jié)束標記如果出現(xiàn)且之前沒有通知過就發(fā)一條桌面通知。#!/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 桌面環(huán)境的常見命令macOS 上需要換成osascript或terminal-notifier這類工具。如果你的環(huán)境沒有桌面通知也可以先把打印信息寫到一個匯總文件或者直接用 tmux 的display-message在狀態(tài)欄里提示。邏輯是一樣的。# 捕獲幾個窗口是否出現(xiàn)等待標記 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跑通之后你會明顯感到一個變化你不再需要把每個窗口都看一遍只要等通知過來再切到對應(yīng)窗口去看它接下來要你做什么。4. 提升可靠性歷史回溯、狀態(tài)去重和卡死兜底最小方案能解決“大部分時候哪個窗口在等我”但它離穩(wěn)定好用還有幾個坑要處理。如果跳過這節(jié)很容易在實際使用中覺得方案還是靠不住。4.1 capture-pane 默認只抓當前屏幕回溯行數(shù)別省tmux capture-pane默認捕獲的是 pane 當前可見的那一屏內(nèi)容。如果某段輸出太長讓標記滾到了可視區(qū)域之外你直接 grep 就會漏掉。因此前面的命令里用了-S -200表示回溯最近的 200 行。這個參數(shù)可以按實際情況加大比如你要處理很長的輸出可以改成-S -1000。實操建議是先用一個小命令驗證一下你捕獲范圍是否覆蓋了標記可能出現(xiàn)的位置tmux capture-pane -p -t work:0.0 | tail -n 3 tmux capture-pane -p -t work:0.0 -S -200 | grep AI_WAIT如果第一條命令里已經(jīng)能看到標記說明還在屏幕內(nèi)否則就要檢查回溯行數(shù)是否夠大以及 pane 目標坐標是否正確。4.2 避免同一個狀態(tài)反復(fù)通知如果腳本每 5 秒檢查一次發(fā)現(xiàn)標記存在就通知一次那個 AI 會話等待你 3 分鐘你就會被提醒幾十次。所以需要給每個 pane 加一個狀態(tài)去重邏輯??梢岳斫鉃闋顟B(tài)從“running”翻轉(zhuǎn)到“waiting”時通知一次進入 waiting 后不再重復(fù)觸發(fā)直到下一次捕獲不到標記才把狀態(tài)重置回 running。上面腳本里的states字典就是這個作用實際使用時也可以改成狀態(tài)文件方便多個腳本共享。這也改變了監(jiān)控的語義。你收到通知的瞬間代表的是一個狀態(tài)翻轉(zhuǎn)事件這個會話剛剛進入等待輸入。而如果它已經(jīng)等了十分鐘你之前已經(jīng)收到過通知那就不需要再被轟炸。4.3 加入卡死判斷但要留足“思考余量”等待標記解決的是正?;睾系慕Y(jié)束但有一種尷尬情況是AI 既沒有輸出結(jié)束標記也沒有繼續(xù)輸出只是長時間停在那里。它可能卡在 API 請求上可能等某個子命令超時也可能只是模型進入了一段很長的靜默推理。不同工具、不同模型靜默行為的差異很大。有些本地模型的思考階段確實可能十幾秒甚至更久沒有任何輸出但這不叫“等你”也不一定是“卡死”。所以卡死判斷必須用寬閾值。一個通用思路是維護每個 host 的“最后有輸出時間”如果超過 N 分鐘沒有輸出并且也沒有出現(xiàn)等待標記才判定為“可能卡死需要人工檢查”。N 的取值要結(jié)合場景不要盲目追求敏感。我一般會把“提醒可能卡死”的閾值放在 3 到 5 分鐘以上太長會漏報警太短會把長思考誤報為卡死。你可以先跑一周觀察自己的工具典型靜默時間再據(jù)此調(diào)整閾值。判斷卡死的核心還是“有輸出時間戳”。每次 capture 到的文本和上一次一樣說明界面沒有變?nèi)绻B續(xù)多次都一樣同時進程還活著那就要懷疑是不是真的卡住了。但要注意如果窗口里恰好有動畫光標或進度條即使內(nèi)容沒變化也可能產(chǎn)生活動事件這種情況靠文本比對會更可靠。5. 再往前走一步與其不斷盯窗口不如把工作流改成任務(wù)閘門到這里我們已經(jīng)能知道“哪個 AI 在等你”。但如果你的工作流本身設(shè)計得不好解決這個問題的價值也會被抵消。比如同時開六個會話即使通知到了你依然會面臨一堆待辦判斷這個會話的結(jié)果要不要采納那個會話是不是改壞了代碼另一個會話的結(jié)果和當前任務(wù)有沒有沖突。這已經(jīng)不是 tmux 能解決的問題而是任務(wù)調(diào)度思路的問題。5.1 先判斷這些任務(wù)適不適合并行多個 AI 會話并行并不總是好事。最典型的反面場景是三個 agent 同時改同一個倉庫。如果它們在同一個代碼副本里寫代碼很容易產(chǎn)生覆蓋、沖突、上下文互相踩踏的問題。你收到的可能不是“哪個在等我”而是“哪個把我的代碼改成了一團亂麻”。真正適合在 tmux 里同時跑的通常是彼此邊界清晰的任務(wù)不同模塊的文檔分析互相獨立的日志排查不同目錄下的測試代碼生成可以用獨立分支或不同目錄隔離的實驗。如果任務(wù)之間需要共享同一個工作目錄那更適合讓它們串行處理或者干脆用任務(wù)隊列而不是讓多個 agent 同時撲上去。多開 AI 的計劃一定要在啟動前先確認任務(wù)彼此獨立。5.2 給每個任務(wù)一個獨立目錄讓 AI 變成“結(jié)果生成器”一個能明顯降低多會話管理成本的做法是為每個 AI 任務(wù)分配一個獨立工作目錄并把任務(wù)的最終輸出寫到固定的結(jié)果文件。work/ ├── job-01-log-analysis/ │ ├── prompt.md │ └── output.md ├── job-02-test-writing/ │ ├── prompt.md │ └── output.md └── job-03-doc-proofreading/每個窗口只負責進入對應(yīng)目錄跑對應(yīng)任務(wù)。結(jié)束標記出現(xiàn)后你只需要切到那個窗口查看結(jié)果文件決定是否接受、修改或重跑。如果結(jié)果符合預(yù)期這個任務(wù)就可以關(guān)閉如果不符合把新的反饋追加到prompt.md再讓 AI 繼續(xù)。這種組織方式的好處是狀態(tài)不只存在于 AI 會話的上下文里也以文件的形式留到了磁盤上。即使 tmux 窗口全部誤刪或者你隔了一天才回來檢查只要任務(wù)目錄還在你就能知道當時讓它做了什么、目前輸出到什么程度。這比靠一串會話記錄更踏實。5.3 真正要管理的不是窗口而是“誰有權(quán)占用你的注意力”當你把多個 AI 會話變成并行任務(wù)后“哪個窗口在等你”這個問題會逐漸演變成“哪個結(jié)果值得我現(xiàn)在投入注意力”。這背后有一個不太容易被意識到的規(guī)律你同時能接收和處理的任務(wù)數(shù)不是無限的。如果你的工作流是讓 5 個 AI 會話同時產(chǎn)出并且都等著你逐個人工確認那你就成了新的瓶頸。結(jié)果不是效率變高而是你被 AI 的通知牽著走。所以與其無限增加窗口不如先穩(wěn)住節(jié)奏。更多實踐里的體面做法是同一時間只保留一兩個需要你交互的 AI 會話另一批可以批量化、不需要頻繁判斷的任務(wù)用腳本加隊列去跑。tmux 在這里的價值不是替你調(diào)度任務(wù)而是提供一個不會丟失會話的穩(wěn)定底座。真正的調(diào)度邏輯仍然需要你用目錄結(jié)構(gòu)、輸出標記和輪詢腳本來搭建。6. 排查鏈路、常見誤判與適用邊界這套監(jiān)控方案并不復(fù)雜但它涉及 tmux、終端輸出、桌面通知、提示詞約定四個環(huán)節(jié)任何一環(huán)出問題都會導(dǎo)致“明明在等你卻沒提醒”或“根本沒在等你卻不斷提醒”。遇到問題不能靠猜按鏈路一層層驗證更高效。6.1 按這個順序排查捕獲、坐標、標記、通知我自己的排查順序通常是這樣的先確認tmux capture-pane -p -t host能正常輸出內(nèi)容再確認host寫對了用tmux list-panes查完整坐標然后確認標記是否在捕獲范圍內(nèi)必要時加大-S的行數(shù)接著檢查標記字符是否完全一致注意大小寫、空格、是否被終端轉(zhuǎn)義影響最后再測試通知命令本身能不能彈出來權(quán)限是否允許桌面通知。如果腳本里用了grep -q還要注意它在沒有匹配時會返回非 0 退出碼。如果 shell 開啟了set -e整個腳本很容易在半路退出看起來就像“沒有發(fā)現(xiàn)問題”實際是腳本已經(jīng)停了。6.2 常見誤判對號入座我在使用中踩過比較典型的幾個坑列出來供你檢查monitor-activity在流式輸出時會高頻觸發(fā)不適合當作“等待我”的信號monitor-silence能發(fā)現(xiàn)安靜但會把長思考和卡死混在一起需要配合超時閾值結(jié)束標記離窗口末尾很遠時只抓當前屏幕的 grep 會漏報模型有時不按提示詞輸出標記需要靠靜默提醒兜底多個 pane 都在等通知時如果狀態(tài)去重沒做好會變成連環(huán)通知轟炸捕獲文本里如果混入大量轉(zhuǎn)義序列會干擾 grep可以先肉眼觀察一段輸出再判斷。解決轉(zhuǎn)義干擾的辦法不是直接寫復(fù)雜過濾器而是先看輸出是什么樣。如果tmux capture-pane -p得到的文本已經(jīng)可讀就不需要額外處理如果看到了一大堆\x1b[開頭的顏色碼說明你的終端工具在輸出 ANSI 控制序列可能需要先清洗或者在客戶端層面關(guān)閉彩色輸出。6.3 這個方法適合誰不適合誰如果工作流主要是終端里的 CLI agent比如