SubagentRunner:子任務獨立上下文
職責
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 的modelId,SubagentBuilder.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 一起打斷。
關鍵檔案
SubagentRunner class:254— 持有 agent、apiHandler、allowedTools、abort 狀態。run method:330— 主迴圈,每輪拉流、解析 tool_calls、執行、回流。while loop:485— 永真迴圈,靠 attempt_completion 或 abort 退出。createMessageWithInitialChunkRetry:519— 拉流並在首 chunk 報「context window exceeded」時壓縮後重試。tool whitelist check:726— 不在 allowedTools 裡就回傳 toolError,不執行。attempt_completion handling:702— 命中 attempt 就把 result 字串回傳給主 agent 並退出。abort method:272— 呼叫 api.abort、取消正在跑的命令。compactConversationForContextWindow:895— 先試 file read 最佳化,再走getNextTruncationRange刪一段。shouldCompactBeforeNextRequest:977—useAutoCondense+ next-gen 模型走 0.75 閾值,否則按maxAllowedSize。buildSystemPrompt:73— 拼成<generated> + <agent identity> + SUBAGENT_SYSTEM_SUFFIX。spawn runners:215— 主 agent 真正起 SubagentRunner 的地方。
資料流
runner 啟動時先準備系統提示和初始 user content。初始對話只放兩條:使用者的 prompt + workspace 元資料塊,後者是給 server-side task loop 校驗用的:
// 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。