上一篇我们分析了上下文管理。今天深入第四个主题:子 Agent 架构。Agent 可以启动子 Agent 来处理复杂任务。Fork 模式如何工作?父子 Agent 如何通信?
为什么需要子 Agent?
复杂任务不适合单个 Agent:
- 探索代码库:需要大量搜索,消耗大量 Token
- 设计计划:需要独立思考,不受主对话干扰
- 并行任务:多个子任务可以同时进行
- 资源隔离:子 Agent 有独立的 Token 预算
子 Agent 是一个独立的执行单元,有自己的上下文、工具、权限模式。
Fork vs Inline 模式
Inline 模式
Skill 内容注入当前对话:
typescript
// Inline 执行
async function executeInlineSkill(skill, args, context) {
// 1. 处理 skill 提示
const processedPrompt = processSkillPrompt(skill.prompt, args)
// 2. 注入消息
const newMessage = createUserMessage({
content: processedPrompt,
isMeta: true,
})
// 3. 返回新消息
return {
newMessages: [newMessage],
contextModifier: (ctx) => ({
...ctx,
options: {
...ctx.options,
allowedTools: skill.allowedTools ?? ctx.options.tools,
},
}),
}
}
Inline 模式:
- 共享主对话的 Token 预算
- 共享消息历史
- 工具白名单可修改
- 适用于简单 skills
Fork 模式
在独立子 Agent 中执行:
typescript
// Fork 执行
async function* executeForkedSkill(skill, args, context) {
// 1. 创建子 Agent 定义
const agentDefinition = {
agentType: `skill-${skill.name}`,
source: 'skill',
getSystemPrompt: () => [skill.prompt],
permissionMode: skill.permissionMode ?? 'default',
skills: skill.preloadSkills,
maxTurns: skill.maxTurns ?? 20,
mcpServers: skill.mcpServers,
}
// 2. Fork 子 Agent
const forkedMessages = context.messages.slice(-5) // 最近几条作为上下文
for await (const message of runAgent({
agentDefinition,
promptMessages: [createUserMessage({ content: args })],
toolUseContext: context,
forkContextMessages: forkedMessages,
isAsync: false,
})) {
yield message
}
}
Fork 模式:
- 独立的 Token 预算
- 独立的消息历史(可选继承部分)
- 独立的权限模式
- 适用于复杂 skills
runAgent 核心流程
typescript
async function* runAgent({
agentDefinition,
promptMessages,
toolUseContext,
canUseTool,
isAsync,
canShowPermissionPrompts,
forkContextMessages,
querySource,
override,
model,
maxTurns,
availableTools,
allowedTools,
}): AsyncGenerator<Message> {
// === 阶段 1: Agent ID ===
const agentId = override?.agentId ?? createAgentId()
const agentType = agentDefinition.agentType
// === 阶段 2: Perfetto 追踪 ===
if (isPerfettoTracingEnabled()) {
registerPerfettoAgent(agentId, agentType, toolUseContext.agentId)
}
// === 阶段 3: 消息初始化 ===
const contextMessages = forkContextMessages
? filterIncompleteToolCalls(forkContextMessages)
: []
const initialMessages = [...contextMessages, ...promptMessages]
// === 阶段 4: 文件状态缓存 ===
const agentReadFileState = forkContextMessages !== undefined
? cloneFileStateCache(toolUseContext.readFileState)
: createFileStateCacheWithSizeLimit(READ_FILE_STATE_CACHE_SIZE)
// === 阶段 5: 用户上下文 ===
const [baseUserContext, baseSystemContext] = await Promise.all([
override?.userContext ?? getUserContext(),
override?.systemContext ?? getSystemContext(),
])
// CLAUDE.md 精简(某些 Agent)
const shouldOmitClaudeMd = agentDefinition.omitClaudeMd &&
getFeatureValue('tengu_slim_subagent_claudemd', true)
const resolvedUserContext = shouldOmitClaudeMd
? { ...baseUserContext, claudeMd: undefined }
: baseUserContext
// === 阶段 6: 权限模式 ===
const agentGetAppState = () => {
let state = toolUseContext.getAppState()
let toolPermissionContext = state.toolPermissionContext
// 子 Agent 可能有不同的权限模式
if (agentDefinition.permissionMode && state.mode !== 'bypassPermissions') {
toolPermissionContext = {
...toolPermissionContext,
mode: agentDefinition.permissionMode,
}
}
// 异步 Agent 避免权限提示
if (isAsync || !canShowPermissionPrompts) {
toolPermissionContext = {
...toolPermissionContext,
shouldAvoidPermissionPrompts: true,
}
}
return { ...state, toolPermissionContext }
}
// === 阶段 7: MCP Servers ===
const { clients: agentMcpClients, tools: agentMcpTools, cleanup: mcpCleanup } =
await initializeAgentMcpServers(agentDefinition, toolUseContext.options.mcpClients)
// === 阶段 8: Skills 预加载 ===
const skillsToPreload = agentDefinition.skills ?? []
if (skillsToPreload.length > 0) {
const loaded = await loadSkillsForAgent(skillsToPreload)
for (const { skill, content } of loaded) {
initialMessages.push(createUserMessage({
content: [skill.metadata, ...content],
isMeta: true,
}))
}
}
// === 阶段 9: SubagentStart Hooks ===
const additionalContexts: string[] = []
for await (const hookResult of executeSubagentStartHooks(agentId, agentType)) {
if (hookResult.additionalContexts) {
additionalContexts.push(...hookResult.additionalContexts)
}
}
// === 阶段 10: Frontmatter Hooks ===
if (agentDefinition.hooks && hooksAllowedForThisAgent) {
registerFrontmatterHooks(agentGetAppState, agentId, agentDefinition.hooks)
}
// === 阶段 11: 子 Agent 上下文 ===
const agentAbortController = new AbortController()
const agentToolUseContext = {
...toolUseContext,
// 独立的 AbortController
abortController: agentAbortController,
// 独立的 Agent ID
agentId,
agentType,
// 独立的消息
messages: initialMessages,
// 克隆/新的文件状态
readFileState: agentReadFileState,
// 独立的状态管理
getAppState: agentGetAppState,
shareSetAppState: !isAsync,
// 独立的 MCP clients
options: {
...toolUseContext.options,
mcpClients: agentMcpClients,
tools: [...availableTools, ...agentMcpTools],
},
}
// === 阶段 12: Transcript 记录 ===
await recordSidechainTranscript(initialMessages, agentId)
await writeAgentMetadata(agentId, { agentType, description: promptMessages })
// === 阶段 13: 执行 query 循环 ===
try {
for await (const message of query({
messages: initialMessages,
systemPrompt: agentSystemPrompt,
userContext: resolvedUserContext,
systemContext: resolvedSystemContext,
canUseTool,
toolUseContext: agentToolUseContext,
querySource,
maxTurns: maxTurns ?? agentDefinition.maxTurns ?? 50,
model: model ?? agentDefinition.model,
})) {
// 转发 API metrics
if (message.type === 'stream_event' && message.event.type === 'message_start') {
toolUseContext.pushApiMetricsEntry?.(message.ttftMs)
}
// max_turns_reached 处理
if (message.attachment?.type === 'max_turns_reached') {
break
}
// 记录并 yield
if (isRecordableMessage(message)) {
await recordSidechainTranscript([message], agentId)
yield message
}
}
// 内置 Agent callback
if (isBuiltInAgent(agentDefinition) && agentDefinition.callback) {
agentDefinition.callback()
}
} finally {
// === 阶段 14: 清理 ===
await mcpCleanup()
clearSessionHooks(agentGetAppState, agentId)
cleanupAgentTracking(agentId)
agentToolUseContext.readFileState.clear()
unregisterPerfettoAgent(agentId)
killShellTasksForAgent(agentId)
}
}
14 个阶段,完整的子 Agent 生命周期。
内置 Agent 类型
| Agent | 用途 | 特性 |
|---|---|---|
Explore |
快速代码库探索 | omitClaudeMd, maxTurns=50, haiku |
Plan |
设计实现计划 | omitClaudeMd, maxTurns=50 |
general-purpose |
通用多步任务 | 无特殊限制 |
claude-code-guide |
CLI 使用指南 | 专用系统提示 |
verification |
代码审查验证 | 专用 callback |
Explore Agent
typescript
const ExploreAgentDefinition = {
agentType: 'Explore',
source: 'bundled',
getSystemPrompt: () => [EXPLORE_AGENT_PROMPT],
omitClaudeMd: true, // 不加载 CLAUDE.md
maxTurns: 50,
model: 'haiku', // 快速模型
description: 'Fast agent for codebase exploration',
}
Explore Agent:
- 不加载 CLAUDE.md(节省 Token)
- 使用 haiku 模型(更快、更便宜)
- 最大 50 轮(足够探索)
Plan Agent
typescript
const PlanAgentDefinition = {
agentType: 'Plan',
source: 'bundled',
getSystemPrompt: () => [PLAN_AGENT_PROMPT],
omitClaudeMd: true,
maxTurns: 50,
model: 'sonnet', // 更强的模型
description: 'Software architect agent for planning',
}
Plan Agent:
- 使用 sonnet 模型(更强推理)
- 专门设计实现计划
父子 Agent 通信
消息传递
typescript
// 子 Agent yield 消息给父 Agent
for await (const message of runAgent(...)) {
yield message // 父 Agent 接收
}
// 父 Agent 处理子 Agent 消息
for await (const message of AgentTool.call(...)) {
if (message.type === 'assistant') {
// 子 Agent 的响应
appendToConversation(message)
}
if (message.type === 'tool_result') {
// 子 Agent 的工具执行结果
appendToConversation(message)
}
}
状态共享
typescript
// shareSetAppState: 是否共享状态
const agentToolUseContext = {
...toolUseContext,
shareSetAppState: !isAsync, // 同步 Agent 共享状态
}
// 同步 Agent:共享 AppState
if (shareSetAppState) {
// 子 Agent 的状态修改影响父 Agent
agentToolUseContext.setAppState = toolUseContext.setAppState
}
// 异步 Agent:独立 AppState
if (!shareSetAppState) {
// 子 Agent 有独立状态
let localState = initialAppState
agentToolUseContext.setAppState = (updater) => {
localState = updater(localState)
}
}
文件状态继承
typescript
// Fork 模式:继承文件状态
if (forkContextMessages) {
// 克隆父 Agent 的文件状态
agentReadFileState = cloneFileStateCache(toolUseContext.readFileState)
}
// 子 Agent 编辑文件后,状态更新
agentReadFileState.set(filePath, { content, timestamp })
// 结束时,父 Agent 可以看到变化吗?
// 不------克隆是独立的,子 Agent 结束后不同步回父 Agent
克隆是独立的------子 Agent 的文件状态变化不影响父 Agent。
子 Agent 的 MCP Servers
typescript
async function initializeAgentMcpServers(agentDefinition, parentMcpClients) {
// Agent 可能有专属 MCP servers
const agentMcpConfig = agentDefinition.mcpServers ?? []
if (agentMcpConfig.length === 0) {
// 继承父 Agent 的 MCP servers
return {
clients: parentMcpClients,
tools: [],
cleanup: async () => {},
}
}
// 启动专属 MCP servers
const newClients: MCPServerConnection[] = []
for (const config of agentMcpConfig) {
const client = await connectMcpServer(config)
newClients.push({
...client,
isSubagentOwned: true, // 标记为子 Agent 拥有
})
}
// 合并:父 Agent servers + 专属 servers
const mergedClients = [...parentMcpClients, ...newClients]
// 清理函数
const cleanup = async () => {
for (const client of newClients) {
await client.cleanup()
}
}
return {
clients: mergedClients,
tools: extractMcpTools(newClients),
cleanup,
}
}
子 Agent 可以有专属的 MCP servers------结束时清理。
异步 Agent
typescript
// 异步 Agent 在后台运行
async function runAsyncAgent(agentDefinition, promptMessages, context) {
const agentId = createAgentId()
// 不共享状态
const agentContext = createSubagentContext(context, {
agentId,
shareSetAppState: false,
getAppState: createLocalAppState,
})
// 后台运行
const promise = (async () => {
const results: Message[] = []
for await (const message of runAgent({
agentDefinition,
promptMessages,
toolUseContext: agentContext,
isAsync: true,
canShowPermissionPrompts: false,
})) {
results.push(message)
}
return results
})()
// 注册追踪
registerAsyncAgent(agentId, promise)
// 返回 Agent ID,不等待完成
return {
agentId,
promise,
status: 'running',
}
}
// 主循环继续
// 异步 Agent 完成时,通知用户
异步 Agent:
- 后台运行,不阻塞主对话
- 独立状态,不共享
- 完成后通知用户
Agent Transcript 记录
typescript
// 每个 Agent 有独立的 transcript
async function recordSidechainTranscript(messages: Message[], agentId: string) {
const transcriptDir = `.claude/transcripts/${agentId}`
await fs.mkdir(transcriptDir)
for (const message of messages) {
const fileName = `${message.uuid}.json`
await fs.writeFile(
join(transcriptDir, fileName),
JSON.stringify(message),
)
}
}
// 子 Agent 结束时清理
async function clearAgentTranscriptSubdir(agentId: string) {
// 可选:保留 transcript 或删除
if (shouldKeepTranscripts) {
// 保留用于调试
} else {
await fs.rm(`.claude/transcripts/${agentId}`, { recursive: true })
}
}
每个 Agent 有独立的 transcript------用于调试和审计。
Agent 元数据
typescript
type AgentMetadata = {
agentId: string
agentType: string
parentId?: string
worktreePath?: string
description?: string
startTime: number
endTime?: number
turnCount?: number
status: 'running' | 'completed' | 'failed'
}
async function writeAgentMetadata(agentId: string, metadata: Partial<AgentMetadata>) {
const filePath = `.claude/agents/${agentId}/metadata.json`
await fs.mkdir(dirname(filePath))
const existing = await fs.exists(filePath)
? JSON.parse(await fs.readFile(filePath))
: {}
const updated = { ...existing, ...metadata }
await fs.writeFile(filePath, JSON.stringify(updated))
}
Agent 元数据用于追踪和管理。
Perfetto 追踪
typescript
// 开启追踪时,注册 Agent
function registerPerfettoAgent(agentId: string, agentType: string, parentId?: string) {
perfettoWriteEvent({
name: 'agent_start',
categories: ['agent'],
args: {
agentId,
agentType,
parentId,
timestamp: Date.now(),
},
})
}
// Agent 结束时注销
function unregisterPerfettoAgent(agentId: string) {
perfettoWriteEvent({
name: 'agent_end',
categories: ['agent'],
args: {
agentId,
timestamp: Date.now(),
},
})
}
Perfetto 追踪用于性能分析------可视化 Agent 执行时间线。
总结
子 Agent 架构展示了分层执行设计:
| 设计点 | 实现 |
|---|---|
| Fork vs Inline | Fork 独立执行,Inline 共享上下文 |
| Agent 定义 | agentType, permissionMode, maxTurns, model, mcpServers |
| 上下文隔离 | 克隆文件状态、独立 AbortController、独立消息 |
| 权限模式 | 子 Agent 可有不同权限模式 |
| MCP Servers | 可继承父 Agent 或启动专属 servers |
| 状态共享 | shareSetAppState 决定是否共享 |
| 异步执行 | 后台运行,不阻塞主对话 |
| Transcript | 每个 Agent 独立 transcript |
| 追踪 | Perfetto 追踪 Agent 时间线 |
核心设计:Fork + 克隆 + 独立上下文 + 异步支持。
下一篇,我们将深入 MCP 协议集成------外部工具如何接入 Claude。