最优雅的任务调度,不是框架,而是一个工具。
13.1 核心问题
单个 Agent 面对复杂任务时有两个瓶颈:
瓶颈 A:Token 预算限制
用户:帮我把 src/ 下的 200 个文件都做一遍安全审查
Agent:好的(读第 1 个文件……读第 2 个……)
[20 个文件后 context 满了]
Agent:抱歉,上下文超出限制,无法完成任务
一个 Agent 的上下文窗口(200K tokens)无法容纳大规模任务的所有中间状态。
瓶颈 B:串行速度
子任务 A:分析前端代码 (需要 30 秒)
子任务 B:分析后端代码 (需要 30 秒)
子任务 C:分析数据库层 (需要 30 秒)
串行执行:90 秒总耗时
并行执行:30 秒总耗时(父 Agent 同时派生 3 个子 Agent)
独立的子任务本可并行,但单 Agent 只能串行执行。
解决方案:Agent as Tool
把"启动一个新 Agent"本身包装成一个工具(Agent 工具),LLM 通过 tool_use 调用它就能派生子 Agent。父 Agent 发出多个 tool_use → 多个子 Agent 并行执行 → 通过 tool_result 返回结果。
13.2 原理讲解
13.2.1 任务类型体系
src/Task.ts 定义了 Claude Code 的任务分类:
type TaskType =
| 'local_bash' // Shell 命令(第 4 章的 BashTool)
| 'local_agent' // 本机子 Agent(主线)
| 'remote_agent' // 远端 CCR 云 Agent
| 'in_process_teammate' // 同进程团队成员(Agent Swarms)
| 'local_workflow' // Workflow 脚本任务
| 'monitor_mcp' // MCP 监控任务
| 'dream' // 后台思考任务(进阶特性)
type TaskStatus =
| 'pending' // 已创建,尚未启动
| 'running' // 正在执行
| 'completed' // 成功完成
| 'failed' // 执行失败
| 'killed' // 被中止
任务 ID 带类型前缀(b = bash,a = local_agent,r = remote_agent),便于在日志中区分:
b_x7k3m9p2 ← 一个 Bash 任务
a_q4w8v1z6 ← 一个本机 Agent 任务
13.2.2 Agent as Tool:AgentTool 的接口
AgentTool(工具名:Agent,旧名:Task)的输入 Schema:
const inputSchema = z.object({
description: z.string(), // 3-5 词任务描述(UI 展示用)
prompt: z.string(), // 子 Agent 的完整任务说明
subagent_type: z.string().optional(), // 指定使用哪种 Agent(Explore/Plan/...)
model: z.enum(['sonnet', 'opus', 'haiku']).optional(), // 覆盖默认模型
run_in_background: z.boolean().optional(), // true = 后台异步执行
isolation: z.enum(['worktree']).optional(), // 在独立 git worktree 中运行
})
当父 Agent 的 LLM 响应包含多个 AgentTool 的 tool_use,它们会被并行执行:
// 父 Agent 的 LLM 响应(简化)
{
"content": [
{"type": "tool_use", "name": "Agent", "input": {"description": "分析前端", "prompt": "..."}},
{"type": "tool_use", "name": "Agent", "input": {"description": "分析后端", "prompt": "..."}},
{"type": "tool_use", "name": "Agent", "input": {"description": "分析数据库", "prompt": "..."}}
]
}
三个 tool_use 在同一次响应中 → 三个子 Agent 并行启动 → 结果通过 tool_result 返回。
13.2.3 Agent 定义:AgentDefinition
自定义 Agent 可以通过 Markdown 文件定义(类似 Skills 的 frontmatter 格式),放在 .claude/agents/ 目录下:
---
description: 专门用于代码安全审查的 Agent
when_to_use: 当需要分析代码安全漏洞时
tools: Read, Grep, Bash
model: sonnet
permission_mode: readonly
---
你是一位专注于代码安全审查的专家 Agent。
对于给定的代码,你需要:
1. 检查常见的安全漏洞(SQL注入、XSS、CSRF等)
2. 评估认证和授权逻辑
3. 给出具体的修复建议
Claude Code 内置了几种专用 Agent 类型:
| Agent 类型 | 用途 | 特点 |
|---|---|---|
Explore |
快速代码探索,返回报告 | 一次性(One-Shot),不需要父 Agent 继续交互 |
Plan |
制定执行计划 | 一次性,只读工具,不执行修改 |
verification |
验证任务结果 | 在独立上下文中二次确认 |
| General Purpose | 通用子任务 | 有完整工具集 |
ONE_SHOT_BUILTIN_AGENT_TYPES(Explore/Plan)不需要 agentId 和 SendMessage 能力,省去了这些 header,节省约 135 字符的 token 消耗(× 每周 3400 万次调用 = 可观的节省)。
13.2.4 子 Agent 的完整生命周期
父 Agent 调用 AgentTool.call(input)
│
├─ 1. 生成子 agentId(UUID)
├─ 2. 构建子 Agent 上下文
│ ├─ 继承父 Agent 的 MCP 连接
│ ├─ 继承 / 覆盖权限模式(permissionMode)
│ └─ 构建子 Agent 的 system prompt(可覆盖父 Agent 的 CLAUDE.md)
│
├─ 3. 写入 metadata 到磁盘
│ └─ ~/.claude/projects/.../sessionId/subagents/<agentId>.meta.json
│
├─ 4. 启动 runAgent()(AsyncGenerator)
│ └─ 进入独立的 query() Agentic Loop
│ ├─ 子 Agent 调用工具(读文件、运行 Bash...)
│ └─ 子 Agent 可再次调用 AgentTool 派生孙 Agent(递归)
│
├─ 5. 子 Agent 完成时,将结果文本序列化为 tool_result
│
└─ 6. 父 Agent 收到 tool_result,继续自己的 Agentic Loop
子 Agent 的对话历史写入独立的 JSONL 文件:
~/.claude/projects/<project>/
└── <session-id>/
└── subagents/
├── <agent-id-1>.jsonl ← 子 Agent A 的完整对话记录
├── <agent-id-2>.jsonl ← 子 Agent B 的完整对话记录
└── <agent-id-3>.jsonl ← 子 Agent C 的完整对话记录
13.2.5 异步 Agent(后台执行)
设置 run_in_background: true 时,子 Agent 进入后台执行:
同步模式(默认):
父 Agent → 调用 AgentTool → 等待子 Agent 完成 → 收到结果 → 继续
异步模式(background):
父 Agent → 调用 AgentTool → 立即返回 task_id → 继续其他工作
│
└─ 子 Agent 在后台运行
完成后通过通知告知父 Agent
异步 Agent 有更严格的工具限制(ASYNC_AGENT_ALLOWED_TOOLS),不能使用会阻塞 UI 的工具(如需要用户确认的权限对话框)。
13.2.6 隔离模式:worktree isolation
设置 isolation: "worktree" 时,子 Agent 在独立的 git worktree 中运行:
# Claude Code 内部等效操作:
git worktree add /tmp/claude-agent-abc123 HEAD # 创建独立工作区
# 子 Agent 在 /tmp/claude-agent-abc123/ 中工作
# 主工作区的文件不受影响
git worktree remove /tmp/claude-agent-abc123 # 完成后清理
用途:让多个子 Agent 并行修改代码,而不会产生文件冲突。父 Agent 收集所有结果后,决定如何合并。
13.2.7 循环检测
子 Agent 也有 AgentTool,理论上可以无限递归:
父 Agent → 子 Agent A → 孙 Agent B → 曾孙 Agent C → ...
Claude Code 通过以下机制防止失控:
maxTurns:每个 Agent 的最大对话轮数(AgentDefinition 中可配置)filterDeniedAgents():权限层面阻止调用被拒绝的 Agent 类型- 异步 Agent 不能再派生新的异步 Agent(
isAsync && IN_PROCESS_TEAMMATE_ALLOWED_TOOLS限制)
13.3 源码索引
| 文件 | 关键函数/类型 | 作用 |
|---|---|---|
src/Task.ts |
TaskType、TaskStatus、generateTaskId() |
任务类型体系和 ID 生成 |
src/tasks/LocalAgentTask/LocalAgentTask.tsx |
LocalAgentTaskState、ProgressTracker |
本机 Agent 任务状态和进度追踪 |
src/tools/AgentTool/AgentTool.tsx |
inputSchema、AgentTool.call() |
AgentTool 工具定义和调用入口 |
src/tools/AgentTool/runAgent.ts |
runAgent() |
子 Agent 的完整运行逻辑(AsyncGenerator) |
src/tools/AgentTool/loadAgentsDir.ts |
AgentDefinition、getAgentDirAgents() |
Agent 定义加载(Markdown → 对象) |
src/tools/AgentTool/constants.ts |
AGENT_TOOL_NAME、ONE_SHOT_BUILTIN_AGENT_TYPES |
工具名称和一次性 Agent 类型集合 |
src/tools/AgentTool/agentToolUtils.ts |
filterToolsForAgent() |
按 Agent 类型过滤可用工具 |
src/tools/AgentTool/forkSubagent.ts |
buildForkedMessages() |
fork 模式下的上下文消息共享 |
13.4 最小化产出物
代码骨架位于
../chapters/13/src/,参考实现位于../chapters/13/solution/。 前置条件:需要ANTHROPIC_API_KEY环境变量(npm start需要)。
本章要实现什么
在 ../chapters/13/src/agent-tool.ts 中完成多 Agent 调度。
接口规范(已提供,不要修改):
export const MAX_AGENT_DEPTH = 3
export const MAX_AGENT_TURNS = 10
export const AGENT_TOOLS: Anthropic.Tool[] // 含 dispatch_agent、bash、read_file
export async function runSubAgent(client: Anthropic, task: AgentTask): Promise<string>
// 独立的 Agentic Loop,直到任务完成或超出轮数
export class MultiAgentCoordinator {
async chat(userInput: string): Promise<string>
// 支持 dispatch_agent 工具,并行派发子 Agent
}
你需要实现:
runSubAgent():独立 Agentic Loop,处理 dispatch_agent(递归)、bash、read_file 工具MultiAgentCoordinator.chat():父 Agent 循环,遇到 dispatch_agent 时调用 runSubAgent
关键约束:
- 深度 >= MAX_AGENT_DEPTH 时不能再派发子 Agent(过滤掉 dispatch_agent 工具)
- 工具执行失败不退出循环,以错误字符串作为 tool_result
验收
cd docs/chapters/13
npm install
npm test
卡住时查看 ../chapters/13/solution/agent-tool.ts。
src/main.ts
/**
* 第 13 章产出物:多 Agent 与任务调度
*
* 核心原理:
* - dispatch_agent 工具:父 Agent 通过 tool_use 调用,派生独立子 Agent
* - 子 Agent 有自己独立的 messages[],完整运行 Agentic Loop
* - 同一次父 LLM 响应中多个 dispatch_agent 调用 → 并行执行
* - 子 Agent 结果通过 tool_result 返回给父 Agent
* - 子 Agent 可递归调用 dispatch_agent(受深度限制)
*
* 验收:
* npx tsx src/main.ts
* 输入:"帮我分析一下当前目录的代码结构,包括:1) 列出所有文件 2) 统计行数"
* → 父 Agent 应派发 2 个子 Agent 分别处理两个子任务
*/
import Anthropic from '@anthropic-ai/sdk'
import * as fs from 'node:fs'
import * as path from 'node:path'
import * as child_process from 'node:child_process'
import * as readline from 'node:readline'
// ─────────────────────────────────────────────────────────────────────────────
// 类型定义
// ─────────────────────────────────────────────────────────────────────────────
interface ApiMessage {
role: 'user' | 'assistant'
content: string | ContentBlock[]
}
interface ContentBlock {
type: string
[key: string]: unknown
}
interface ToolUseBlock {
type: 'tool_use'
id: string
name: string
input: Record<string, unknown>
}
interface ToolResultBlock {
type: 'tool_result'
tool_use_id: string
content: string
}
// 对应 src/Task.ts 的 TaskStatus(简化版)
type AgentStatus = 'pending' | 'running' | 'completed' | 'failed'
interface AgentTask {
id: string
description: string
prompt: string
status: AgentStatus
result?: string
error?: string
depth: number // 递归深度,防止无限循环
}
// ─────────────────────────────────────────────────────────────────────────────
// 工具定义
// ─────────────────────────────────────────────────────────────────────────────
/** dispatch_agent 工具的输入类型 */
interface DispatchAgentInput {
description: string // 3-5 词任务描述
prompt: string // 子 Agent 的完整任务
}
/** bash 工具的输入类型 */
interface BashInput {
command: string
}
/** 工具定义(发给 Claude API 的 schema) */
const TOOLS: Anthropic.Tool[] = [
{
name: 'dispatch_agent',
description: [
'Launch a sub-agent to handle an independent subtask.',
'The sub-agent runs its own Agentic Loop with access to bash and file tools.',
'Use this when you can decompose a task into independent parts that can be done separately.',
'Multiple dispatch_agent calls in the same response run in PARALLEL.',
'Returns the sub-agent\'s final result as a string.',
].join('\n'),
input_schema: {
type: 'object',
properties: {
description: {
type: 'string',
description: 'A short (3-5 word) description of what this sub-agent will do',
},
prompt: {
type: 'string',
description: 'The complete task description for the sub-agent to perform',
},
},
required: ['description', 'prompt'],
},
},
{
name: 'bash',
description: 'Run a bash command and return its output. Use for file operations, searching, and system commands.',
input_schema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'The bash command to run',
},
},
required: ['command'],
},
},
{
name: 'read_file',
description: 'Read the contents of a file',
input_schema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Absolute or relative path to the file',
},
},
required: ['path'],
},
},
]
// ─────────────────────────────────────────────────────────────────────────────
// 工具执行(本地工具:bash / read_file)
// ─────────────────────────────────────────────────────────────────────────────
function executeBash(command: string): string {
try {
// 安全:只允许读取类操作的命令(不允许写入/删除/网络请求)
const FORBIDDEN_PATTERNS = [/\brm\b/, /\bmv\b/, /\bdd\b/, /curl/, /wget/, />\s*[^>]/, /\|\s*sh/, /eval/]
for (const pattern of FORBIDDEN_PATTERNS) {
if (pattern.test(command)) {
return `[安全拦截] 不允许执行此命令:${command}`
}
}
const result = child_process.execSync(command, {
timeout: 10_000,
maxBuffer: 1024 * 1024, // 1MB
encoding: 'utf-8',
})
return result || '(命令执行成功,无输出)'
} catch (err) {
const error = err as { message?: string; stderr?: string | Buffer }
const stderr = error.stderr ? error.stderr.toString() : ''
return `命令执行失败:${error.message}\n${stderr}`.trim()
}
}
function executeReadFile(filePath: string): string {
try {
const resolved = path.resolve(filePath)
const content = fs.readFileSync(resolved, 'utf-8')
// 限制返回大小,防止大文件撑爆 context
if (content.length > 10_000) {
return content.slice(0, 10_000) + '\n[...文件过长,已截断]'
}
return content
} catch (err) {
return `读取文件失败:${(err as Error).message}`
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 子 Agent 运行器(对应 runAgent.ts)
// ─────────────────────────────────────────────────────────────────────────────
const MAX_AGENT_DEPTH = 3 // 最大递归深度
const MAX_AGENT_TURNS = 10 // 每个 Agent 最多对话轮数
/**
* 运行一个子 Agent:独立的 Agentic Loop,直到任务完成或超出轮数限制
* 对应 Claude Code 中的 runAgent() AsyncGenerator
*/
async function runSubAgent(
client: Anthropic,
task: AgentTask,
): Promise<string> {
const prefix = ' '.repeat(task.depth) // 缩进显示 Agent 层级
console.log(`${prefix}[子 Agent ${task.id}] 开始:${task.description}`)
const messages: ApiMessage[] = [
{
role: 'user',
content: task.prompt,
},
]
let turns = 0
while (turns < MAX_AGENT_TURNS) {
turns++
const response = await client.messages.create({
model: 'claude-opus-4-5',
max_tokens: 4096,
system: [
'你是一个专注于单一子任务的 Agent。',
'当任务完成时,直接返回结果文本,不要调用任何工具。',
'保持简洁,直接给出结果,无需解释过程。',
`当前递归深度:${task.depth}/${MAX_AGENT_DEPTH}(${task.depth >= MAX_AGENT_DEPTH - 1 ? '不要再派发子 Agent' : '必要时可派发子 Agent'})`,
].join('\n'),
tools: task.depth < MAX_AGENT_DEPTH ? TOOLS : TOOLS.filter(t => t.name !== 'dispatch_agent'),
messages: messages as Anthropic.MessageParam[],
})
const assistantContent = response.content
// 收集本轮的 tool_use 调用
const toolUses = assistantContent.filter(
(b): b is Anthropic.ToolUseBlock => b.type === 'tool_use'
)
// 无工具调用 = Agent 认为任务完成,返回最终文本
if (toolUses.length === 0 || response.stop_reason === 'end_turn') {
const finalText = assistantContent
.filter(b => b.type === 'text')
.map(b => (b as Anthropic.TextBlock).text)
.join('')
console.log(`${prefix}[子 Agent ${task.id}] 完成(${turns} 轮)`)
return finalText || '(子 Agent 未返回文本结果)'
}
// 有工具调用:添加 assistant 消息,处理工具
messages.push({
role: 'assistant',
content: assistantContent as ContentBlock[],
})
// 并行执行所有工具调用(对应 Claude Code 的并行 tool execution)
const toolResults = await Promise.all(
toolUses.map(async (toolUse): Promise<ToolResultBlock> => {
const input = toolUse.input as Record<string, unknown>
let result: string
console.log(`${prefix} [工具] ${toolUse.name}(${JSON.stringify(input).slice(0, 80)})`)
if (toolUse.name === 'dispatch_agent') {
const dispatchInput = input as unknown as DispatchAgentInput
if (task.depth >= MAX_AGENT_DEPTH) {
result = '[错误] 达到最大递归深度,无法再派发子 Agent'
} else {
// 递归派发子 Agent
const childTask: AgentTask = {
id: `${task.id}.${toolUses.indexOf(toolUse) + 1}`,
description: dispatchInput.description,
prompt: dispatchInput.prompt,
status: 'running',
depth: task.depth + 1,
}
try {
result = await runSubAgent(client, childTask)
} catch (err) {
result = `子 Agent 执行失败:${(err as Error).message}`
}
}
} else if (toolUse.name === 'bash') {
result = executeBash((input as unknown as BashInput).command)
} else if (toolUse.name === 'read_file') {
result = executeReadFile(String(input.path ?? ''))
} else {
result = `未知工具:${toolUse.name}`
}
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: result,
}
})
)
// 将工具结果添加到消息历史
messages.push({
role: 'user',
content: toolResults as ContentBlock[],
})
}
return `(达到最大轮数 ${MAX_AGENT_TURNS},子 Agent 未能完成任务)`
}
// ─────────────────────────────────────────────────────────────────────────────
// 父 Agent(主循环)
// ─────────────────────────────────────────────────────────────────────────────
class MultiAgentCoordinator {
private client: Anthropic
private messages: ApiMessage[] = []
constructor() {
if (!process.env.ANTHROPIC_API_KEY) {
throw new Error('请设置 ANTHROPIC_API_KEY 环境变量')
}
this.client = new Anthropic()
}
async chat(userInput: string): Promise<string> {
this.messages.push({ role: 'user', content: userInput })
let turns = 0
const MAX_TURNS = 20
while (turns < MAX_TURNS) {
turns++
const response = await this.client.messages.create({
model: 'claude-opus-4-5',
max_tokens: 4096,
system: [
'你是一个多 Agent 协调者。',
'对于可以分解为独立子任务的复杂请求,使用 dispatch_agent 工具并行派发子 Agent。',
'例如:分析多个文件时,每个文件派发一个子 Agent;执行相互独立的操作时,同时派发多个 Agent。',
'每个子 Agent 会独立完成任务并返回结果,你再汇总这些结果给用户。',
'对于简单的单步任务,直接使用 bash 或 read_file 工具。',
].join('\n'),
tools: TOOLS,
messages: this.messages as Anthropic.MessageParam[],
})
const content = response.content
// 无工具调用 = 任务完成
const toolUses = content.filter(
(b): b is Anthropic.ToolUseBlock => b.type === 'tool_use'
)
if (toolUses.length === 0 || response.stop_reason === 'end_turn') {
const finalText = content
.filter(b => b.type === 'text')
.map(b => (b as Anthropic.TextBlock).text)
.join('')
this.messages.push({ role: 'assistant', content: finalText })
return finalText
}
// 添加 assistant 消息(含工具调用)
this.messages.push({
role: 'assistant',
content: content as ContentBlock[],
})
// 并行执行所有工具(包括 dispatch_agent)
console.log(`\n[父 Agent] 并行执行 ${toolUses.length} 个工具调用…`)
const toolResults = await Promise.all(
toolUses.map(async (toolUse): Promise<ToolResultBlock> => {
const input = toolUse.input as Record<string, unknown>
let result: string
if (toolUse.name === 'dispatch_agent') {
const dispatchInput = input as unknown as DispatchAgentInput
const taskId = `root.${toolUses.indexOf(toolUse) + 1}`
const task: AgentTask = {
id: taskId,
description: dispatchInput.description,
prompt: dispatchInput.prompt,
status: 'running',
depth: 1,
}
try {
result = await runSubAgent(this.client, task)
} catch (err) {
result = `子 Agent 失败:${(err as Error).message}`
}
} else if (toolUse.name === 'bash') {
result = executeBash((input as unknown as BashInput).command)
} else if (toolUse.name === 'read_file') {
result = executeReadFile(String(input.path ?? ''))
} else {
result = `未知工具:${toolUse.name}`
}
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: result,
}
})
)
// 将工具结果添加到消息历史
this.messages.push({
role: 'user',
content: toolResults as ContentBlock[],
})
}
return '(达到最大轮数,任务未能完成)'
}
}
// ─────────────────────────────────────────────────────────────────────────────
// CLI 入口
// ─────────────────────────────────────────────────────────────────────────────
async function main(): Promise<void> {
const coordinator = new MultiAgentCoordinator()
console.log('\nClaude 多 Agent 协调者')
console.log(`配置:最大子 Agent 深度=${MAX_AGENT_DEPTH},每 Agent 最多 ${MAX_AGENT_TURNS} 轮\n`)
console.log('示例任务(可以试试):')
console.log(' "列出当前目录下的文件,并统计每种扩展名的数量"')
console.log(' "找出当前目录所有 .json 文件,并显示各自的第一行"\n')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
const ask = (prompt: string): Promise<string> =>
new Promise(resolve => rl.question(prompt, resolve))
while (true) {
const input = await ask('你: ')
if (!input.trim()) continue
if (input.trim() === '/exit') {
console.log('\n再见!')
process.exit(0)
}
console.log('\nClaude: (思考中…)')
try {
const result = await coordinator.chat(input)
console.log('\n' + result + '\n')
} catch (err) {
console.error(`\n错误:${err instanceof Error ? err.message : String(err)}\n`)
}
}
}
main().catch(err => {
console.error(err)
process.exit(1)
})
13.5 本章小结
核心机制回顾
Agent as Tool:把"启动子 Agent"包装成普通工具,让 LLM 通过 tool_use 自然地派发子任务。这是 Claude Code 最关键的设计决策之一——没有专门的任务调度框架,只是一个工具。
并行来自多 tool_use:LLM 在同一响应中发出多个 dispatch_agent 调用 → Agent 框架并行执行它们 → 通过 tool_result 聚合结果。并行不是显式编排的,而是 LLM 自然选择的。
独立上下文:每个子 Agent 有自己独立的 messages[] 数组和 JSONL 文件,不共享父 Agent 的对话历史。父子 Agent 只通过 tool_use 输入和 tool_result 输出通信。
递归控制:maxTurns(每 Agent 对话轮数上限)+ 深度限制(MAX_AGENT_DEPTH)防止无限递归。Claude Code 还通过工具过滤(异步 Agent 不能调用 dispatch_agent)进行额外约束。
Claude Code vs 本章产出物的差异
| 特性 | Claude Code | 本章产出物 |
|---|---|---|
| Agent 类型系统 | 5 种 TaskType(含 remote/in_process/workflow/dream) | 仅 local_agent |
| 工具过滤 | 按 Agent 类型/权限模式精细过滤 | 仅深度限制 |
| 异步执行 | run_in_background: true + 通知系统 |
全部同步 |
| 进度追踪 | ProgressTracker(工具调用数 + token 数 + 最近活动) |
仅打印日志 |
| 上下文隔离 | worktree 隔离(独立 git 工作区) | 无隔离 |
| 元数据持久化 | writeAgentMetadata() + JSONL 子目录 |
无持久化 |
| 子 Agent 恢复 | --resume 可恢复中断的子 Agent |
不支持 |
架构演进
第 7 章 Agentic Loop ──────────────────────────────┐
│ 子 Agent = 独立的 Agentic Loop
第 13 章 多 Agent 调度 ◄──────────────────────────── ┘
├── dispatch_agent 工具(启动子 Agent)
├── 父子间通信:tool_use → tool_result
├── 并行执行:同一响应多个 tool_use
└── 递归控制:深度限制 + maxTurns
验收命令
cd chapters/13
npm install
npx tsx src/main.ts
# 验证点一:父 Agent 派发子 Agent
# 输入:"列出当前目录的文件,并统计行数最多的文件"
# 预期:父 Agent 派发 2 个子 Agent(一个列文件,一个统计行数)
# 验证点二:并行执行
# 输入:"分别找出 package.json 和 README.md 的内容摘要"
# 预期:父 Agent 同时派发 2 个子 Agent,两者并行运行(观察日志顺序)
# 验证点三:递归限制
# 要求子 Agent 再派发子 Agent 时,深度应被限制在 MAX_AGENT_DEPTH 以内