SubAgent 架构与设计思想解读
面向工程师与架构师的深度解析
本文从”为什么需要子代理(SubAgent)””它如何运行””哪些设计保障了可靠性与可扩展性”三个层面,通俗但尽量完整地解读 packages/core/src/core/subagent.ts 的架构与设计哲学。
我们为什么需要 SubAgent?
                    check_circle
                    
                
                        职责分离与自治执行:将一个复杂目标拆解成可自治运行的子任务,每个子任务由一个”子代理”负责,在明确的预算与工具权限下独立推进。
                    
                
                    check_circle
                    
                
                        非交互式工作流:很多自动化场景无法依赖人工问答。SubAgent 被设计为”非交互模式”,能在没有用户输入的情况下完成工作、调用工具并达成目标。
                    
                
                    check_circle
                    
                
                        结构化出参与可编排性:通过内置的 
                self.emitvalue 工具,SubAgent 能以结构化方式”发射”结果变量,便于上层编排收集和传递。
                    
                    check_circle
                    
            
                        安全与确定性:在工具调用前进行”需确认”的预检,执行过程中有”时间/回合数”双重预算,避免无限循环或交互卡死。
                    
                架构总览:关键构件与数据流
                SubAgent 架构组件示意图
关键构件
- 终止原因枚举 
SubagentTerminateMode:ERROR | TIMEOUT | GOAL | MAX_TURNS,准确表达为什么停止。 - 输出对象 
OutputObject:emitted_vars存储通过self.emitvalue发射的变量;terminate_reason标示终止原因。 - 提示与模型配置:
                        
PromptConfig:二选一提供systemPrompt或initialMessages(few-shot 历史)。ModelConfig:model、temp、top_p控制生成行为。RunConfig:max_time_minutes与可选max_turns控制预算。ToolConfig:允许以”注册名”或直接给出FunctionDeclaration暴露工具。OutputConfig:声明期望输出变量的键与语义描述。
 - 运行态上下文 
ContextState:一个简单的键值容器,用来为系统提示模板${key}替换提供值;在替换前会强校验缺失键。 - 模板函数 
templateString:校验并替换systemPrompt内${...}占位符,缺失即抛错,保障提示完整性与确定性。 - 作用域类 
SubAgentScope:SubAgent 的核心执行体,负责:- 工具预检查与实例化(
create) - 构建聊天对象(
createChatObject) - 主事件循环(
runNonInteractive) - 工具调用执行与结果回灌(
processFunctionCalls) - 构建系统提示与本地工具注入(
buildChatSystemPrompt/getScopeLocalFuncDefs) 
 - 工具预检查与实例化(
 
数据流(简化)
- 创建 Scope →
 - 构建 Chat(环境上下文 + systemPrompt/initialMessages)→
 - 进入循环(发送消息 → 接收函数调用 → 执行工具 → 结果作为”用户消息”回灌)→
 - 若未满足输出约束则”提醒”继续 →
 - 满足目标或达到预算而终止,回填 
OutputObject。 
生命周期与关键步骤
1) 创建与工具预检:SubAgentScope.create
                加载 ToolRegistry,对配置里以”名称”声明的工具做”非交互可用性”预检:
- 若工具无必填参数,则尝试 
shouldConfirmExecute;若需要用户确认,直接抛错禁止在非交互子代理中使用。 - 若工具存在必填参数,无法构造通用的空入参校验,默认”假定安全”(保守权衡:避免创建时崩溃优先于运行时潜在挂起)。
 
lightbulb设计理念
                    在创建阶段尽早发现不可用工具,避免运行期才暴露”需要用户确认”的卡死问题。
2) 构建对话体:createChatObject
                - 注入环境上下文(
getEnvironmentContext)形成start_history,帮助模型”落地”当前会话的系统状态。 - 若使用 
systemPrompt:将ContextState注入做模板替换;否则使用initialMessages做 few-shot 引导。 - 组装 
GenerateContentConfig(温度、topP、可选systemInstruction),并通过createContentGenerator与GeminiChat封装出聊天对象。 - 将选定模型名写入 
runtimeContext(便于统一上报/可观测)。 
lightbulb设计理念
                    在”提示治理”层面将环境与系统约束前置,减少”工具-模型”不一致与幻觉。
3) 非交互主循环:runNonInteractive
                初始消息为一个轻量”点火”指令(Get Started!)。每轮:
- 检查预算:
max_turns/max_time_minutes,命中则以MAX_TURNS/TIMEOUT终止。 - 发送消息(附 
tools与abortSignal),流式接收模型产生的functionCalls。 - 若存在函数调用 → 进入”工具执行阶段”。
 - 若没有函数调用:
                        
- 如未声明输出要求,直接以 
GOAL终止。 - 如声明了输出要求:
                                
- 若已全部 
emit,以GOAL终止; - 否则给出”nudge”提示,要求调用 
self.emitvalue发射缺失变量,继续下一轮。 
 - 若已全部 
 
 - 如未声明输出要求,直接以 
 
lightbulb设计理念
                    让模型”自监督”完成工具调用与收尾动作;当不再调用工具但未满足”结构化出参”时,通过 nudge 将其拉回正轨。
4) 工具执行与结果回灌:processFunctionCalls
                对每个 FunctionCall:
