跳到內容

代理派送生命週期

一次派送就是一個代理、在一個對話中、透過一個配接器的一次執行。與它有關的一切都發生在 cc_server 上:用戶端送出訊息,然後看著事件串流回來。任何用戶端裡都沒有派送路徑。

代理會基於六個原因被喚醒,執行會把這些原因作為喚醒情境帶著,好讓代理知道自己_為什麼_醒著:

喚醒原因 觸發條件
userMessage 有人在空間中送出訊息,或提及了該代理
assignment 一張工單被指派給該代理
pipelineStep 某個管線步驟派送了它
childCompleted 一張子工單完成了
followUp 一次排定的後續心跳
recovery 恢復路徑在失敗後重新派送

喚醒情境會攜帶與該原因相配的各種 id——工單、空間、訊息、管線執行——它們會被注入代理的環境與提示中。

提示是分層建構的,每一層都是一個帶標籤的區段:

  • 身分——代理自己的 id、名稱與工作區 id,並明確指示要把這些確切的 UUID 傳給以工作區為範圍的工具。工作區是代理固有的,因此永遠存在。
  • 工具目錄與資源協定,接著是工作區配置。
  • 系統提示覆寫(若代理有設定),接著是角色設定(依角色參數化)與團隊簡報。
  • 技能——掛在代理上的名稱。
  • 目前模式的執行契約與程序。
  • 喚醒情境與提及情境:誰召喚了該代理,以及它周遭的名冊。
  • 模式區塊——由模式的能力設定檔_及其具體化後的工具清單_產生,因此提示永遠不可能寫出這次執行實際上沒有的工具。
  • 記憶情境——為任務檢索到的事實。
  • 對話情境——最近的空間歷史,無論代理設定的情境大小為何,逐字視窗一律上限為 50,000 個字元。較舊的歷史不會被截掉:代理會用 get_messages 視需要擷取,語意相關的較舊訊息仍會浮現。

命令分散在兩個地方處理。/todo 與 /compact 在輸入區遭到攔截,並且刻意永不持久化為訊息,也不會送給代理——持久化 /compact 會把壓縮切點往回推一個回合,並污染逐字稿。/plan 兩處都會處理:輸入區翻轉空間的模式,這正是讓工具介面、護欄預設集與提示在本回合以及之後每一回合都保持一致的原因。/goal、/loop 與 /skill:<name> 只在執行內部處理,而且只適用於內建執行環境——/goal 與 /loop 會以費用預算啟動自主工作,而以 skill: 為前綴的名稱則會以技能查詢,其 SKILL.md 本文會成為該回合的指令。命名空間正是把這兩套詞彙區隔開來的東西:拿裸名稱去解析技能,意味著名為 plan 或 compact 的技能會被內建命令永久遮蔽。對於命名空間出現之前寫下的訊息,裸的非內建名稱仍可解析。無法辨識的名稱則直接作為純文字處理。

在任何東西執行之前,對話會取得自己的工作根目錄,其下每個儲存庫都有一個隔離的寫入時複製工作樹。預設情況下,那就是連結到工作區的每個儲存庫;一個 PR 空間——或以明確選取範圍建立的空間——只會佈建允許清單中的儲存庫,而 PR 審查對話會把它那一個儲存庫釘在該 pull request 的 head ref,其餘則停在各自的預設分支上。來源簽出永遠不會被更動,空間遭刪除時整個對話目錄都會被回收。

在 rift 隨附供應的所有地方,寫入時複製都是唯一的實作,並由隨附的原生元件支援。無法 reflink 的檔案系統——或資料目錄與儲存庫位於不同磁碟區——會讓佈建失敗,而不是退回。git worktree 是 Windows 上的後端,因為那裡沒有可建置的寫入時複製後端,而且在其他主機上它並不是無聲的降級。在隨附供應它的平台上缺少 rift 就是安裝損壞——伺服器會拒絕啟動,而不是悄悄退回,因為無聲的退回會把這種損壞永遠藏在較慢的路徑背後。

每次執行都會在任何行程啟動之前解析兩種身分。

Git 作者身分。 代理所做的提交是以_代理的身分_署名的——在 GIT_AUTHOR_* 與 GIT_COMMITTER_* 中都是 <Name> (agent) <agentId@agents.control-center.local>——絕不冒充人類。local-part 是代理的 UUID,而不是它的名稱 slug。提出請求的人類會以一條 Co-Authored-By: 尾註記名,該尾註會放在環境中,並在系統提示中交代。呼叫方明確設定的身分一律勝過這兩者。

程式碼託管身分。 執行的 GitHub 憑證永遠只會是憑證仲介的——一個以儲存庫為範圍的 App 安裝權杖,或它的環境 PAT 退回——絕不是提出請求成員自己儲存的權杖。actingUserId 會限制 PAT 退回的範圍,這樣不是伺服器擁有者的成員就不會繼承伺服器的觸及範圍;它不會把個人權杖疊加到代理環境上。

模型供應商憑證會依固定順序解析,而這個順序很重要:

  1. 依配接器的環境覆寫,
  2. 呼叫方的環境,
  3. 伺服器的憑證存放區(若 OAuth 權杖接近到期便重新整理),
  4. 行程環境。

