Claude Code Subagent 系统:完整设计与源码分析
基于 Claude Code npm 包 source map 泄露快照 (2026-03-31) 的源码分析。
适用于团队内部分享,理解 AI Agent 多级派生与协同的工程范式。
一、设计哲学
Claude Code 的 subagent 系统解决一个核心问题:单个 agent 无法高效处理所有任务。有些任务需要深度探索(读大量文件),有些需要独立编写代码(隔离修改),有些需要多个 agent 并行工作。
源码中体现的设计原则:
- Cache 复用优先:Fork 子 agent 继承父 agent 的完整系统提示和工具列表,实现字节级 prompt cache 命中
- 隔离可控:从共享 CWD 到 git worktree 到远程环境,三级隔离按需选择
- 工具收敛:子 agent 的工具集是父 agent 的子集,通过多层过滤递进收窄
- 生命周期解耦:前台/后台可动态切换,中断可恢复
二、Subagent 类型体系
2.1 三种执行模式
| 模式 | 触发条件 | 上下文 | 工具集 | 阻塞父 agent |
|---|---|---|---|---|
| 同步前台 | run_in_background=false(默认) | 新建(无历史) | agent 定义过滤后 | 是 |
| 异步后台 | run_in_background=true 或 background: true | 新建(无历史) | 异步安全子集 | 否 |
| Fork | subagent_type 未指定 + FORK_SUBAGENT 启用 | 继承父 agent 完整对话 | 父 agent 完全相同 | 否(强制异步) |
2.2 Agent 定义类型层次
文件:src/tools/AgentTool/loadAgentsDir.ts
type AgentDefinition = BuiltInAgentDefinition | CustomAgentDefinition | PluginAgentDefinition
type BaseAgentDefinition = {
agentType: string // 唯一标识(如 'Explore', 'Plan', 'worker')
whenToUse: string // 何时使用的描述
tools?: string[] // 工具白名单;undefined = 所有可用工具
disallowedTools?: string[] // 工具黑名单
skills?: string[] // 预加载的技能
mcpServers?: AgentMcpServerSpec[] // agent 专属 MCP 服务器
hooks?: HooksSettings // 会话级 hook 定义
color?: AgentColorName // UI 颜色
model?: string // 'inherit'|'haiku'|'sonnet'|'opus'
effort?: EffortValue // 推理深度
permissionMode?: PermissionMode // 'acceptEdits'|'plan'|'bubble'|'auto'
maxTurns?: number // 最大迭代轮数
memory?: AgentMemoryScope // 'user'|'project'|'local'
isolation?: 'worktree'|'remote' // 文件系统隔离级别
background?: boolean // 是否始终异步
omitClaudeMd?: boolean // 是否排除 CLAUDE.md
criticalSystemReminder_EXPERIMENTAL?: string // 每轮注入的关键提醒
}
2.3 Agent 定义来源
| 来源 | 位置 | 优先级 | 示例 |
|---|---|---|---|
| built-in | 代码内置 | 最低 | Explore, Plan, code-reviewer |
| userSettings | ~/.claude/agents/*.md | 中 | 用户自定义 agent |
| projectSettings | .claude/agents/*.md | 中高 | 项目级 agent |
| policySettings | 组织策略 | 高 | 企业级 agent |
| plugin | 已安装插件 | 按插件 | 插件提供的 agent |
内置 agent 示例:
// ONE_SHOT_BUILTIN_AGENT_TYPES — 一次性 agent,不期望后续通信
const ONE_SHOT_BUILTIN_AGENT_TYPES = new Set(['Explore', 'Plan'])
2.4 Agent 定义格式(Markdown)
用户可以在 .claude/agents/ 中创建 markdown 文件定义自定义 agent:
---
agentType: my-reviewer
whenToUse: Use for code review tasks
tools: ['Read', 'Grep', 'Glob']
model: sonnet
maxTurns: 10
permissionMode: plan
---
You are a code reviewer. Focus on...
三、工具过滤:三级收窄
3.1 过滤层次
文件:src/tools/AgentTool/agentToolUtils.ts + src/constants/tools.ts
3.2 核心过滤函数
export function filterToolsForAgent({
tools, isBuiltIn, isAsync, permissionMode
}): Tools {
return tools.filter(tool => {
// MCP 工具始终允许
if (tool.name.startsWith('mcp__')) return true
// Plan 模式下允许 ExitPlanMode
if (toolMatchesName(tool, EXIT_PLAN_MODE_V2_TOOL_NAME)
&& permissionMode === 'plan') return true
// 全局禁止
if (ALL_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
// 自定义 agent 额外禁止
if (!isBuiltIn && CUSTOM_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
// 异步 agent 白名单
if (isAsync && !ASYNC_AGENT_ALLOWED_TOOLS.has(tool.name)) {
// in-process 队友的额外白名单
if (isAgentSwarmsEnabled() && isInProcessTeammate()) {
if (IN_PROCESS_TEAMMATE_ALLOWED_TOOLS.has(tool.name)) return true
}
return false
}
return true
})
}
3.3 Fork 路径的特殊处理
Fork 子 agent 设置 useExactTools: true,跳过所有过滤,直接继承父 agent 的完整工具集。这确保了与父 agent 字节级相同的工具 schema,从而命中 prompt cache。
四、Subagent 上下文创建
4.1 上下文隔离函数
文件:src/utils/forkedAgent.ts
export function createSubagentContext(
parentContext: ToolUseContext,
overrides?: SubagentContextOverrides,
): ToolUseContext {
// ① AbortController:新子控制器链接到父控制器
// 父 abort → 子自动 abort;子 abort → 父不受影响
const abortController = overrides?.shareAbortController
? parentContext.abortController
: createChildAbortController(parentContext.abortController)
// ② AppState 访问:包装为自动设置 shouldAvoidPermissionPrompts
// 后台 agent 无法弹出权限对话框,自动拒绝
const getAppState = () => ({
...parentContext.getAppState(),
toolPermissionContext: {
...parentContext.getAppState().toolPermissionContext,
shouldAvoidPermissionPrompts: true,
},
})
return {
// ③ 可变状态:全部克隆,隔离父子
readFileState: cloneFileStateCache(parentContext.readFileState),
nestedMemoryAttachmentTriggers: new Set(),
loadedNestedMemoryPaths: new Set(),
discoveredSkillNames: new Set(),
contentReplacementState: /* 克隆 */,
// ④ 状态变更回调:默认为 no-op(子 agent 的 setAppState 不影响父)
setAppState: () => {},
setAppStateForTasks: parentContext.setAppStateForTasks ?? parentContext.setAppState,
// ↑ 任务注册回调始终可达根 store(后台任务需要在任何嵌套深度注册/清理)
// ⑤ UI 回调:子 agent 无 UI
setToolJSX: undefined,
addNotification: undefined,
setStreamMode: undefined,
// ⑥ 拒绝追踪:独立实例,防止子 agent 的拒绝计数影响父
localDenialTracking: createDenialTrackingState(),
// ⑦ 查询追踪:深度 + 1
queryTracking: {
chainId: randomUUID(),
depth: (parentContext.queryTracking?.depth ?? -1) + 1,
},
// ⑧ 继承不可变数据
options: parentContext.options,
fileReadingLimits: parentContext.fileReadingLimits,
}
}
4.2 关键隔离决策
| 字段 | 隔离方式 | 原因 |
|---|---|---|
readFileState | 克隆 LRU cache | 防止子 agent 的 Read 操作污染父的文件缓存 |
abortController | 新子控制器(链接父) | 子 abort 不影响父,但父 abort 传播到子 |
setAppState | no-op | 子 agent 不能修改全局状态 |
setAppStateForTasks | 共享根 store | 后台任务必须能在任何嵌套层注册 |
localDenialTracking | 独立实例 | 子 agent 的权限拒绝不应触发父的熔断器 |
contentReplacementState | 克隆 | 工具结果落盘决策需要独立追踪 |
五、Agent 执行引擎
5.1 runAgent() — 核心执行生成器
文件:src/tools/AgentTool/runAgent.ts
export async function* runAgent(params: {
agentDefinition: AgentDefinition
promptMessages: Message[]
toolUseContext: ToolUseContext
canUseTool: CanUseToolFn
isAsync: boolean
availableTools: Tools
useExactTools?: boolean // Fork: 使用父 agent 完全相同的工具
forkContextMessages?: Message[] // Fork: 完整父对话上下文
worktreePath?: string // Worktree 隔离路径
model?: ModelAlias
maxTurns?: number
}): AsyncGenerator<Message, void> {
// ① 初始化
const agentId = createAgentId()
const { mergedClients, agentTools, cleanup } = await initializeAgentMcpServers(
agentDefinition, parentMcpClients
)
// ② 上下文构建
const systemPrompt = isFork
? params.forkParentSystemPrompt
: buildAgentSystemPrompt(agentDefinition, toolUseContext)
if (shouldOmitClaudeMd(agentDefinition)) {
userContext = { ...userContext, claudeMd: undefined }
}
// ③ 工具组装
const agentTools = useExactTools
? params.availableTools
: assembleToolPool(workerPermissionContext, mcpTools)
// ④ 预加载技能
for (const skillName of agentDefinition.skills ?? []) {
const resolved = resolveSkillName(skillName, commands)
if (resolved) {
skillMessages.push(createSkillMessage(resolved))
}
}
// ⑤ 主查询循环
try {
for await (const message of query({
messages: [...skillMessages, ...promptMessages],
systemPrompt,
canUseTool,
toolUseContext: agentToolUseContext,
maxTurns: maxTurns ?? agentDefinition.maxTurns,
})) {
yield message
await recordSidechainTranscript([message], agentId, lastUuid)
}
} finally {
// ⑥ 清理
cleanup()
clearSessionHooks()
clearFileStateCache()
clearTodosRegistry()
killBackgroundBashTasks()
}
}
5.2 系统提示构建
Fork 路径:直接传入父 agent 的 renderedSystemPrompt,确保字节级一致。
// 源码注释解释原因:
// re-calling getSystemPrompt() at fork-spawn time can diverge
// (GrowthBook cold→warm) and bust the cache
Normal 路径:
const systemPrompt = enhanceSystemPromptWithEnvDetails(
agentDefinition.getSystemPrompt({ toolUseContext }),
{ projectRoot, gitStatus, platform }
)
上下文优化:
- Explore / Plan agent:省略 CLAUDE.md(gate:
tengu_slim_subagent_claudemd,节省 5-15 Gtok/周) - Explore / Plan agent:省略 gitStatus(过时信息无价值)
六、同步执行路径
6.1 前台注册
const registration = registerAgentForeground({
agentId, description, prompt, selectedAgent,
autoBackgroundMs: getAutoBackgroundMs() // 默认 120 秒
})
// 返回:{ taskId, backgroundSignal, cancelAutoBackground }
6.2 消息竞赛循环
// 两个 Promise 竞赛:下一条消息 vs 后台化信号
const raceResult = await Promise.race([
agentIterator.next().then(r => ({ type: 'message', result: r })),
backgroundPromise // 用户点击「后台运行」或超时 120 秒
])
if (raceResult.type === 'message') {
// 正常处理消息,继续循环
agentMessages.push(raceResult.result.value)
} else {
// 动态后台化:前台 → 后台转换
backgroundAgentTask(foregroundTaskId)
registerAsyncAgent({ ... }) // 注册为异步任务
resumeAgentBackground({ ... }) // 从当前进度继续
wasBackgrounded = true // 跳过同步完成
}
6.3 后台提示 UI
2 秒后(PROGRESS_THRESHOLD_MS)显示后台化提示:
toolUseContext.setToolJSX({ jsx: <BackgroundHint /> })
6.4 同步完成
如果未被后台化:
const result = finalizeAgentTool(agentMessages, agentId, metadata)
// → { agentId, agentType, content, totalToolUseCount, totalDurationMs, totalTokens, usage }
return { data: { status: 'completed', ...result } }
七、异步执行路径
7.1 异步注册
const task = registerAsyncAgent({
agentId, description, prompt, selectedAgent,
setAppState: rootSetAppState,
toolUseId: toolUseContext.toolUseId,
})
创建 LocalAgentTaskState:
{
type: 'local_agent',
status: 'running',
agentId,
abortController, // 独立(不链接父,异步 agent 存活独立于父)
isBackgrounded: true,
pendingMessages: [], // 通过 SendMessage 排队的消息
retain: false, // 是否在 UI 中保留
evictAfter?: number, // 自动清理时间
}
7.2 异步生命周期
文件:src/tools/AgentTool/agentToolUtils.ts
export async function runAsyncAgentLifecycle({
taskId, abortController, makeStream, metadata,
description, toolUseContext, rootSetAppState,
enableSummarization, getWorktreeResult,
}) {
const tracker = createProgressTracker()
try {
// ① 消息流迭代
for await (const message of makeStream(onCacheSafeParams)) {
agentMessages.push(message)
// ② 实时进度更新
updateAsyncAgentProgress(taskId, getProgressUpdate(tracker), rootSetAppState)
// ③ SDK 进度事件
emitTaskProgress(tracker, taskId, toolUseId, description, startTime, lastToolName)
}
// ④ 结果定稿
const result = finalizeAgentTool(agentMessages, taskId, metadata)
// ⑤ 状态转换
completeAsyncAgent(result, rootSetAppState)
// ⑥ 危险操作分类(可选)
const warning = await classifyHandoffIfNeeded({ agentMessages, tools, ... })
// ⑦ Worktree 结果
const worktreeResult = await getWorktreeResult()
// ⑧ 通知用户
enqueueAgentNotification({
taskId, description, status: 'completed',
finalMessage, usage, worktreeResult,
})
} catch (error) {
if (error instanceof AbortError) {
killAsyncAgent(taskId, rootSetAppState)
enqueueAgentNotification({ status: 'killed', ... })
} else {
failAsyncAgent(taskId, error, rootSetAppState)
enqueueAgentNotification({ status: 'failed', ... })
}
} finally {
clearInvokedSkillsForAgent(agentId)
}
}
7.3 进度数据结构
type AgentToolProgress = {
toolUseCount: number
tokenCount: number
lastActivity?: ToolActivity // 最近的工具调用
recentActivities?: ToolActivity[] // 最近 N 个工具调用
summary?: string // 后台摘要
}
八、Fork Subagent — Prompt Cache 最大化
8.1 设计动机
传统 subagent 每次都重建系统提示 + 工具 schema。Fork 子 agent 复用父 agent 的完全相同的 API 请求前缀,实现 prompt cache 命中。
8.2 启用条件
export function isForkSubagentEnabled(): boolean {
if (feature('FORK_SUBAGENT')) {
if (isCoordinatorMode()) return false // 协调器模式不 fork
if (getIsNonInteractiveSession()) return false // 非交互模式不 fork
return true
}
return false
}
8.3 Fork 消息构建
export function buildForkedMessages(
directive: string,
assistantMessage: AssistantMessage,
): Message[] {
// ① 克隆父 agent 的助手消息(包含所有 tool_use 块)
const fullAssistantMessage = { ...assistantMessage, uuid: randomUUID() }
// ② 为每个 tool_use 创建占位 tool_result
const toolResultBlocks = toolUseBlocks.map(block => ({
type: 'tool_result',
tool_use_id: block.id,
content: [{ type: 'text', text: 'Fork started — processing in background' }],
}))
// ③ 返回:[完整助手消息, 用户消息(占位结果 + 指令)]
return [fullAssistantMessage, createUserMessage({
content: [...toolResultBlocks, { type: 'text', text: directive }],
})]
}
8.4 Fork 指令模板
<fork_boilerplate>
STOP. READ THIS FIRST.
You are a forked worker process. You ARE the fork. Do NOT spawn sub-agents;
execute directly.
Rules:
1. Execute the task directly using available tools
2. Do NOT spawn sub-agents or delegate
3. Work within your assigned scope only
4. Report results in the specified format
...
Output format:
Scope: ...
Result: ...
Key files: ...
Files changed: ...
Issues: ...
</fork_boilerplate>
<DIRECTIVE_PREFIX>your assigned scope
8.5 递归防护
export function isInForkChild(messages: Message[]): boolean {
// 扫描消息中是否存在 <fork_boilerplate> 标签
// 如果是 → 拒绝再次 fork(防止无限递归)
return messages.some(msg => msg.content?.includes('<fork_boilerplate>'))
}
8.6 Fork vs Normal 对比
| 维度 | Fork | Normal Agent |
|---|---|---|
subagent_type | 省略 | 必须指定 |
| 系统提示 | 父 agent 的(字节级相同) | agent 定义的 |
| 对话上下文 | 继承父完整对话 | 空白开始 |
| 工具集 | 父 agent 完全相同 | 过滤后子集 |
| Prompt cache | 命中父的 cache | 独立 cache |
| 执行方式 | 强制异步 | 可同步/异步 |
| Thinking 配置 | 继承父 | 默认禁用 |
九、Worktree 隔离
9.1 触发方式
if (effectiveIsolation === 'worktree') {
const slug = `agent-${earlyAgentId.slice(0, 8)}`
worktreeInfo = await createAgentWorktree(slug)
}
9.2 Worktree 创建流程
文件:src/utils/worktree.ts
9.3 Worktree 清理
async function cleanupWorktreeIfNeeded(worktreeInfo) {
if (await hasWorktreeChanges(worktreeInfo.worktreePath)) {
// 有改动 → 保留 worktree,返回路径和分支名
return { worktreePath, worktreeBranch }
} else {
// 无改动 → 删除 worktree 和分支
await removeWorktree(worktreeInfo.worktreePath)
return undefined
}
}
9.4 Fork + Worktree
Fork 子 agent 收到一条路径翻译通知:
export function buildWorktreeNotice(parentCwd, worktreeCwd): string {
return `You've inherited the conversation context above from a parent agent
working in ${parentCwd}. You are operating in an isolated git worktree at
${worktreeCwd} — same repository, same relative file structure, separate
working copy. Paths in the inherited context refer to the parent's working
directory; translate them to your worktree root. Re-read files before
editing if the parent may have modified them since they appear in the context.`
}
十、多 Agent 协同
10.1 Coordinator 模式
文件:src/coordinator/coordinatorMode.ts
当 CLAUDE_CODE_COORDINATOR_MODE 启用时,主 agent 变为协调器:
export function getCoordinatorSystemPrompt(): string {
return `You are Claude Code, an AI assistant that orchestrates software
engineering tasks across multiple workers.
## Your Tools
- Agent — Spawn a new worker
- SendMessage — Continue an existing worker
- TaskStop — Stop a running worker
## Workers
Workers execute tasks autonomously. Each worker has access to standard
tools but CANNOT spawn sub-workers. When you launch a worker, provide
a clear, self-contained prompt.`
}
协调器的工具集:
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
AGENT_TOOL_NAME,
SEND_MESSAGE_TOOL_NAME,
TASK_STOP_TOOL_NAME,
SYNTHETIC_OUTPUT_TOOL_NAME,
])
10.2 团队创建(TeamCreateTool)
文件:src/tools/TeamCreateTool/TeamCreateTool.ts
// 创建团队文件(共享可变状态)
const teamFile: TeamFile = {
name: finalTeamName,
description,
createdAt: Date.now(),
leadAgentId,
members: [{
agentId: leadAgentId,
name: TEAM_LEAD_NAME,
agentType: leadAgentType,
joinedAt: Date.now(),
cwd: getCwd(),
subscriptions: [],
}],
}
await writeTeamFileAsync(finalTeamName, teamFile)
10.3 Agent 间通信(SendMessageTool)
文件:src/tools/SendMessageTool/SendMessageTool.ts
邮箱模型:Agent 不直接调用对方,而是通过邮箱投递:
async function handleMessage(recipientName, content, summary, context) {
await writeToMailbox(recipientName, {
from: senderName,
text: content,
summary,
timestamp: new Date().toISOString(),
color: senderColor,
}, teamName)
return { data: { success: true, message: `Message sent to ${recipientName}'s inbox` } }
}
自动恢复:如果目标 agent 已停止,SendMessage 会自动恢复它:
if (task.status !== 'running') {
// 自动恢复停止的 agent
await resumeAgentBackground({
agentId,
prompt: input.message,
toolUseContext: context,
canUseTool,
})
return { data: { success: true, message: 'Agent resumed in the background' } }
}
十一、Agent 恢复机制
文件:src/tools/AgentTool/resumeAgent.ts
十二、MCP 服务器初始化
12.1 两类 MCP 客户端
type AgentMcpServerSpec =
| string // 引用:使用已有 mcp.json 中的服务器
| { [name: string]: McpServerConfig } // 内联:agent 专属服务器
// 引用型:共享父的连接,agent 退出时不清理
// 内联型:创建新连接,agent 退出时清理
12.2 初始化流程
async function initializeAgentMcpServers(agentDefinition, parentClients) {
const referencedClients = [] // 共享父连接
const inlineClients = [] // 新建连接
const cleanupFns = []
for (const spec of agentDefinition.mcpServers ?? []) {
if (typeof spec === 'string') {
// 引用型:从父 client 查找并复用
const existing = parentClients.find(c => c.name === spec)
if (existing) referencedClients.push(existing)
} else {
// 内联型:创建新连接
const client = await connectToServer(spec)
inlineClients.push(client)
cleanupFns.push(() => client.disconnect())
}
}
return {
mergedClients: [...parentClients, ...inlineClients],
agentTools: getToolsFromClients(inlineClients),
cleanup: () => cleanupFns.forEach(fn => fn()),
}
}
十三、完整生命周期图
十四、关键源码文件清单
十五、可借鉴的设计模式
| 模式 | Claude Code 做法 | 可借鉴场景 |
|---|---|---|
| Cache 复用 Fork | 子 agent 继承父的完整 prompt + 工具,字节级 cache 命中 | 需要频繁派生子任务的 agent 系统 |
| 三级工具收窄 | 全局禁止 → 自定义禁止 → 异步白名单 → agent 定义级 | 多角色、多权限的 agent 体系 |
| 上下文隔离克隆 | 可变状态克隆、不可变数据共享、回调 no-op | 父子 agent 间状态管理 |
| 动态前后台切换 | Promise.race(消息 vs 后台信号),运行时转换 | 长时间运行任务的用户体验 |
| 邮箱通信模型 | 不直接调用,通过邮箱投递 + 自动恢复 | 解耦的多 agent 通信 |
| Sidechain 转录 | 独立 JSONL 文件记录,支持恢复 | 可中断/恢复的后台任务 |
| Worktree 隔离 | git worktree + 符号链接大目录 + 自动清理 | 需要文件系统隔离的并行任务 |
| 递归防护 | isInForkChild() 扫描标签防止无限 fork | 任何允许自我派生的系统 |
| Markdown 定义 | YAML frontmatter + 正文作为 system prompt | 用户可自定义的 agent 配置 |
| 多来源合并 | built-in < user < project < policy + MCP 过滤 | 分层配置系统 |
文件来源:Claude Code npm 包 source map 泄露快照 (2026-03-31)
分析日期:2026-04-08