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。