因為依配接器的覆寫排在最前面,它會悄悄勝過在應用程式中連接的供應商帳號。如果某次執行記帳到錯誤的帳號,那個覆寫就是第一個該查看的地方。供應商憑證是主機全域的——在整個安裝的所有工作區之間共用。

這次執行的 GitHub 權杖來自憑證仲介:一個限定於工作樹儲存庫的唯讀權杖。推送會經過代理執行閘道,閘道在轉送前會詢問推送到遠端規則,而且不會注入任何工單金鑰。請參見沙箱與資安。

執行會被啟動,並以其工作樹作為工作目錄,而且——對 Claude Code 而言——帶上一份產生的 MCP 設定,讓代理能觸及 Control Center 的工具介面。ACP 與執行環境的 bash 會取得清理過的環境;透過 NoSandboxAdapter 的 Claude Code 則繼承主機環境。

執行會串流回一個具型別的事件序列,串流處理器會將它持久化為訊息增量、摺疊成逐字稿區段,並轉換為訊息事件:

事件 承載內容
thinking 推理輸出,內嵌渲染
text 可見輸出
toolCall 一次工具呼叫,帶有它的 id 與結構化引數
toolResult 對應的結果;在出錯或屬於部分串流時會被標記
usage 某個計量視窗內的 Token 數與費用
error 一個錯誤;配接器回報了結構化錯誤碼時也會帶上
sandboxViolation OS 拒絕了一次系統呼叫。只會在沙箱實際包住行程的地方發出——請見上方說明
debug 執行階段診斷資訊(啟動標記、護欄決定、契約提醒)
done 執行結束

任何地方都刻意沒有回合上限。硬性的迭代次數會殺掉合法的長時間工作(移轉、扇出清理),卻抓不到底下這些防護更早就能抓到的東西。執行會在下列情況結束:

  • 模型自行停止;
  • 人類停止它,或接手對話(這會讓內建執行環境在下一個乾淨的回合邊界暫停——而沒有安全邊界的 Claude Code 執行則會被直接停止);
  • 靜默看門狗觸發。 超過閾值仍無輸出,工作階段就會以 silentRun 失敗終止。預設是 15 分鐘,在偏重讀取的模式(review 與 plan)下縮短為 10 分鐘;代理可在 1 到 240 分鐘之間覆寫。看門狗會在子行程回報自己的 PID 時武裝,因此涵蓋 Claude Code CLI——不涵蓋內建執行環境,後者不會生成任何子行程;
  • 費用上限生效——但只限自主命令。

死迴圈防護不是終止器:連續三次相同的工具呼叫,迴圈會_引導_代理改變做法。

綜合起來,在內建執行環境上的一般互動回合沒有回合上限、沒有費用上限,也沒有靜默看門狗。它會在模型停止或你停止它時結束。這是刻意的取捨——其他替代做法都會殺掉合法的長時間工作——但也確實意味著,無人看管的互動執行除了模型自身的判斷之外,不受任何限制。

執行途中送到執行中代理的訊息會以引導排入佇列,並在下一個回合邊界排空,因此你可以在不殺掉執行的情況下推動它。引導、暫停、子代理與完成契約都是內建執行環境的功能;Claude Code 一項都沒有。

每次派送都會寫入一筆 AgentRunLog:開始與結束時間、摘要、Token 數與預估費用(包括從子代理彙總上來的費用)、配接器與模型、行程 id 與日誌路徑、所屬的對話以及任何工單或管線執行,外加兩個承載大部分真相的分類欄位。

  • 狀態是粗粒度的:pending、running、completed、error。
  • 存活狀態是更細的判讀:alive、productive、completed、blocked、empty、looping、failed、stalled、dead。
  • 錯誤類別會把失敗有效地分組:transientUpstream、sandboxInfrastructure、budgetExceeded、processLost、silentRun、unknown。

關閉該次執行會發布 AgentRunCompleted,而許多東西都掛在它上面:管線費用彙總、恢復一個正在等待該次執行的管線步驟、完成背後的工單、把一次編排對應到終態、目標監督者的繼續或停止決定、檢查者角色,以及執行後的預算評估。線上狀態名冊則另行更新,方式是觀察最近的執行日誌,而不是該事件。「代理已完成」通知不在這份清單上:該事件在行程串流結束時觸發,此時回覆尚未寫入,因此這則通知其實是該回合的 MessageReceived,會在訊息持久化之後才發布。

預算強制執行是一種_反應_,而不是一道關卡。當一次執行完成時,評估器會把代理本月至今的支出與其有效上限比較(若有連結的預算原則就用它,否則用代理自己的每月數字;零代表不限)。越過軟性閾值會記錄一筆警告事故;用盡預算會記錄一筆硬性事故,並把代理在組織圖上的生命週期狀態翻轉為 paused。派送仍會接受來自該代理的工作。一個 BudgetThresholdCrossed 管線觸發器可以從同一個事件啟動後續工作。

每筆執行日誌都可以帶有重試譜系——一個父執行 id、一個嘗試計數器,以及一份續接摘要。對話的執行樹會在嘗試編號非零時把它渲染出來。從聊天中的「再試一次」是一次新的派送,帶有一筆全新的執行日誌。