五、AI Agent 设计模式:子 Agent 架构

8 阅读2分钟

上一篇我们分析了上下文管理。今天深入第四个主题:子 Agent 架构。Agent 可以启动子 Agent 来处理复杂任务。Fork 模式如何工作?父子 Agent 如何通信?

为什么需要子 Agent?

复杂任务不适合单个 Agent:

  • 探索代码库:需要大量搜索,消耗大量 Token
  • 设计计划:需要独立思考,不受主对话干扰
  • 并行任务:多个子任务可以同时进行
  • 资源隔离:子 Agent 有独立的 Token 预算

子 Agent 是一个独立的执行单元,有自己的上下文、工具、权限模式。

Fork vs Inline 模式

Inline 模式

Skill 内容注入当前对话:

// 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 中执行:

// 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 核心流程

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-guideCLI 使用指南专用系统提示
verification代码审查验证专用 callback

Explore Agent

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

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 通信

消息传递

// 子 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)
  }
}

状态共享

// 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)
  }
}

文件状态继承

// Fork 模式:继承文件状态
if (forkContextMessages) {
  // 克隆父 Agent 的文件状态
  agentReadFileState = cloneFileStateCache(toolUseContext.readFileState)
}

// 子 Agent 编辑文件后,状态更新
agentReadFileState.set(filePath, { content, timestamp })

// 结束时,父 Agent 可以看到变化吗?
// 不——克隆是独立的,子 Agent 结束后不同步回父 Agent

克隆是独立的——子 Agent 的文件状态变化不影响父 Agent。

子 Agent 的 MCP Servers

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

// 异步 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 记录

// 每个 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 元数据

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 追踪

// 开启追踪时,注册 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 InlineFork 独立执行,Inline 共享上下文
Agent 定义agentType, permissionMode, maxTurns, model, mcpServers
上下文隔离克隆文件状态、独立 AbortController、独立消息
权限模式子 Agent 可有不同权限模式
MCP Servers可继承父 Agent 或启动专属 servers
状态共享shareSetAppState 决定是否共享
异步执行后台运行,不阻塞主对话
Transcript每个 Agent 独立 transcript
追踪Perfetto 追踪 Agent 时间线

核心设计:Fork + 克隆 + 独立上下文 + 异步支持

下一篇,我们将深入 MCP 协议集成——外部工具如何接入 Claude。