- 若为作用域内工具 
self.emitvalue:将emit_variable_name/value记录进output.emitted_vars,并构造成功响应。 - 否则通过 
executeToolCall调用注册工具,携带AbortSignal;收集工具返回的responseParts。 
若所有工具调用均失败,会向模型回灌一条”全部失败,请分析错误并换一种方法”的文本,促使其调整策略。
将工具响应作为”用户消息”放回对话,以闭环”观察-行动-反馈”。
lightbulb设计理念
                    严格区分”本地作用域工具”(出参)与”外部工具”(环境交互);失败要能被模型”感知并重试”。
系统提示治理与可观测性
buildChatSystemPrompt 会在 systemPrompt 基础上追加两类关键指令:
- 输出约束:逐条告知如何用 
self.emitvalue发射每个期望变量。 - 非交互规则:明确”不得向用户提问””完成目标后停止调用工具”。
 
promptId 由 sessionId#subagentId#turn 组成,贯穿工具调用链路,便于追踪与诊断。
可扩展性与二次开发建议
新增工具
- 若已经在 
ToolRegistry注册,可在ToolConfig.tools中以字符串引用; - 若是临时/作用域内工具,可直接提供 
FunctionDeclaration。 
输出协议
- 在 
OutputConfig.outputs中声明期望 key 及其语义描述,系统提示会自动追加”如何发射”的指引; - 模型完成主任务后,应逐一调用 
self.emitvalue发射所有 key。 
提示模板化
- 使用 
ContextState.set(key, value)注入上下文变量; - 确保 
systemPrompt中的${key}均有对应值,否则会在创建 Chat 前被强校验拦截(fail fast)。 
预算治理
- 先以较保守的 
max_time_minutes与max_turns启动; - 结合日志与 
terminate_reason调整参数,达到”既不超时也不早停”的平衡。 
与 GeminiChat 的协作关系
SubAgent 自身不直接与模型交互,而是经由 GeminiChat 与 ContentGenerator 层对接 @google/genai:
- 从工程上看,这一分层让 SubAgent 聚焦”编排(prompt/工具/预算/收尾)”,而将”对话与生成”的复杂度下沉到更通用的封装中。
 - 这也为未来的”模型路由/回退/观测”提供了稳定扩展点,例如引入”自动模型选择”或”token 预算”只需扩展 
ModelConfig与ContentGenerator。 
一个最小可用示例(TypeScript 伪代码)
import { SubAgentScope, ContextState } from 'packages/core/src/core/subagent';
import { Config } from 'packages/core/src/config/config';
// 1) 准备运行时上下文(含工具注册、会话信息等)
const runtimeContext: Config = /* 你的实现 */;
// 2) 声明各类配置
const promptConfig = {
  systemPrompt: `你是一个文档提取子代理。目标是从 ${'${doc_path}'} 中提取标题和作者。`,
};
const modelConfig = { model: 'gemini-2.5-pro', temp: 0.2, top_p: 0.9 };
const runConfig = { max_time_minutes: 2, max_turns: 8 };
const toolConfig = { tools: ['read_file', 'grep'] }; // 假设已在 ToolRegistry 中注册
const outputConfig = { outputs: { title: '文档标题', author: '作者姓名' } };
// 3) 构建上下文(用于 systemPrompt 模板化)
const ctx = new ContextState();
ctx.set('doc_path', '/abs/path/to/paper.md');
// 4) 创建并运行子代理
const scope = await SubAgentScope.create(
  'doc-extractor',
  runtimeContext,
  promptConfig,
  modelConfig,
  runConfig,
  toolConfig,
  outputConfig,
);
await scope.runNonInteractive(ctx);
// 5) 收集结构化出参
console.log(scope.output.emitted_vars, scope.output.terminate_reason);
            要点:
systemPrompt与initialMessages不能同时提供;- 所有 
${...}占位都必须在ContextState中有值; - 若未满足 
outputs的发射要求,SubAgent 会通过 nudge 提醒模型调用self.emitvalue; - 若某工具需要用户确认,将在 
create阶段直接阻止使用。 
易错点与最佳实践清单
                    warning
                    
                
                        二选一提示源:
                systemPrompt 与 initialMessages 不可同时设置。
                    
                    warning
                    
                
                        模板强校验:
                systemPrompt 中的 ${key} 缺失会立即抛错,尽早发现配置问题。
                    
                    warning
                    
                
                        工具交互性:避免在非交互子代理中配置”需要确认”的工具;对有必填参数的工具要确保在调用时提供正确入参。
                    
                
                    warning
                    
                
                        结果要”发射”:仅在所有 
                outputs 都通过 self.emitvalue 发射后,才会以 GOAL 结束;否则会被反复提醒。
                    
                    warning
                    
            
                        预算与幂等:设置合理的 
                max_time_minutes/max_turns;工具实现保持幂等,便于模型在失败后重试。
                    设计哲学小结
                    stars
                    
                
                        可编排的自治体:SubAgent 将”执行逻辑”与”对话生成”解耦,使其可以被上层编排自由组合。
                    
                
                    stars
                    
                
                        以提示为界面:通过模板化的 system prompt 与环境上下文,将”约束/目标/接口(outputs)”显式化为”模型契约”。
                    
                
                    stars
                    
                
                        工具即能力单元:统一用 
                FunctionDeclaration/注册名暴露,可预检、可拦截、可观测。
                    
                    stars
                    
                
                        稳健为先:Fail fast(模板缺失即抛错)、双预算限制、错误回灌与 nudge,尽可能减少挂死与漂移。
                    
                
                    stars
                    
            
                        面向扩展:从 
                ToolConfig 到 ModelConfig 到 GeminiChat 封装,均保留了日后路由、预算化、回退与更复杂自治策略的空间。
                    