孤立的子智慧體
flowchart TD
c["每 15 秒,每個 session<br/>檢查它啟動的子智慧體"] --> s{"有子智慧體被孤立?"}
s -->|"有"| m["對子智慧體送一則訊息:<br/>送出最終報告後停止"]
m --> r["報告抵達啟動它的 session"]
s -->|"沒有"| n["不送任何東西"]
子智慧體的結果只有在啟動它的 session 自己的行程持有 pi-herdr-agents 的 watcher 與已啟動子層的登記簿(runningSubagents,位於行程全域的槽位 globalThis[Symbol.for("pi-subagents/runtime")])時,才會送達那個 session。/reload、/new、/resume 和 /fork 會保留它們,結束 pi 會抑制送達,崩潰或重新啟動則會永久丟失:pi-herdr-agents 在完全重新啟動後不會恢復 watcher。完成後仍保持開啟的子智慧體(例如互動式的)就此被擱置:它完成後停在自己的 pane 裡,沒有人被告知。
細節
Herdr 中每個本身不是子智慧體的 session,每 15 秒檢查一次這種情況。只要 session 沒有被擱置的子智慧體,一次檢查就只讀登記簿和一個目錄;只有未被追蹤、且自身活動顯示在等待的子智慧體,才會帶出一次 pi-intercom session 清單,之後每次檢查再有一次 herdr pane list(以及在即將詢問它時的一次 herdr pane process-info)。符合以下全部條件時,子智慧體會被要求回報:
- 啟動它的 session 不再追蹤它。 它的 id 不在那個登記簿裡。登記簿讀不到時(pi-herdr-agents 沒有載入,或不再把它存在那裡),無法確定有東西遺失,所以什麼都不送。
- 它的結果不在啟動者的逐字稿裡。 沒有任何
subagent_result或subagent_ping訊息指名該子層的 session 檔。 - 它已經完成。 它的活動檔顯示
waiting(它結束了自己的回合),它的 pane 執行 pi 且 Herdr 顯示done(或idle,pane 在有人看過之後會變成這個),而且 pi-intercom 顯示該 pane 中的 session 為idle。仍在工作、等待問題回覆或尚未開始的子層不會被動。 - 連續兩次都這樣判定, 間隔 15 秒,以免把剛好正要送出的結果當成遺失。
- 它的 pane 執行的就是這個子層本身。 該 pane 的前景行程(
herdr pane process-info)包含這個 session 為該次執行寫下的啟動腳本artifacts/<session id>/subagent-scripts/<name>-<run id>.sh。只是重用該次執行 session 檔的 pane,例如另一個 session 接手的執行,或有人在該檔上自己啟動的 pi,都不是這個子層;Herdr 沒有列出其行程的 pane 也不是。
Straw Boss 接著向該 session 送一則 intercom 訊息:啟動者的 session id 與名稱,以及請它透過 intercom 把最終報告送給那個 session 並停止的要求。啟動者會顯示一則通知。每次執行最多送一則訊息,這裡的一次執行指一個子層 id:它的活動檔以此命名,而子層自己的 /reload 會重新啟動該檔,所以檔案的建立時間不能說明任何事。這次執行在訊息送出之前就寫入啟動者的 session(一筆帶有 nudged 鍵的 straw-boss 項目),所以重新啟動不會重複送,送出失敗則顯示為警告,不再重試。
找不到 pane、pane 沒有執行其啟動腳本,或沒有自己的 pi-intercom session 的子層,看過四次後就放棄,直到下一次 session 啟動(reload 也算)。行程已結束的子智慧體無法被詢問;它的結果留在它的 session 檔裡。被接手的執行(subagent_resume)有自己的 id,而它的 pane session 檔裡沒有任何東西指名這個 id,所以它永遠找不到,也永遠不會被詢問。
這是子智慧體自己的啟動者做的事,不是塔台:只有啟動者的行程知道它追蹤了什麼,所以塔台絕不會催促其他 session 的子智慧體,而自己啟動了子智慧體的子智慧體也不會這樣做(它不執行 Straw Boss)。
Herdr 還原後變成一般 pi 的子智慧體
Section titled “Herdr 還原後變成一般 pi 的子智慧體”還原的情況,一張圖看完:
sequenceDiagram
participant H as Herdr
participant P as 還原的 pane
participant S as Straw Boss
participant L as 啟動者的 session
H->>P: 重啟在每個有智慧體的 pane 啟動一般的 pi
S->>P: 找到它(每 15 秒檢查,連續兩次)
S->>L: 寫入 closing 鍵,關掉 pane
S->>L: 一則帶著後續動作的訊息
L->>L: subagent_resume(pi-herdr-agents)
上面的催促要求子層仍然是 pi-herdr-agents 當初啟動的樣子。Herdr 重新啟動後就不是了:在 resume_agents_on_restore(預設開啟)下,Herdr 會在每個曾經回報過智慧體 session 的 pane 裡啟動一個一般的 pi --session <file>,包括子智慧體的 pane。那個 pi 沒有子層的任何設定(沒有 subagent-done.ts 擴充功能、沒有 PI_SUBAGENT_* 環境變數、沒有 caller_ping、沒有自動結束、沒有 <session>.exit sidecar),所以它停在提示字元上閒置,任務沒做完,也永遠不會回報,而還原後啟動者的登記簿是空的,之後也不會再被填入。沒有任何東西能讓它自己回報,所以啟動者改為接手那次執行,方式是透過 pi-herdr-agents 的公開工具 subagent_resume。
細節
同樣每 15 秒的檢查(restored.ts)在以下全部條件成立時找出這種執行。它只在還有執行可能被記下或找到時才列出 Herdr 的 pane:永遠不可能符合的執行(常駐專家,或啟動腳本不存在或沒有指名該次執行自己的 session),會在那之前就被剔除,所以不會花一次清單或一次行程查看。
- 啟動者沒有追蹤它,而且它的結果不在逐字稿裡。 與催促時相同:它的 id 不在登記簿裡,也沒有
subagent_result或subagent_ping指名該子層的 session 檔。它不是常駐專家(啟動腳本中有PI_SUBAGENT_PERSISTENT=1),pi-herdr-agents 無法接手常駐專家。 - 它從未結束。 它的 session 檔旁沒有
<session>.exit,而且 Straw Boss 先前沒有對它採取過行動(straw-bosssession 項目中沒有它的resumed或nudged鍵)。 - 它的 pane 是被還原的那個。 啟動腳本的標頭(
# Session:與# Surface:)指名子層的 session 檔與它執行所在的 pane,而還原會保留 pane id。Herdr 列出同一個 pane,智慧體為pi、session 檔相同,狀態為idle或done。 - 確實發生過還原。 子層還在以 pi-herdr-agents 當初啟動的方式執行時(它的 pane 前景執行啟動腳本),啟動者會把該 pane 的
terminal_id記在自己的 session 裡(一筆帶有seen與terminal的straw-boss項目)。Herdr 會給還原的 pane 一個新的 terminal,而 pane id 不變,所以只有 terminal 與記下的不同時,這個 pane 才算數。有人在同一個 pane(同一個 terminal)手動啟動的 pi、從未被看見在執行的執行(重新啟動發生在第一次查看之前),以及不回報terminal_id的 Herdr,全都是未知,而未知絕不會被關閉。 - 那裡沒有別的東西在執行。
herdr pane process-info剛好列出一個 argv0 為pi的前景行程,而其他每個行程都有可讀的 argv,且不含啟動或接手腳本(含/或\的路徑)。仍在執行啟動腳本的 pane 屬於催促的情況;另一個 session 接手的執行位於自己的 pane,執行的是接手腳本;這些都不會被動。在 Windows 上,清單無法證明腳本不存在,所以那裡的 pane 絕不會被當成已還原。 - 啟動者本身不是子智慧體,而且這次執行是一般的執行。 檔案旁有
.pi-herdr-subagent-policy.json的 session(還原後的巢狀子智慧體)什麼都不做。由子層自己的 policy sidecar 決定:persistent的執行不動,owner 不是public的執行按 worktree 執行處理。 - 連續兩次都這樣判定, 間隔 15 秒,因為 pane 在重新啟動後可能還在啟動中。沒有這種 pane 的執行,看過八次後就放棄,直到下一次 session 啟動。
接著依序進行:把該次執行的 closing 鍵寫入啟動者的 session,用 herdr pane close 關閉還原的 pane(而且只關那個 pane;舊的 pi 不能和接手的那個同時持有 session 檔),Straw Boss 最多等五秒讓它消失,然後啟動者收到一則自訂訊息;session 接收之後,寫入 resumed 鍵。在關閉與訊息之間 reload 或結束,會留下沒有 resumed 的 closing:啟動者的下一個實例會找到這次執行,看到它的 pane 已不在,就在那時送出訊息。
如果 pane 仍然開著,表示關閉從未生效(行程先死了):closing 鍵會被收回(session 中的 unclosed 鍵,讓 reload 不會再讀它),這次執行從頭再經過一次還原的證明。之後不再符合資格的執行(沒有還原的 pane、同一個 terminal、不是一般 pi)會和其他任何情況一樣算作一次落空,第八次落空而放棄時,會有一則警告指名它的 session 路徑。這則訊息以 sessionPath 和名稱指名每次執行,引述要傳遞的後續指示(完成你拿到的任務,並回報結果),並告訴模型對每次執行呼叫一次 subagent_resume(以 PI_SUBAGENT_AUTO_EXIT=0 啟動的執行則設 autoExit 為 false);接手後的執行隨後以一般的 subagent_result 回報。
它是會觸發回合的 steering 訊息,就像 pi-herdr-agents 自己送達 subagent_result 的方式,也像塔台 run被驅動的方式:閒置的啟動者立即醒來,忙碌的啟動者在下一步收到它。Straw Boss 不呼叫該工具、不匯入 pi-herdr-agents,也不替子層寫結果:這次執行是否、如何被接手,仍由 pi-herdr-agents 和模型決定。
身為塔台的啟動者完全不做這些(塔台由伺服器的佇列驅動,而不是由這類訊息)。每個失敗的步驟都顯示為警告,記為 gaveUp,不再重試:關不掉(或關了仍開著)的 pane 不會被接手,放不進 session 的訊息則連同它本來要指名的 session 一起顯示,方便手動接手。
managed-worktree 執行(在 artifacts/<session id>/worktree-runs/ 下有一份 manifest)是例外。它的 pane 是保留下來的 worktree 工作區的根 pane,所以 Straw Boss 不關它,而 pi-herdr-agents 的 subagent_resume 既不會重新接上 worktree 的生命週期,也不接受這樣的 session。訊息會說明這點,指名 pane、worktree 與分支,告訴模型不要對它呼叫 subagent_resume,並讓還原的 pi 停在提示字元等使用者繼續(回合中途被切斷的執行,會被告知繼續,見下文)。
回合中途被切斷的子層,會被告知繼續
Section titled “回合中途被切斷的子層,會被告知繼續”重新啟動或重新開機可能正好發生在子層回合的中途,還原的 pi 於是停在提示字元,沒有事可做:被打斷的工具呼叫沒有結果,也沒有人接續該回合。在一次執行交給啟動者之前,cut.ts 會讀子層 session 檔的結尾(它的最後一段,會增長到包含最後一則助理訊息;大檔案不會整個讀)。只有 message 項目,以及啟動回合的自訂訊息(intercom 訊息、巢狀啟動者的結果、Monitor 的喚醒、second-thought 或文件審閱通知、robot-hand 的後續)才算數,所以自訂項目、模型變更或任何其他通知都說明不了什麼。
細節
符合以下任一情況,session 即為被切斷:最後一則助理訊息的最後幾個工具呼叫沒有結果,或最後一步是模型從未讀過的工具結果,或最後一步是沒有人回答的訊息,或最後一則助理訊息是重新啟動會留下的錯誤:claude-bridge 的 Claude Code process terminated by signal SIGHUP 或 exited with code 129(SIGTERM 或代碼 143 可能是手動砍掉的,沒有任何東西顯示重新啟動發生在它之後,所以不算被切斷)(回覆在寫到一半時被切斷,不論是否有已開始串流的工具呼叫)。
以下情況不算被切斷:最後一則助理訊息結束了它的回合(stop;abort,也就是老闆自己按的 Esc;任何其他錯誤,例如配額或被取代的對話)、回合停在一個問題上(ask_user_question、caller_ping、intercom 的 ask:子層和重新啟動前一樣在等答案)、檔案讀不到,或檔案沒有任何步驟。只有單純的工具名稱(字母、數字、_、.、:、-,最長 64 字)會被引述進文字,其他的稱為「a tool」,時間必須是 ISO 時間。
子層只會被告知一次,在一則訊息裡:它的最後一個回合被 Herdr 重新啟動或重新開機切斷(附上它最後一筆記錄步驟的時間),該工具呼叫(以名稱)沒有得到結果,在重新執行任何會改變狀態的東西之前,應先檢查目前的狀態,然後繼續任務並回報。怎麼送達則視這次執行而定:
-
被接手的執行(pane 已被關閉)把它當作後續指示收到。啟動者的訊息把這次執行列為在回合中途還原的,附上那段文字,並告訴模型把這一段而不是通用的後續指示傳給
subagent_resume。這次執行只會被接手一次,所以只會被告知一次。 -
managed-worktree 執行保留它還原的 pane,所以 Straw Boss 自己透過 intercom 送出文字(與上面回報要求相同的送法),對象是 Herdr 位置為目前且位於該次執行 pane 中的 broker session(位置不可用時,會保留它註冊時的 pane id,所以不算)。還原的 worktree pi 不會自己向任何人回報,所以文字中也指名啟動者(它的名稱與 intercom session id),並像上面的回報要求一樣,請它把最終報告送到那裡。它絕不會被打進 pane,所以老闆在提示字元裡有的東西會保留。
只有在該 session 自己的 intercom 狀態為
idle(且 pane 在查看時為 idle 或 done)時才送出,而且在送出前會再讀一次逐字稿:已有人讓它繼續的子層不會被告知。尚無法連上的 session(pi-intercom 在 pi 啟動後一會兒才連線)會在接下來的查看中再看,最多四次;之後告知啟動者無法連上它。continued在送出之前就寫入啟動者的 session,所以每次還原最多告知一個子層一次,即使跨過 reload 或送出失敗也一樣。 -
兩種情況下,啟動者收到的那一則訊息都會說明哪些執行是在回合中途還原的,對 worktree 執行還會說明子層是否已被告知(「not idle, wait for its report over intercom」;此時訊息不會讓啟動者為這次執行去那個 pane)或沒有(原因,並請在它的 pane 裡讓它繼續)。
限制:老闆正在輸入的草稿
Section titled “限制:老闆正在輸入的草稿”Straw Boss 看不到另一個 pi 的提示編輯器,而 intercom 訊息在狀態為 idle 時就會立即啟動子層的回合。如果老闆在還原後的第一分鐘內在還原的 pane 裡有草稿,草稿會保留(不會打任何東西進編輯器),但子層可能在老闆送出之前就先行動。文字會告訴子層先檢查狀態。子層這邊的防護(它自己的 Straw Boss 擴充功能在編輯器清空前先扣住訊息)需要兩者之間專屬的通道、一個確認,以及對擴充功能版本較舊的子層的備援,所以不屬於這個版本。
細節
未涵蓋:本身已被接手(subagent_resume)的執行,不會在第二次重新啟動後被復原。它的接手腳本命名為 <name>-resume-<time>.sh,標頭(# Session:、# Surface:)確實給出 session 檔與 pane,但沒有該次執行的 id(它只在腳本的指令中,作為 PI_SUBAGENT_ID),而這裡其他每項證明都繫於這個 id。被接手的執行重用原本的 session 檔,檔名帶的是原本的 id,所以 pane 和逐字稿都無法與新 id 對應(ownFile),而原本那次執行的 subagent_result 指名同一個 session 檔,所以逐字稿也說不出被接手的執行是否完成。
要涵蓋它,就得憑猜測關閉 pane,所以接手之後的重新啟動維持未知,什麼都不關;而留下未被取用的 .exit sidecar,表示子層已完成,所以這次執行不動,它的結果必須從 session 檔讀取。