模块九:多 Agent 架构 | 前置依赖:第 25 课 | 预计学习时间:65 分钟
学习目标
完成本课后,你将能够:
- 区分两套 Task 系统(后台任务 TaskState vs 结构化 Todo 列表)及其各自的工具集
- 说明 TaskState 的 7 种类型、5 种状态及完整生命周期
- 解释 6 个 Task 工具(TaskCreate/Get/List/Update/Output/Stop)的职责与协作方式
- 理解 Agent 后台运行模式的进度追踪与通知机制
26.1 两套 Task 系统
Claude Code 存在两套"任务"系统,容易混淆:
┌──────────────────────────────────────────────────────────┐
│ Task 系统全景 │
├──────────────────────┬───────────────────────────────────┤
│ 后台任务 (TaskState) │ 结构化 Todo 列表 │
│ │ │
│ 定义: Task.ts │ 定义: utils/tasks.ts │
│ 存储: AppState.tasks│ 存储: 磁盘 JSON 文件 │
│ 工具: TaskOutput, │ 工具: TaskCreate, TaskGet, │
│ TaskStop │ TaskList, TaskUpdate │
│ │ │
│ 用途: 管理后台运行 │ 用途: 跟踪多步骤工作进度 │
│ 的 Shell/Agent/ │ 面向用户可见的任务清单 │
│ Remote 进程 │ │
│ │ │
│ 旧版: TodoWriteTool │ 新版: TaskCreate + TaskUpdate │
│ (全量替换模式) │ (增量操作模式) │
└──────────────────────┴───────────────────────────────────┘
为什么有两套?
- 后台任务(TaskState):管理进程级别的生命周期 — Shell 命令在后台运行、Agent 异步执行、远程 Agent 等。这些是真正的"计算任务"。
- 结构化 Todo:管理用户的工作清单 — "修复 Bug #123"、"添加测试"。这些是"工作项",不直接对应进程。
本课会分别覆盖两套系统,重点放在后台任务系统上。
26.2 后台任务系统 — Task.ts
TaskType — 7 种任务类型
type TaskType =
| 'local_bash' // 本地 Shell 命令
| 'local_agent' // 本地 Agent(AgentTool 的后台模式)
| 'remote_agent' // 远程 Agent(CCR 环境)
| 'in_process_teammate' // 进程内 Teammate
| 'local_workflow' // 本地工作流脚本
| 'monitor_mcp' // MCP 监控任务
| 'dream' // Dream 任务(实验性)
TaskStatus — 5 种状态
type TaskStatus =
| 'pending' // 已创建,等待运行
| 'running' // 正在运行
| 'completed' // 成功完成
| 'failed' // 执行失败
| 'killed' // 被手动终止
function isTerminalTaskStatus(status: TaskStatus): boolean {
return status === 'completed' || status === 'failed' || status === 'killed'
}
状态机
┌─────────┐
│ pending │────────────────┐
└────┬────┘ │
│ 开始执行 │ 启动前被取消
▼ ▼
┌─────────┐ ┌─────────┐
│ running │─────────▶│ killed │
└────┬────┘ 手动终止 └─────────┘
│
├── 成功 ──▶ ┌───────────┐
│ │ completed │
│ └───────────┘
│
└── 失败 ──▶ ┌─────────┐
│ failed │
└─────────┘
TaskStateBase — 通用字段
type TaskStateBase = {
id: string // 任务 ID(如 "a3kf9x2m")
type: TaskType // 任务类型
status: TaskStatus // 当前状态
description: string // 人类可读的描述
toolUseId?: string // 关联的 tool_use ID
startTime: number // 开始时间戳
endTime?: number // 结束时间戳
totalPausedMs?: number // 总暂停时间
outputFile: string // 输出文件路径
outputOffset: number // 输出读取偏移量
notified: boolean // 完成通知是否已发送
}
Task ID 生成
每种类型有不同的前缀,后跟 8 个随机字符:
const TASK_ID_PREFIXES: Record<string, string> = {
local_bash: 'b', // 如 "b3kf9x2m"
local_agent: 'a', // 如 "akm4np7q"
remote_agent: 'r', // 如 "rw2xt8jz"
in_process_teammate: 't', // 如 "t5hv3ynr"
local_workflow: 'w', // 如 "w9cd6fmb"
monitor_mcp: 'm', // 如 "mp8kq4sg"
dream: 'd', // 如 "dj7nh2wa"
}
// 使用 36 字符字母表(数字 + 小写字母),8 位 = 36^8 ≈ 2.8 万亿组合
const TASK_ID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
function generateTaskId(type: TaskType): string {
const prefix = getTaskIdPrefix(type)
const bytes = randomBytes(8)
let id = prefix
for (let i = 0; i < 8; i++) {
id += TASK_ID_ALPHABET[bytes[i]! % TASK_ID_ALPHABET.length]
}
return id
}
Task 接口
type Task = {
name: string
type: TaskType
kill(taskId: string, setAppState: SetAppState): Promise<void>
}
每种 TaskType 实现一个 Task 对象,由 tasks.ts 统一管理:
function getAllTasks(): Task[] {
const tasks: Task[] = [
LocalShellTask,
LocalAgentTask,
RemoteAgentTask,
DreamTask,
]
if (LocalWorkflowTask) tasks.push(LocalWorkflowTask) // feature-gated
if (MonitorMcpTask) tasks.push(MonitorMcpTask) // feature-gated
return tasks
}
function getTaskByType(type: TaskType): Task | undefined {
return getAllTasks().find(t => t.type === type)
}
26.3 TaskState 具体类型
tasks/types.ts 定义了具体的 TaskState 联合类型:
type TaskState =
| LocalShellTaskState // Shell 命令:含 command, result (exitCode)
| LocalAgentTaskState // 本地 Agent:含 prompt, result, agentId
| RemoteAgentTaskState // 远程 Agent:含 command, sessionUrl
| InProcessTeammateTaskState// 进程内队友:含 teammateId, agentName
| LocalWorkflowTaskState // 工作流脚本
| MonitorMcpTaskState // MCP 监控
| DreamTaskState // Dream
LocalAgentTaskState
这是 AgentTool 后台模式的核心状态:
LocalAgentTaskState extends TaskStateBase
├── prompt: string // Agent 的输入提示
├── agentId: AgentId // Agent 实例 ID
├── agentDefinition?: AgentDefinition
├── result?: AgentToolResult // 最终结果
├── error?: string // 错误信息
├── progress?: AgentProgress // 实时进度
│ ├── toolUseCount: number // 工具调用次数
│ ├── tokenCount: number // Token 使用量
│ ├── lastActivity?: ToolActivity
│ ├── recentActivities?: ToolActivity[]
│ └── summary?: string // AI 生成的进度摘要
├── isBackgrounded: boolean // 是否在后台
└── worktree?: WorktreeInfo // Worktree 隔离信息
后台任务判定
function isBackgroundTask(task: TaskState): task is BackgroundTaskState {
// 必须是运行中或等待中
if (task.status !== 'running' && task.status !== 'pending') return false
// 未被显式后台化的前台任务不算
if ('isBackgrounded' in task && task.isBackgrounded === false) return false
return true
}
26.4 Agent 进度追踪
LocalAgentTask.ts 实现了精细的进度追踪系统:
ProgressTracker
type ProgressTracker = {
toolUseCount: number // 工具调用次数
latestInputTokens: number // 最新输入 Token(累积值)
cumulativeOutputTokens: number // 累积输出 Token
recentActivities: ToolActivity[] // 最近 5 次工具活动
}
type ToolActivity = {
toolName: string
input: Record<string, unknown>
activityDescription?: string // 如 "Reading src/foo.ts"
isSearch?: boolean // Grep/Glob 等搜索操作
isRead?: boolean // Read/cat 等读取操作
}
进度更新流程
Agent 的每个 Assistant Message
│
▼
updateProgressFromMessage(tracker, message)
│
├── 更新 Token 计数
│ ├── latestInputTokens = input + cache_creation + cache_read
│ └── cumulativeOutputTokens += output_tokens
│
└── 遍历 content blocks
└── 对每个 tool_use block
├── toolUseCount++
├── 计算 activityDescription(通过 Tool.getActivityDescription)
├── 分类 isSearch/isRead
└── 加入 recentActivities(最多保留 5 条)
进度通知机制
Agent 运行中
│
├── 每次工具调用后 → updateAsyncAgentProgress()
│ └── 更新 AppState.tasks[id].progress
│
├── 定期(SDK 模式)→ emitTaskProgress()
│ └── 发送 SDK 事件
│
└── Agent 完成
├── completeAsyncAgent() → 状态变为 'completed'
│ └── enqueueAgentNotification()
│ └── 生成 <task-notification> XML 消息
│
└── failAsyncAgent() → 状态变为 'failed'
└── 同样发送通知
task-notification 消息格式
<task-notification>
<task_id>akm4np7q</task_id>
<tool_use_id>toolu_xxx</tool_use_id>
<status>completed</status>
<output_file>/path/to/output</output_file>
<summary>Found 3 security issues in auth module...</summary>
</task-notification>
26.5 停止任务(stopTask.ts)
stopTask.ts 提供统一的任务停止逻辑,被 TaskStopTool 和 SDK 控制请求共用:
async function stopTask(taskId: string, context: StopTaskContext): Promise<StopTaskResult> {
const task = appState.tasks?.[taskId]
// 验证
if (!task) throw new StopTaskError('No task found', 'not_found')
if (task.status !== 'running') throw new StopTaskError('Not running', 'not_running')
// 查找对应的 Task 实现
const taskImpl = getTaskByType(task.type)
if (!taskImpl) throw new StopTaskError('Unsupported type', 'unsupported_type')
// 执行 kill
await taskImpl.kill(taskId, setAppState)
// Shell 任务:静默 exit code 137 通知(噪音)
// Agent 任务:不静默(通知包含 partial result)
if (isLocalShellTask(task)) {
setAppState(prev => ({
...prev,
tasks: {
...prev.tasks,
[taskId]: { ...prev.tasks[taskId], notified: true },
},
}))
}
return { taskId, taskType: task.type, command }
}
26.6 结构化 Todo 列表 — 6 个 Task 工具
概览
| 工具 | 文件大小 | 职责 |
|---|---|---|
| TaskCreateTool | 3.4KB | 创建新任务 |
| TaskGetTool | 2.9KB | 按 ID 获取任务详情 |
| TaskListTool | 2.8KB | 列出所有任务 |
| TaskUpdateTool | 12KB | 更新任务状态/字段 |
| TaskOutputTool | 67KB | 读取后台任务输出(已标记 Deprecated) |
| TaskStopTool | 3.9KB | 停止后台运行的任务 |
TaskCreateTool
const inputSchema = z.strictObject({
subject: z.string(), // 任务标题
description: z.string(), // 任务描述
activeForm: z.string().optional(), // 进度条文案(如 "Running tests")
metadata: z.record(z.string(), z.unknown()).optional(),
})
// 调用逻辑
async call({ subject, description, activeForm, metadata }, context) {
// 1. 创建任务(写入磁盘 JSON)
const taskId = await createTask(getTaskListId(), {
subject, description, activeForm,
status: 'pending',
owner: undefined,
blocks: [], blockedBy: [],
metadata,
})
// 2. 运行 TaskCreated Hooks(可被 Hook 阻止)
for await (const result of executeTaskCreatedHooks(...)) {
if (result.blockingError) blockingErrors.push(...)
}
// 3. Hook 阻止时回滚
if (blockingErrors.length > 0) {
await deleteTask(getTaskListId(), taskId)
throw new Error(blockingErrors.join('\n'))
}
// 4. 自动展开任务列表 UI
context.setAppState(prev => ({ ...prev, expandedView: 'tasks' }))
return { data: { task: { id: taskId, subject } } }
}
TaskUpdateTool
最复杂的 Todo 工具,支持多种更新操作:
const inputSchema = z.strictObject({
taskId: z.string(),
subject: z.string().optional(),
description: z.string().optional(),
activeForm: z.string().optional(),
status: TaskUpdateStatusSchema.optional(), // 包含 'deleted' 特殊值
addBlocks: z.array(z.string()).optional(),
addBlockedBy: z.array(z.string()).optional(),
owner: z.string().optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
})
状态工作流:pending → in_progress → completed
特殊操作:
status: 'deleted'— 永久删除任务addBlockedBy— 设置任务依赖关系owner— 分配任务给特定 Agent
TaskGetTool / TaskListTool
只读工具,从磁盘读取任务数据:
// TaskListTool — 列出所有非内部任务
async call() {
const allTasks = (await listTasks(taskListId))
.filter(t => !t.metadata?._internal) // 排除内部任务
// 过滤已解决的 blockedBy(不显示已完成任务的阻塞)
const resolvedTaskIds = new Set(
allTasks.filter(t => t.status === 'completed').map(t => t.id),
)
const tasks = allTasks.map(task => ({
...task,
blockedBy: task.blockedBy.filter(id => !resolvedTaskIds.has(id)),
}))
return { data: { tasks } }
}
TaskOutputTool(已 Deprecated)
虽然标记为 Deprecated,但仍然可用。它负责读取后台任务的输出:
// 输入
z.strictObject({
task_id: z.string(),
block: z.boolean().default(true), // 是否阻塞等待完成
timeout: z.number().default(30000), // 最大等待时间(ms)
})
// 返回
type TaskOutputToolOutput = {
retrieval_status: 'success' | 'timeout' | 'not_ready'
task: TaskOutput | null
}
替代方案:推荐直接用 Read 工具读取任务的 outputFile 路径。
TaskStopTool
const inputSchema = z.strictObject({
task_id: z.string().optional(),
shell_id: z.string().optional(), // 兼容旧版 KillShell
})
// 别名
aliases: ['KillShell'] // 向后兼容
26.7 Todo 存储机制(utils/tasks.ts)
存储格式
Todo 列表存储在磁盘上的 JSON 文件中,按 session 或 team 隔离:
type Task = {
id: string
subject: string
description: string
activeForm?: string // 进度条文案
owner?: string // 所属 Agent
status: 'pending' | 'in_progress' | 'completed'
blocks: string[] // 此任务阻塞的任务 ID
blockedBy: string[] // 阻塞此任务的任务 ID
metadata?: Record<string, unknown>
}
Task List ID
每个会话或团队有独立的 Task 列表:
function getTaskListId(): string {
// 团队模式 → 团队名称
// 单人模式 → session ID
return leaderTeamName ?? getTeamName() ?? getSessionId()
}
并发安全
多 Agent 场景(如 swarm 中 10+ Agent 同时操作)使用文件锁保护:
// 锁选项:异步锁 + 退避重试
// 每个临界区约 50-100ms(readdir + N×readFile + writeFile)
// 预算支持 ~10+ 并发 swarm agent
const LOCK_OPTIONS = {
retries: { retries: 15, maxTimeout: 2000 },
}
更新通知
任务变更后通过 Signal 通知同进程的订阅者(用于 UI 即时刷新):
const tasksUpdated = createSignal()
export const onTasksUpdated = tasksUpdated.subscribe
export function notifyTasksUpdated(): void {
try { tasksUpdated.emit() } catch { /* 忽略 listener 错误 */ }
}
26.8 TodoWriteTool — 旧版全量替换
TodoWriteTool 是结构化 Todo 系统的前身,仍然存在但通过 feature gate 互斥:
isEnabled() {
return !isTodoV2Enabled() // 新版启用时旧版禁用
}
关键区别:
| 维度 | TodoWriteTool | TaskCreate + TaskUpdate |
|---|---|---|
| 操作模式 | 全量替换整个列表 | 增量创建/更新单个任务 |
| 存储 | AppState.todos | 磁盘 JSON 文件 |
| 并发 | 不安全(覆盖写入) | 文件锁保护 |
| 多 Agent | 不支持 | 支持 owner/team |
| 依赖关系 | 不支持 | blocks/blockedBy |
TodoWriteTool 的一个独特功能 — 验证提醒:
// 当所有任务标记完成时,检查是否有验证步骤
let verificationNudgeNeeded = false
if (feature('VERIFICATION_AGENT') && allDone && oldTodos.length >= 3) {
// 如果没有包含 "verification" 类型的任务,提醒需要验证
verificationNudgeNeeded = true
}
26.9 AgentTool 与 Task 系统的关系
当 AgentTool 以 run_in_background: true 调用时,它与 Task 系统深度集成:
AgentTool.call({ run_in_background: true })
│
├── 1. 生成 Task ID (type: 'local_agent')
│
├── 2. 注册到 AppState.tasks
│ └── registerAsyncAgent() → status: 'pending'
│
├── 3. 启动 Agent 运行(独立 Promise)
│ └── runAsyncAgentLifecycle()
│ ├── status → 'running'
│ ├── 定期更新 progress
│ └── 完成/失败时发送通知
│
├── 4. 立即返回 async_launched 给模型
│ └── { status: 'async_launched', agentId, outputFile }
│
└── 5. Agent 完成后
└── <task-notification> 作为 User 消息注入
→ 模型在下一轮看到完成通知
自动后台化
超过指定时间未完成的 Agent 可被自动后台化:
function getAutoBackgroundMs(): number {
if (isEnvTruthy(process.env.CLAUDE_AUTO_BACKGROUND_TASKS) ||
getFeatureValue_CACHED_MAY_BE_STALE('tengu_auto_background_agents', false)) {
return 120_000 // 2 分钟后自动后台化
}
return 0 // 禁用
}
课后练习
练习 1:Task 系统辨析
给定以下场景,判断使用哪套 Task 系统和哪些工具:
- 用户说 "同时运行测试和 lint"
- 用户说 "列出需要做的事情"
- 模型需要启动一个后台 Agent 做代码审查
- 用户想停止一个正在运行的后台 Shell
练习 2:状态转换追踪
画出一个后台 Agent 从创建到完成的完整状态转换图,标注每一步涉及的函数调用和 AppState 变更。
练习 3:并发场景分析
在 10 个 Agent 组成的 swarm 中,所有 Agent 同时更新同一个 Task 列表。分析文件锁机制如何保证数据一致性,以及在极端情况下(锁超时)会发生什么。
练习 4:进度追踪设计
设计一个改进的 AgentProgress 类型,增加以下功能:
- 预估完成时间
- 按工具类型分组的调用统计
- 错误/重试计数
本课小结
| 要点 | 内容 |
|---|---|
| 两套 Task 系统 | 后台任务(进程级 TaskState)vs 结构化 Todo(工作项 Task) |
| TaskState | 7 种 type × 5 种 status,ID 带类型前缀 |
| 后台 Agent | register → run → progress update → complete/fail → notification |
| 6 个 Task 工具 | Create/Get/List/Update(Todo 列表)+ Output/Stop(后台任务) |
| 进度追踪 | ProgressTracker 记录 tool use count / token / recent activities |
| 并发安全 | 文件锁 + 退避重试,支持 10+ 并发 Agent |
| TodoWriteTool | 旧版全量替换,通过 feature gate 与新版互斥 |
下一课预告
第 27 课:权限系统深度解析 — 理解 Claude Code 的多层权限模型:PermissionMode(plan/auto/default)、工具级权限分类、YOLO 模式的自动分类器、权限规则引擎以及 Hook 系统如何参与权限决策。