Claude Code 源码分析第 05 课 · 第 03
第五课

智能体系统

⏱ ~35 分钟 ⚡ 中级 📂 代理工具·发送消息工具·spawnMultiAgent

🗺 什么是代理系统?

Claude Code 的代理系统是一种机制,允许一个 Claude 实例将工作委托给其他 Claude 实例 - 每个实例都是一个单独的 LLM 轮次,具有自己的工具池、系统提示、模型和可选的文件系统隔离。 家长打电话 AgentTool (电线名称: Agent),这会产生一个孩子。 该子级本身可以生成更多子级,从而在运行时产生多级层次结构。

遗留名称是 Task. 来源将两个名字都注册通过 aliases: [LEGACY_AGENT_TOOL_NAME] 与现有权限规则、挂钩和恢复会话向后兼容。 所有新代码都使用 Agent.

下图显示了完整的运行时层次结构。 主循环位于顶部。 它可以访问 AgentTool,它分为三种代理类型和两种执行模式。

flowchart TD ML["🧠 Main Loop
parent REPL / SDK"] AT["🛠 AgentTool
tool: Agent / Task"] ML -->|"calls"| AT AT --> BI["🔷 Built-In Agents
source: 'built-in'"] AT --> CA["📝 Custom Agents
source: userSettings / projectSettings / policySettings"] AT --> PA["🧩 Plugin Agents
source: 'plugin'"] BI --> GP["general-purpose
tools: ['*']"] BI --> EX["Explore
read-only · haiku/inherit"] BI --> PL["Plan
read-only · inherit"] BI --> VF["verification
background: true"] CA --> MD["Markdown .md
.claude/agents/"] CA --> JS["JSON frontmatter
settings.json agents{}"] AT --> SY["🔄 Sync Lifecycle
blocks parent turn"] AT --> AS["⚡ Async Lifecycle
fire-and-forget · notified on complete"] AT --> FK["🔀 Fork Path
inherits parent context byte-exact"] AT --> TM["👥 Teammate / Swarm
tmux pane or in-process"] SY --> WT["🌲 Worktree Isolation
git worktree · separate branch"] AS --> WT style ML fill:#1a1816,stroke:#7d9ab8,color:#b8b0a4 style AT fill:#1a1816,stroke:#7d9ab8,color:#b8b0a4 style BI fill:#171513,stroke:#7d9ab8,color:#7d9ab8 style CA fill:#1d211b,stroke:#6e9468,color:#6e9468 style PA fill:#1f1b24,stroke:#8e82ad,color:#8e82ad style GP fill:#141211,stroke:#3a3632,color:#5c564f style EX fill:#141211,stroke:#3a3632,color:#5c564f style PL fill:#141211,stroke:#3a3632,color:#5c564f style VF fill:#241816,stroke:#c47a50,color:#c47a50 style MD fill:#141211,stroke:#3a3632,color:#5c564f style JS fill:#141211,stroke:#3a3632,color:#5c564f style SY fill:#141211,stroke:#3a3632,color:#5c564f style AS fill:#231f16,stroke:#b8965e,color:#b8965e style FK fill:#1f1b24,stroke:#8e82ad,color:#8e82ad style TM fill:#141211,stroke:#3a3632,color:#5c564f style WT fill:#141211,stroke:#3a3632,color:#5c564f

📦 三种代理类型

系统中的每个代理都是三种具体的 TypeScript 类型之一,都满足 AgentDefinition (受歧视的工会 source).

Built-In

BuiltInAgentDefinition

随 Claude Code 一起发货。 动态系统提示通过 getSystemPrompt({toolUseContext})。 不能被用户文件覆盖——但是 managed (策略)代理可以通过代理类型名称来隐藏它们。

Custom

CustomAgentDefinition

用户/项目/策略设置代理。 加载自 .claude/agents/*.md 或 JSON blob settings.json。 系统提示存储在 Markdown 主体的闭包中。

Plugin

PluginAgentDefinition

Bundled 带有插件(--plugin-dir)。 行为类似于自定义,但是 source === 'plugin'。 被视为 MCP 服务器策略的管理员信任 — 即使在以下情况下也可以加载 frontmatter MCP strictPluginOnlyCustomization 已设置。

显示:AgentDefinition 联合和类型保护 (loadAgentsDir.ts)
tools/AgentTool/loadAgentsDir.ts TypeScript
// Built-in agents — dynamic prompts only, no static systemPrompt field
export type BuiltInAgentDefinition = BaseAgentDefinition & {
  source: 'built-in'
  baseDir: 'built-in'
  getSystemPrompt: (params: { toolUseContext: Pick<ToolUseContext, 'options'> }) => string
}

// Custom agents from user/project/policy settings
export type CustomAgentDefinition = BaseAgentDefinition & {
  getSystemPrompt: () => string
  source: SettingSource
  filename?: string
  baseDir?: string
}

// Plugin agents — like Custom but source is 'plugin'
export type PluginAgentDefinition = BaseAgentDefinition & {
  getSystemPrompt: () => string
  source: 'plugin'
  plugin: string
}

export type AgentDefinition =
  | BuiltInAgentDefinition
  | CustomAgentDefinition
  | PluginAgentDefinition

// Type guards
export function isBuiltInAgent(agent: AgentDefinition): agent is BuiltInAgentDefinition {
  return agent.source === 'built-in'
}
export function isCustomAgent(agent: AgentDefinition): agent is CustomAgentDefinition {
  return agent.source !== 'built-in' && agent.source !== 'plugin'
}
export function isPluginAgent(agent: AgentDefinition): agent is PluginAgentDefinition {
  return agent.source === 'plugin'
}

优先/优先顺序

当两个代理共享相同的 agentType 字符串,优先级映射决定哪一个获胜。 订单来自 getActiveAgentsFromList() is:

内置→插件→用户设置→项目设置→标志设置→策略设置  — 后面的组会覆盖地图中前面的组,所以 policySettings (托管代理)具有最高的有效优先级。

🔷 内置代理深入研究

Agent agentType Model Tools Mode
通用型 general-purpose 默认子代理 ['*'] — 所有工具 同步/异步
Explore Explore 俳句(外部)/继承(蚂蚁) 只读; 不允许编辑、写入、文件编辑、代理 sync
Plan Plan inherit 与 Explore 相同的 disallowedTools sync
Verification verification inherit 没有编辑/写入; 允许临时 /tmp 脚本 背景:true(始终异步)
Fork fork inherit ['*'] with useExactTools (cache-identical) 实验门
StatuslineSetup statusline-setup default 有限的 shell 范围 sync
显示:探索代理系统提示(摘录)
tools/AgentTool/built-in/exploreAgent.ts TypeScript
export const EXPLORE_AGENT: BuiltInAgentDefinition = {
  agentType: 'Explore',
  // Ants get inherit; external users get haiku for speed
  model: process.env.USER_TYPE === 'ant' ? 'inherit' : 'haiku',
  disallowedTools: [
    AGENT_TOOL_NAME,          // no spawning sub-agents
    EXIT_PLAN_MODE_TOOL_NAME,
    FILE_EDIT_TOOL_NAME,
    FILE_WRITE_TOOL_NAME,
    NOTEBOOK_EDIT_TOOL_NAME,
  ],
  // Saves ~5-15 Gtok/week — Explore doesn't need commit/PR/lint rules
  omitClaudeMd: true,
  source: 'built-in',
  baseDir: 'built-in',
  getSystemPrompt: () => getExploreSystemPrompt(),
}
显示:自定义代理降价格式(.claude/agents/my-agent.md)
.claude/agents/my-agent.md Markdown + YAML 前端内容
---
name: my-agent
description: A focused TypeScript refactoring specialist.
model: sonnet
tools:
  - Read
  - Edit
  - Bash
  - Grep
  - Glob
permissionMode: acceptEdits
maxTurns: 50
memory: project
isolation: worktree
---

You are a TypeScript refactoring specialist. Your job is to improve
type safety and reduce any-casts in the provided code.

Rules:
- Only touch files you are explicitly asked about
- Run tsc --noEmit before and after to confirm zero new errors
- Commit changes with a clear message before reporting

同步与异步生命周期

当 AgentTool 的 call() 运行时,它计算一个布尔值: shouldRunAsync。 下游的一切都在该标志上分支。

tools/AgentTool/AgentTool.tsx TypeScript
const shouldRunAsync = (
  run_in_background === true   // explicit caller request
  || selectedAgent.background === true   // agent def forces background (e.g. verification)
  || isCoordinator                           // coordinator mode: always async
  || forceAsync                              // fork experiment: all spawns async
  || assistantForceAsync                     // KAIROS assistant mode
  || (proactiveModule?.isProactiveActive() ?? false)
) && !isBackgroundTasksDisabled

同步路径

步骤1
构建系统提示+提示信息
正常路径:代理路径 getSystemPrompt() + enhanceSystemPromptWithEnvDetails()。 分叉路径:父级已渲染的字节(提示缓存的字节精确)。
步骤2
可选:创建 git 工作树
If isolation === 'worktree', createAgentWorktree(slug) 之前被调用 runAgent()。 蛞蝓是 agent-{earlyAgentId.slice(0,8)}.
步骤3
await runAgent(params)
家长回合被阻止。 AgentTool 返回一个 status: 'completed' 结果与代理的最终文本。
步骤4
清理工作树(如果没有更改)
hasWorktreeChanges() 与预生成的 HEAD 提交不同。 如果没有更改,则立即删除工作树分支。

异步路径

步骤1
registerAsyncAgent() — 在 AppState 中注册的任务
返回一个 agentBackgroundTask 拥有自己的 AbortControllernot 链接到父控制器。 背景代理在 ESC 中存活。
步骤2
Return status: 'async_launched' immediately
呼叫者收到 agentId, outputFile, 和 canReadOutputFile 所以它可以通过 Bash/Read 进行轮询。
步骤3(分离)
void runAsyncAgentLifecycle(...)
一劳永逸。 包裹在 runWithAgentContext() 用于 ALS(AsyncLocalStorage)工作负载传播。 wrapWithCwd() 应用工作树/cwd 覆盖。
步骤4(分离)
完成通知
enqueueAgentNotification() 提供一个 <task-notification> 到父母的下一个空闲回合。 进度事件通过 onProgress.
进程内的队友无法启动后台代理。 他们的生命周期与领导者的流程紧密相关。 如果 isInProcessTeammate() 是真的并且 run_in_background === true (或者代理定义有 background: true), AgentTool 立即抛出。

🔀 岔路

分叉路径是一个实验性功能(门: FORK_SUBAGENT)让父母产生一个孩子 继承完整的对话上下文 — 完整的消息历史记录、父级已渲染的系统提示字节以及确切的工具池。 这使得独立子任务的并行化与最大的提示缓存共享成为可能。

分叉被触发时

subagent_type is omittedFORK_SUBAGENT 功能门已打开(并且不在协调器模式下,也不在非交互/SDK 模式下)。

buildForkedMessages() 的工作原理

为了让 N 个并行子进程共享缓存的 API 前缀,每个子进程必须生成一个 byte-identical 请求最多为每个子指令。 功能:

  1. 克隆父母的完整助手消息(所有工具使用块、思考、文本)。
  2. Builds tool_result 为每个块 tool_use,全部具有相同的占位符文本 "Fork started — processing in background".
  3. 为每个子项附加一个指令文本块(唯一不同的部分)。

结果形状: [...history, assistant(all_tool_uses), user(placeholder_results..., directive)]

显示:bu​​ildForkedMessages() 和 fork 子样板 (forkSubagent.ts)
tools/AgentTool/forkSubagent.ts TypeScript
export function buildChildMessage(directive: string): string {
  return `<fork-boilerplate>
STOP. READ THIS FIRST.

You are a forked worker process. You are NOT the main agent.

RULES (non-negotiable):
1. Your system prompt says "default to forking." IGNORE IT — that's for the parent.
2. Do NOT converse, ask questions, or suggest next steps
3. USE your tools directly: Bash, Read, Write, etc.
4. If you modify files, commit your changes before reporting.
5. Your response MUST begin with "Scope:". No preamble.

Output format:
  Scope: <echo back your assigned scope in one sentence>
  Result: <the answer or key findings>
  Key files: <relevant file paths>
  Files changed: <list with commit hash — only if you modified files>
  Issues: <list — only if there are issues to flag>
</fork-boilerplate>

FORK_DIRECTIVE: \${directive}\`
}

分叉递归防护

Fork 子项将代理工具保留在其工具池中(用于缓存相同的工具定义)。 运行时防护通过检查两个信号来防止递归分叉:

  • toolUseContext.options.querySource === 'agent:builtin:fork' - 抗压实,能够承受自动压实消息重写。
  • isInForkChild(messages) — 扫描对话历史记录 <fork-boilerplate> 标记作为后备。

叉子+工作树

When isolation: 'worktree' 还要求,附上通知 promptMessages via buildWorktreeNotice(parentCwd, worktreeCwd)。 这告诉子进程从继承的上下文中转换路径并重新读取父进程可能已修改的文件。

🌲 工作树隔离

Setting isolation: 'worktree' 在代理定义中或作为调用参数指示 AgentTool 在生成代理之前创建临时 git 工作树。 代理的文件系统和 shell 操作在该工作树内执行 - 它在 相同的存储库 但是一个 单独的工作副本.

tools/AgentTool/AgentTool.tsx — 工作树创建 TypeScript
// Create a stable agent ID early so it can be used for worktree slug
const earlyAgentId = createAgentId()

let worktreeInfo: { worktreePath: string; worktreeBranch?: string; headCommit?: string } | null = null
if (effectiveIsolation === 'worktree') {
  const slug = `agent-\${earlyAgentId.slice(0, 8)}`
  worktreeInfo = await createAgentWorktree(slug)
}

// After agent completes — cleanup if no changes
const cleanupWorktreeIfNeeded = async () => {
  if (!worktreeInfo) return {}
  const { worktreePath, worktreeBranch, headCommit } = worktreeInfo
  worktreeInfo = null  // idempotent guard
  if (headCommit) {
    const changed = await hasWorktreeChanges(worktreePath, headCommit)
    if (!changed) {
      await removeAgentWorktree(worktreePath, worktreeBranch, gitRoot)
      return {}
    }
  }
  // Changes detected — keep the worktree branch
  return { worktreePath, worktreeBranch }
}
清理很聪明: 如果代理没有进行 git-tracked 更改(通过比较来确定) headCommit),工作树会自动删除。 如果确实进行了更改,则保留该分支以供父级检查或合并。

Exception: 基于钩子的工作树是 always 保留是因为当通过钩子管理 Git 时,无法进行 VCS 更改检测。

📬 SendMessageTool 和 Swarm 协议

一旦代理作为队友运行(tmux 窗格或进程内),他们就需要进行通信。 SendMessageTool (电线名称: SendMessage) 是代理间消息传递原语。 仅当以下情况时才启用 isAgentSwarmsEnabled() 返回真。

消息路由逻辑

to消息类型Result
"teammate-name" string 写入队友的邮箱文件。 如果代理停止,则会从脚本中自动恢复。
"*" string 向所有团队成员广播(不包括发送者)。
任何名字 shutdown_request 发送结构化关闭请求。 收件人可以批准或拒绝。
"team-lead" shutdown_response 批准:触发器 gracefulShutdown(0)。 拒绝:继续工作。
任何名字 plan_approval_response 只有团队领导才能批准/拒绝计划。 传播 permissionMode.
"uds:<path>" string Unix 域套接字 — 跨会话发送到本地对等点。
"bridge:<session-id>" 仅字符串 远程控制:通过 Anthropic 服务器发布到另一个 Claude 实例。 需要明确的用户同意。
显示:spawnMultiAgent.ts 中的 SpawnTeammate(摘录)
tools/shared/spawnMultiAgent.ts TypeScript
// Build the full spawn command for a new pane
const teammateArgs = [
  `--agent-id \${quote([teammateId])}`,
  `--agent-name \${quote([sanitizedName])}`,
  `--team-name \${quote([teamName])}`,
  `--agent-color \${quote([teammateColor])}`,
  `--parent-session-id \${quote([getSessionId()])}`,
  plan_mode_required ? '--plan-mode-required' : '',
  agent_type ? `--agent-type \${quote([agent_type])}` : '',
].filter(Boolean).join(' ')

// Propagate permission mode, model, settings, plugin dirs
const inheritedFlags = buildInheritedCliFlags({ planModeRequired, permissionMode })

const spawnCommand = `cd \${quote([workingDir])} && env \${envStr} \${quote([binaryPath])} \${teammateArgs}\${flagsStr}`

// Send to the tmux pane (or swarm socket when outside tmux)
await sendCommandToPane(paneId, spawnCommand, !insideTmux)

📋 代理 Frontmatter 字段参考

展开完整的 frontmatter 架构
FieldTypeEffect
name字符串(必填)唯一标识符——用作 subagent_type value
description字符串(必填)向母法学硕士显示“何时使用”指导
modelsonnet|opus|haiku|inherit|<id>inherit → 在运行时使用父模型
toolsstring[]Allow-list. ['*'] = 所有工具。 省略=继承默认池。
disallowedToolsstring[]从池中减去 - 之后应用 tools allow-list
permissionModedefault|acceptEdits|bypassPermissions|auto|plan|bubble覆盖此代理工具调用的父模式
maxTurns正整数代理停止前代理轮次的硬上限
backgroundboolean始终运行异步,无论 run_in_background param
isolationworktree (| remote ant-only)Git 每个生成的工作树隔离
memoryuser|project|local所选范围内跨会话的持久内存
mcpServers字符串[] | 目的[]附加 MCP 服务器 — 在代理启动时连接,在完成时清理
hooksHooksSettings仅在代理运行时注册会话范围的挂钩
skillsstring[]技能斜线命令预加载到代理的上下文中
initialPromptstring放在第一个用户回合之前(斜杠命令有效)
effortlow|normal|high | 整数控制思维深度(扩展思维预算)
requiredMcpServersstring[]如果这些 MCP 服务器未经身份验证且没有工具,代理将隐藏

要点

  • 每个代理都是三种 TypeScript 类型之一,其区别在于 source:内置、自定义(userSettings/projectSettings/policySettings)或插件。 政策制定在优先权争议中获胜。
  • The shouldRunAsync 布尔值是单个分支点。 可以强制异步的六个条件:显式 run_in_background,代理定义的 background: true、协调者模式、分叉实验、KAIROS 模式或主动模式。
  • 异步代理有自己的 AbortController 与父母无关——它们在 ESC 中存活下来,并且只有通过显式的方式才能被杀死 chat:killAgents.
  • 分叉路径通过使用实现最大提示缓存共享 相同的占位符文本 对于每个 tool_result 块 - 每个子项只有最终指令文本不同。
  • 工作树隔离是智能清理的:如果代理没有进行 git 跟踪的更改,则分支将被删除。 如果确实如此,则会保留供家长查看。
  • omitClaudeMd: true 按 Anthropic 的使用规模,“探索和计划”每周可节省约 5-15 Gtok — 这是通过只读专业化实现的有意义的优化。
  • 验证代理是唯一内置的 background: true 硬编码 - 它总是异步运行并且总是以 VERDICT: PASS/FAIL/PARTIAL line.
  • SendMessageTool 支持四种消息类型(字符串、 shutdown_request, shutdown_response, plan_approval_response)和三个寻址方案:队友姓名、广播(*)、UDS 套接字和桥接会话。

🧪 Quiz

Q1. 当两个代理共享相同资源时,哪个代理源具有最高的有效优先级 agentType?
A built-in
B plugin
C projectSettings
D 策略设置(托管代理)
Correct. getActiveAgentsFromList() 按顺序处理组:内置→插件→用户设置→项目设置→标志设置→策略设置。 每个组都会覆盖映射中较早的条目,因此策略设置获胜。
Q2. 相同占位符文本的目的是什么 "Fork started — processing in background" 用于所有叉子吗?
A 它告诉孩子跳过工具执行
B 它使所有子进程生成字节相同的 API 请求前缀,以实现快速缓存共享
C 它作为状态消息显示给用户
D 它可以防止子代理生成子代理
正确的。 每一个 tool_result fork 前缀中的块使用相同的占位符,以便 API 请求前缀在所有 N 个子级中都是字节相同的。 每个子项只有最终的指令文本块不同 - 最大化缓存命中。
Q3. 进程中的队友通过以下方式调用 AgentTool run_in_background: true。 会发生什么?
A 代理运行异步,链接到父级的 AbortController
B AgentTool 立即抛出错误
C 代理运行同步作为后备
D 请求在队友的进程分离时排队等待
正确的。 进程内的队友无法生成后台代理,因为它们的生命周期与领导者的进程相关联。 AgentTool 抛出: “进程中的队友无法生成后台代理。”
Q4. 如果代理做了以下操作,工作树清理逻辑会做什么 no git 跟踪的更改?
A 它将工作树分支合并到主分支中
B 它立即删除工作树和分支
C 它保留工作树以供父级检查
D 它存储更改并关闭工作树
Correct. hasWorktreeChanges() 与生成前的差异 headCommit。 如果没有任何改变, removeAgentWorktree(path, branch, gitRoot) 被叫去清理。 如果存在更改,则保留分支。
Q5. 哪种内置代理类型始终具有 background: true 硬编码在其定义中?
A Explore
B Plan
C verification
D general-purpose
正确的。 这 VERIFICATION_AGENT 定义集 background: true。 它总是异步运行并且总是以 VERDICT: PASS/FAIL/PARTIAL 线。 探索和计划默认同步; 除非调用者选择加入,否则通用目的是同步。
0 / 5 已回答