模块九:多 Agent 架构 | 前置依赖:第 10、15 课 | 预计学习时间:75 分钟
学习目标
完成本课后,你将能够:
- 描述 AgentTool 的输入/输出 Schema 及调用路径(Fork / 指定类型 / 多 Agent 生成)
- 解释 Agent 定义的加载机制(loadAgentsDir.ts)、来源优先级与过滤规则
- 列出 Agent 的工具限制(ALL_AGENT_DISALLOWED_TOOLS)及其设计理由
- 说明 runAgent 的完整生命周期、后台运行模式与 worktree 隔离
25.1 AgentTool 概览
AgentTool.tsx 是 233KB 的 TypeScript/React 文件,是 Claude Code 中最复杂的单个工具。它负责生成子 Agent — 让 Claude 能够将任务委托给专门化的 Agent。
文件结构
tools/AgentTool/
├── AgentTool.tsx 233KB 核心工具定义与调用逻辑
├── UI.tsx 125KB Agent UI 渲染(进度、结果、分组)
├── runAgent.ts 36KB Agent 运行生命周期
├── loadAgentsDir.ts 26KB Agent 定义加载
├── agentToolUtils.ts 23KB 工具过滤、异步生命周期
├── prompt.ts 17KB Prompt 模板与示例
├── forkSubagent.ts 9KB Fork 子 Agent 实验
├── resumeAgent.ts 9KB Agent 恢复(session resume)
├── agentMemory.ts 6KB Agent 持久化记忆
├── agentMemorySnapshot.ts 6KB 记忆快照管理
├── agentDisplay.ts 3KB 显示名称生成
├── builtInAgents.ts 3KB 内置 Agent 注册
├── constants.ts 547B 常量定义
├── agentColorManager.ts 1.5KB Agent 颜色管理
└── built-in/ 内置 Agent 定义目录
工具身份
export const AGENT_TOOL_NAME = 'Agent'
export const LEGACY_AGENT_TOOL_NAME = 'Task' // 历史别名,向后兼容
25.2 输入/输出 Schema
输入 Schema
const baseInputSchema = z.object({
description: z.string()
.describe('A short (3-5 word) description of the task'),
prompt: z.string()
.describe('The task for the agent to perform'),
subagent_type: z.string().optional()
.describe('The type of specialized agent to use'),
model: z.enum(['sonnet', 'opus', 'haiku']).optional()
.describe('Optional model override'),
run_in_background: z.boolean().optional()
.describe('Set to true to run this agent in the background'),
})
// 完整 Schema 添加多 Agent 参数
const fullInputSchema = baseInputSchema.merge(z.object({
name: z.string().optional()
.describe('Name for addressable spawned agent'),
team_name: z.string().optional()
.describe('Team name for spawning'),
mode: permissionModeSchema().optional()
.describe('Permission mode (e.g., "plan")'),
isolation: z.enum(['worktree']).optional()
.describe('Isolation mode for git worktree'),
cwd: z.string().optional()
.describe('Working directory override'),
}))
动态 Schema 裁剪:根据 feature gate 状态,部分字段在运行时被 .omit() 移除:
export const inputSchema = lazySchema(() => {
let schema = fullInputSchema()
// 后台任务被禁用时移除 run_in_background
if (isBackgroundTasksDisabled || isForkSubagentEnabled()) {
schema = schema.omit({ run_in_background: true })
}
return schema
})
输出 Schema
const outputSchema = z.union([
// 同步完成
agentToolResultSchema().extend({
status: z.literal('completed'),
prompt: z.string(),
}),
// 异步后台启动
z.object({
status: z.literal('async_launched'),
agentId: z.string(),
description: z.string(),
prompt: z.string(),
outputFile: z.string(),
canReadOutputFile: z.boolean().optional(),
}),
])
还有两个内部输出类型(不暴露在 Schema 中用于 dead code elimination):
TeammateSpawnedOutput— 多 Agent 生成结果RemoteLaunchedOutput— 远程 Agent 启动结果
25.3 调用路径分析
call() 方法是 AgentTool 的核心,包含三条主要路径:
AgentTool.call()
│
├── team_name + name 有值?
│ │
│ ▼ YES
│ 路径 1:spawnTeammate()
│ → 在 tmux/iTerm2/进程内 生成独立 Agent
│ → 返回 TeammateSpawnedOutput
│
├── subagent_type 未指定 + Fork gate 开启?
│ │
│ ▼ YES
│ 路径 2:Fork 子 Agent
│ → 继承父 Agent 完整上下文
│ → 共享 prompt cache
│ → 返回 async_launched
│
└── 默认路径
│
▼
路径 3:标准子 Agent 生成
→ 查找 selectedAgent 定义
→ 同步或后台运行
→ 返回 completed 或 async_launched
路径 1:多 Agent 生成(spawnTeammate)
if (teamName && name) {
const result = await spawnTeammate({
name,
prompt,
description,
team_name: teamName,
use_splitpane: true,
plan_mode_required: spawnMode === 'plan',
model: model ?? agentDef?.model,
agent_type: subagent_type,
}, toolUseContext)
return { data: { status: 'teammate_spawned', prompt, ...result.data } }
}
spawnMultiAgent.ts 负责实际的 Teammate 创建,支持三种后端:
spawnTeammate()
│
├── tmux — 在 tmux 面板中启动独立 Claude Code 进程
├── iTerm2 — 在 iTerm2 分割面板中启动
└── in-process — 在同一进程中运行(轻量级)
限制:Teammate 不能嵌套生成(团队花名册是扁平的),in-process Teammate 不能运行后台 Agent。
路径 2:Fork 子 Agent
Fork 是一个实验性功能 — 子 Agent 继承父 Agent 的完整对话上下文:
export const FORK_AGENT = {
agentType: 'fork',
tools: ['*'], // 继承父 Agent 完全相同的工具集
maxTurns: 200,
model: 'inherit', // 不切换模型,共享 prompt cache
permissionMode: 'bubble', // 权限请求冒泡到父终端
source: 'built-in',
}
防递归 Fork:
// 两道防线
// 1. querySource 检查(压缩安全,设定于 spawn 时)
if (toolUseContext.options.querySource === `agent:builtin:fork`) {
throw new Error('Fork is not available inside a forked worker.')
}
// 2. 消息扫描后备(检测 FORK_BOILERPLATE_TAG)
if (isInForkChild(toolUseContext.messages)) {
throw new Error('Fork is not available inside a forked worker.')
}
路径 3:标准子 Agent
// 查找 Agent 定义
const found = agents.find(agent => agent.agentType === effectiveType)
if (!found) {
// 检查是否被权限规则阻止
const denyRule = getDenyRuleForAgent(permissionContext, AGENT_TOOL_NAME, effectiveType)
throw new Error(`Agent type '${effectiveType}' has been denied by permission rule...`)
}
selectedAgent = found
// MCP 依赖检查:等待 pending 的 MCP Server 就绪
if (requiredMcpServers?.length) {
// 最多等 30 秒,500ms 轮询
while (Date.now() < deadline) {
await sleep(500)
if (!stillPending) break
}
// 验证工具可用
if (!hasRequiredMcpServers(selectedAgent, serversWithTools)) {
throw new Error(`Required MCP server(s) not available: ...`)
}
}
25.4 Agent 定义加载(loadAgentsDir.ts)
定义来源
Agent 定义有四种来源,按优先级排列:
┌─────────────────────────────────────┐
│ 来源 │ 路径/方式 │
├─────────────────────────────────────┤
│ built-in │ AgentTool/built-in/ │
│ plugin │ 插件提供的 Agent │
│ userSettings │ ~/.claude/agents/ │
│ projectSettings│ .claude/agents/ │
│ policySettings │ 企业管理策略 │
└─────────────────────────────────────┘
AgentDefinition 类型
type BaseAgentDefinition = {
agentType: string // 类型标识(如 "code-reviewer")
whenToUse: string // 描述何时使用此 Agent
tools?: string[] // 允许的工具列表
disallowedTools?: string[] // 禁止的工具列表
skills?: string[] // 预加载的技能
mcpServers?: AgentMcpServerSpec[] // Agent 专属 MCP Server
hooks?: HooksSettings // 会话级 Hook
color?: AgentColorName // UI 颜色
model?: string // 模型覆盖(如 'haiku', 'inherit')
effort?: EffortValue // 推理努力级别
permissionMode?: PermissionMode
maxTurns?: number // 最大对话轮数
background?: boolean // 始终后台运行
isolation?: 'worktree' | 'remote'
memory?: AgentMemoryScope // 持久化记忆范围
omitClaudeMd?: boolean // 省略 CLAUDE.md(节省 Token)
}
// 三种具体类型
type BuiltInAgentDefinition = BaseAgentDefinition & {
source: 'built-in'
getSystemPrompt: (params) => string // 动态系统提示
}
type CustomAgentDefinition = BaseAgentDefinition & {
source: SettingSource // 'userSettings' | 'projectSettings' | ...
getSystemPrompt: () => string
}
type PluginAgentDefinition = BaseAgentDefinition & {
source: 'plugin'
plugin: string // 来源插件标识
getSystemPrompt: () => string
}
Markdown Agent 定义
用户通常通过 Markdown 文件定义 Agent(~/.claude/agents/code-reviewer.md):
---
description: "Review code for quality, security, and best practices"
tools: ["Read", "Glob", "Grep"]
model: haiku
maxTurns: 10
mcpServers:
- slack
- name: custom-server
command: npx custom-mcp
---
You are a code reviewer. Review the code for...
前置 YAML 被解析为 AgentDefinition 字段,Markdown 正文成为系统提示。
活跃 Agent 过滤
不是所有 Agent 都对模型可见。过滤逻辑:
function getActiveAgentsFromList(allAgents): AgentDefinition[] {
// 1. 分组:built-in / plugin / user / project / managed
// 2. 用户/项目 Agent 可覆盖 built-in Agent(同名时)
// 3. 过滤掉缺少必要 MCP Server 的 Agent
// 4. 过滤掉被权限规则阻止的 Agent
return activeAgents
}
One-Shot Agent 优化
某些内置 Agent(如 Explore、Plan)只运行一次就返回报告,不需要后续 SendMessage 交互:
export const ONE_SHOT_BUILTIN_AGENT_TYPES: ReadonlySet<string> = new Set([
'Explore',
'Plan',
])
// 跳过 agentId/SendMessage/usage 尾部,每次节省 ~135 字符
// × 3400 万次 Explore 调用/周 = 可观的 Token 节省
25.5 工具限制
ALL_AGENT_DISALLOWED_TOOLS
子 Agent 被禁止使用某些工具:
export const ALL_AGENT_DISALLOWED_TOOLS = new Set([
'TaskOutput', // 不能读取其他任务的输出
'ExitPlanMode', // 不能退出计划模式
'EnterPlanMode', // 不能进入计划模式
'Agent', // 默认不能嵌套生成 Agent(ant 用户除外)
'AskUserQuestion', // 不能直接向用户提问
'TaskStop', // 不能停止其他任务
'Workflow', // 不能执行工作流脚本
])
// 自定义 Agent 额外限制
export const CUSTOM_AGENT_DISALLOWED_TOOLS = new Set([
...ALL_AGENT_DISALLOWED_TOOLS,
// 可扩展
])
异步 Agent 工具白名单
后台运行的 Agent 使用白名单而非黑名单:
export const ASYNC_AGENT_ALLOWED_TOOLS = new Set([
'Read', 'WebSearch', 'TodoWrite', 'Grep', 'WebFetch',
'Glob', 'Bash', 'PowerShell', 'Edit', 'Write',
'NotebookEdit', 'Skill', 'SyntheticOutput', 'ToolSearch',
'EnterWorktree', 'ExitWorktree',
// ... 其他安全工具
])
工具过滤实现
function filterToolsForAgent({ tools, isBuiltIn, isAsync, permissionMode }): Tools {
return tools.filter(tool => {
// MCP 工具始终允许
if (tool.name.startsWith('mcp__')) return true
// Plan 模式下允许 ExitPlanMode
if (toolMatchesName(tool, 'ExitPlanMode') && permissionMode === 'plan')
return true
// 黑名单检查
if (ALL_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
if (!isBuiltIn && CUSTOM_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
// 异步模式白名单检查
if (isAsync && !ASYNC_AGENT_ALLOWED_TOOLS.has(tool.name)) return false
return true
})
}
25.6 Agent 运行生命周期(runAgent.ts)
完整生命周期
runAgent() 被调用
│
├── 1. 初始化 Agent MCP Servers
│ └── 连接 Agent 专属的 MCP Server
│
├── 2. 构建工具池
│ └── 解析 tools/disallowedTools,过滤
│
├── 3. 构建系统提示
│ ├── Agent 的 systemPrompt
│ ├── 环境信息增强
│ ├── 用户上下文(CLAUDE.md,可选省略)
│ └── 系统上下文
│
├── 4. 注册 Frontmatter Hooks
│
├── 5. 运行主循环
│ └── query() — 与模型对话
│ ├── 处理流事件(streaming)
│ ├── 执行工具调用
│ ├── 轮数检查(maxTurns)
│ └── abort 信号处理
│
├── 6. 提取最终结果
│ └── 最后一条 Assistant 消息
│
└── 7. 清理
├── 关闭 Agent MCP Server
├── 注销 Frontmatter Hooks
└── 清理 Perfetto 追踪
Agent 专属 MCP Server
Agent 可以定义自己的 MCP Server,独立于父 Agent:
async function initializeAgentMcpServers(agentDefinition, parentClients) {
for (const spec of agentDefinition.mcpServers) {
if (typeof spec === 'string') {
// 引用现有 Server(共享连接)
config = getMcpConfigByName(spec)
} else {
// 内联定义(独立连接,Agent 结束后清理)
const [serverName, serverConfig] = Object.entries(spec)[0]
config = { ...serverConfig, scope: 'dynamic' }
}
const client = await connectToServer(name, config)
// 获取工具并合并
}
// cleanup 只关闭内联定义的连接,不关闭共享连接
}
Worktree 隔离
isolation: 'worktree' 时,Agent 在独立的 git worktree 中工作:
主仓库: /project
│
└── .git/worktrees/agent-xxx/
│
▼
/tmp/claude-worktree-xxx/
└── (完整的文件副本)
Agent 在此操作,不影响主仓库
Agent 完成后检查是否有变更需要合并。
25.7 Prompt 工程
Agent 列表注入
Agent 列表可以通过两种方式注入到模型的上下文中:
function shouldInjectAgentListInMessages(): boolean {
// 方式 1:内联在工具描述中(传统方式)
// 方式 2:作为 attachment 消息注入(新方式,利于 prompt cache)
}
为什么要用 attachment? 动态 Agent 列表(MCP 异步连接、插件重载)会导致工具描述变化,进而导致 prompt cache 失效。将列表移到 attachment 中,工具描述保持静态。
Prompt 模板
// 引导模型正确使用 Agent
`Brief the agent like a smart colleague who just walked into the room —
it hasn't seen this conversation, doesn't know what you've tried, doesn't
understand why this task matters.
- Explain what you're trying to accomplish and why.
- Describe what you've already learned or ruled out.
- Give enough context about the surrounding problem...
**Never delegate understanding.** Don't write "based on your findings, fix
the bug" — those phrases push synthesis onto the agent instead of doing it
yourself.`
Fork 专用提示
Fork 模式有独立的提示规则:
Fork yourself (omit subagent_type) when the intermediate tool output
isn't worth keeping in your context.
**Don't peek.** The tool result includes an output_file path — do not
Read or tail it unless the user explicitly asks.
**Don't race.** After launching, you know nothing about what the fork
found. Never fabricate or predict fork results.
25.8 通信模型
同步通信
标准 Agent 运行完成后返回单一结果消息。模型看到的是工具的返回值。
异步通信
后台 Agent 通过 TaskOutputTool 进行进度查询和结果获取(详见第 26 课)。
SendMessageTool
多 Agent 模式下,Agent 之间通过 SendMessageTool 通信:
Leader Agent Teammate Agent
│ │
│ SendMessage({to: "reviewer", │
│ message: "Please check..."}) │
│ ──────────────────────────────▶│
│ │
│ ◀───────────────────────────── │
│ (通过 mailbox 接收响应) │
课后练习
练习 1:调用路径追踪
给定以下 AgentTool 调用,判断每个走哪条路径:
Agent({ prompt: "review code", subagent_type: "code-reviewer" })Agent({ prompt: "check tests", name: "tester", team_name: "dev" })Agent({ prompt: "research options" })(Fork gate 开启)Agent({ prompt: "deploy", run_in_background: true, subagent_type: "deploy-agent" })
练习 2:Agent 定义设计
为一个 "security-scanner" Agent 编写 Markdown 定义文件。要求:
- 只允许使用 Read、Grep、Glob 工具
- 使用 haiku 模型
- 最多 20 轮对话
- 依赖名为 "semgrep" 的 MCP Server
练习 3:工具限制分析
解释为什么 AskUserQuestion 被禁止用于子 Agent,但 Bash 被允许。从安全性和用户体验两个角度分析。
练习 4:Fork vs 标准 Agent
列出 Fork 子 Agent 和标准子 Agent 在以下维度的差异:上下文继承、prompt cache 共享、递归限制、工具集、模型选择。
本课小结
| 要点 | 内容 |
|---|---|
| 文件规模 | AgentTool.tsx 233KB,是最复杂的单个工具 |
| 三条路径 | spawnTeammate(多 Agent)/ Fork(上下文继承)/ 标准(指定类型) |
| Agent 定义 | 4 种来源(built-in/plugin/user/project),Markdown 前置 YAML + 正文 |
| 工具限制 | 黑名单(ALL_AGENT_DISALLOWED_TOOLS)+ 异步白名单 |
| 生命周期 | MCP 初始化 → 工具池 → 系统提示 → query() 循环 → 清理 |
| 隔离模式 | worktree(独立 git 分支)、remote(远程 CCR) |
下一课预告
第 26 课:Task 系统 — 后台任务管理 — 理解 6 个 Task 工具(Create/Get/List/Update/Output/Stop)、TaskState 生命周期、Agent 后台运行机制、实时进度追踪以及 TodoWriteTool 的演进。