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

上一篇我们分析了上下文管理。今天深入第四个主题:子 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。

相关推荐
人月神话-Lee1 小时前
【图像处理】坐标系与图像加载——UIImage 是怎么变成内存像素的
图像处理·人工智能
PanShanShan1 小时前
我把 Claude Code 发掘金的 token 成本砍到 1/50:web-publish 设计实录
人工智能
Hector_zh1 小时前
容器化部署踩坑记:测试环境 Git 凭证外挂方案验证
人工智能·ai编程
cici158741 小时前
基于 BP 神经网络的语音信号分类系统
人工智能·神经网络·分类
AI街潜水的八角1 小时前
PyTorch框架——基于深度学习SRN-DeblurNet神经网络AI去模糊图像增强系统
人工智能·pytorch·深度学习
alex2751 小时前
🔥 Spring AI 流式输出深度实战:SSE + 停止按钮 + JSON 事件,一文全搞定
人工智能
alex2751 小时前
深入 Spring AI 聊天补全:ChatClient、PromptTemplate、Advisor 一网打尽!
人工智能
IVEN_1 小时前
Hermes Agent 接入 Kimi Coding 套餐:修复 Vision 图像分析功能
人工智能
Bode_20021 小时前
AI时代制造企业创新的需要的关键技术
人工智能