Skip to content

SubagentRunner:子任務獨立上下文

源码版本v4.0.10

職責

SubagentRunner 是 Cline 把「讓 LLM 在自己的上下文視窗裡跑一個子任務」這件事封裝出來的執行器。當主 agent 遇到 subagent 這種工具呼叫 (new SubagentRunner:215),會為每條 prompt 起一個 runner,runner 內部跑一個迷你的 agent loop——拉流、解析 tool_calls、執行、把結果塞回對話——但用的是完全獨立的 conversation 陣列和獨立的 ContextManager,跟主 Task 的訊息歷史互不污染。

它的位置在主 Task 之下、具體工具之上。主 Task 把 prompt 和 TaskConfig 傳進來,runner 藉助 SubagentBuilder 算出該子 agent 能用的工具子集、系統提示、API handler,然在自己的 run 裡迴圈。子 agent 跑完呼叫 attempt_completion,結果字串原樣回給主 agent,主 agent 看到的就是一條普通工具結果。這種設計讓主 Task 可以把「去探索程式碼、讀若干檔案、綜合答案」這種大開銷動作關進一個隔離的視窗裡,不讓它的中間 token 拖累主上下文。

設計動機

  • 獨立上下文視窗:子 agent 用自己的 ClineStorageMessage[] 陣列,主 Task 看不到子 agent 的中間 tool 呼叫 (conversation init:463)。
  • 工具白名單:子 agent 預設唯讀不寫,SUBAGENT_DEFAULT_ALLOWED_TOOLS 只放 file_read / list_files / search / list_code_def / bash / use_skill / attempt_completion (default allowed tools:14)。
  • 強制 attempt_completion:無論使用者設定什麼工具集,ATTEMPT 永遠被加進去 (force ATTEMPT:96),否則子 agent 無法收尾。
  • 按 agent 設定覆蓋模型:AgentConfigLoader 讀單個 agent 的 modelIdSubagentBuilder.applyModelOverride 替換 apiHandler (applyModelOverride:57),讓不同子 agent 用不同模型。
  • 自動壓縮上下文:runner 在每輪請求前檢查上一次請求的 token 數,超閾值就呼叫 compactConversationForContextWindow 先做 file read 最佳化、再走 truncation (shouldCompactBeforeNextRequest:488)。
  • 空回應重試:模型這一輪沒出 tool_use 就當空回應,餵 noToolsUsed 提示再問,超過 MAX_EMPTY_ASSISTANT_RETRIES (3) 次直接失敗 (empty response retry:662)。
  • 並行多 runner 同步取消:一條 subagent 工具呼叫可以帶多條 prompt,多個 runner 並行跑,abort 輪詢 100ms 一次 (abort poll:216),把所有 runner 一起打斷。

關鍵檔案

資料流

runner 啟動時先準備系統提示和初始 user content。初始對話只放兩條:使用者的 prompt + workspace 元資料塊,後者是給 server-side task loop 校驗用的:

typescript
// apps/vscode/src/core/task/tools/subagent/SubagentRunner.ts
const conversation: ClineStorageMessage[] = [
  {
    role: "user",
    content: [
      { type: "text", text: prompt } as ClineTextContentBlock,
      // Server-side task loop checks require workspace metadata to be present in the
      // initial user message of subagent runs.
      ...(workspaceMetadataEnvironmentBlock
        ? [{ type: "text", text: workspaceMetadataEnvironmentBlock } as ClineTextContentBlock]
        : []),
    ],
  },
];

while (true) {
  if (
    usageState.lastRequest &&
    this.shouldCompactBeforeNextRequest(usageState.lastRequest.totalTokens, api, providerInfo.model.id)
  ) {
    const compactResult = this.compactConversationForContextWindow(
      contextManager,
      conversation,
      contextState.conversationHistoryDeletedRange,
    );
    contextState.conversationHistoryDeletedRange = compactResult.conversationHistoryDeletedRange;
    // ...
  }
  // ...
}

這段在 conversation + while:463。進入迴圈後,每輪 createMessageWithInitialChunkRetry 拉流,stream chunk 分類為 usage/text/tool_calls/reasoning。流結束把 finalized tool calls 一個個跑,命中 attempt_completion 的 call 直接把 result 回傳給主 agent (return on attempt:723);其他工具透過 coordinator.getHandler(toolName).execute 執行,結果作為 user content 回灌繼續迴圈。

邊界與失敗

  • 工具不在白名單直接 toolError:子 agent 想呼叫 write_to_file 這種被攔下,錯誤字串塞回 user content (whitelist check:726),迴圈繼續,不打斷整條任務。
  • attempt_completion 缺 result:result 為空時塞 missingToolParameterError 而非收尾 (missing result:705)。
  • 首 chunk context window exceeded 重試:首 chunk 失敗且錯誤是 context window 相關,立即壓縮 conversation 再重試 (context window retry:1036),最多 MAX_INITIAL_STREAM_ATTEMPTS 次。
  • abort 時正在跑的命令:abort 不只是 cancel API 流,還委託 cancelRunningCommandTool 把當前 bash 也停掉 (cancel running command:281)。
  • native vs non-native tool calls fallback:non-native 模式收到結構化 tool_calls chunk 時,仍執行但把結果序列化成純文字塞回,避免 tool_result pairing 錯位 (non-native fallback:631)。
  • stats 累計:每次 chunk 都更新 inputTokens/outputTokens/cacheWrite/cacheRead/totalCost,並透過 onProgress 即時給前端 (stats accumulation:534)。
  • empty assistant 也算一輪:assistant content 完全空時主動塞一句「Failure: I did not provide a response.」再餵 noToolsUsed (empty assistant:671)。

小結

SubagentRunner 讓主 agent 能把「大範圍探索」外包給一個隔離的 mini-agent。想看它依賴的上下文壓縮策略可以讀 context-manager;想看主 Task 如何把工具結果回流並遞迴可以讀 agent-loop/task-class;想看 MCP 工具如何被主 agent 呼叫可以讀 mcp-hub

對照官方資料:Cline 文件 · README