在 Claude Code 的工具体系中,如果说 AgentTool 是"子 Agent 调度器",那么 Task 系列工具就是整个任务系统的"仪表盘与遥控器"。它们不直接创建子 Agent 或执行 Shell 命令,而是提供了一套完整的 CRUD(创建、读取、更新、删除/停止)接口,让 LLM 能够主动管理后台运行的任务集合。
这六个工具——TaskCreateTool、TaskGetTool、TaskUpdateTool、TaskListTool、TaskStopTool、TaskOutputTool——共同构成了 Claude Code 的任务生命周期管理协议。无论是后台编译、异步测试、远程 Agent 执行,还是本地子 Agent 的并行运算,都需要经过这套协议进行注册、追踪、控制和回收。
本文将从源码层面解析这六个工具的设计哲学、Schema 定义、状态机和集成机制。
一、Task 系统概览:六个工具的分工与协作
1.1 工具职能矩阵
Task 系列工具的设计理念非常清晰:将任务的生命周期操作原子化,每个工具只负责一个明确的操作维度。这种设计使得 LLM 可以根据实际需要精确调用,避免冗余操作。
| 工具名称 | 核心职能 | 读写属性 | 典型使用场景 |
|---|---|---|---|
TaskCreateTool | 创建新任务 | 写 | LLM 决定启动一个后台进程或子 Agent |
TaskGetTool | 获取单个任务详情 | 读 | 查询特定任务的状态和元数据 |
TaskUpdateTool | 更新任务状态或描述 | 写 | 标记任务优先级、修改描述信息 |
TaskListTool | 列出所有任务 | 读 | 获取当前会话中的任务全景 |
TaskStopTool | 停止运行中的任务 | 写 | 终止不再需要或失控的后台任务 |
TaskOutputTool | 读取任务输出 | 读 | 获取已完成任务的 stdout / 结果 |
这六个工具的编排遵循了一个经典的生命周期管理范式:创建(Create)→ 监控(Get / List)→ 更新(Update)→ 终止(Stop)→ 收割(Output)。
1.2 与 AgentTool 的关系
Task 系列工具与 AgentTool 之间是**"管理层"与"执行层"**的关系:
AgentTool负责创建具体的执行实体——子 Agent(本地或远程)。当run_in_background: true时,AgentTool会调用registerAsyncAgent()将子 Agent 注册到任务系统中。- Task 系列工具 负责管理这些已注册的执行实体。它们不创建新的 Agent 进程,而是对已有任务进行查询、控制和结果读取。
flowchart TD
A[AgentTool
执行层] -->|registerAsyncAgent| B[Task系统
管理层]
A -->|registerRemoteAgentTask| B
C[BashTool
执行层] -->|spawnBackgroundTask| B
B --> D[TaskCreateTool]
B --> E[TaskListTool]
B --> F[TaskGetTool]
B --> G[TaskUpdateTool]
B --> H[TaskStopTool]
B --> I[TaskOutputTool]
D -.->|创建任务| J[任务状态机]
E -.->|查询| J
F -.->|查询| J
G -.->|修改| J
H -.->|终止| J
I -.->|读取输出| K[磁盘输出文件]
J -.->|完成后| K
style A fill:#4a90d9,color:#fff
style C fill:#4a90d9,color:#fff
style B fill:#e67e22,color:#fff这种分层架构的一个关键优势是解耦。AgentTool 的实现在 AgentTool.tsx 中,而任务管理的逻辑分散在 src/tasks/ 目录下的各个模块中。Task 系列工具作为中间层,让执行工具无需关心任务如何被追踪和展示,也让任务系统无需了解 Agent 或 Shell 的具体实现细节。
1.3 任务系统的整体架构
Claude Code 的任务系统位于 src/tasks/ 目录下,核心模块包括:
src/tasks/
├── types.ts # TaskState 联合类型与类型守卫
├── LocalShellTask/ # 本地后台 Shell 任务
├── LocalAgentTask/ # 本地子 Agent 任务
├── RemoteAgentTask/ # 远程 Agent 任务
├── InProcessTeammateTask/ # 同进程队友 Agent
├── DreamTask/ # 后台"思考"任务
└── LocalMainSessionTask.ts # 主会话自身作为任务所有任务对象都遵循统一的 TaskState 接口规范,存储在 AppState 的 tasks 字典中:
// src/tasks/types.ts:1-15
export type TaskState =
| LocalShellTaskState
| LocalAgentTaskState
| RemoteAgentTaskState
| InProcessTeammateTaskState
| LocalWorkflowTaskState
| MonitorMcpTaskState
| DreamTaskStateAppState 中的任务存储结构是一个以 task_id 为键的字典:
// AppState 中的任务存储(示意)
tasks?: Record<string, TaskState>;这使得 TaskGetTool 和 TaskListTool 可以通过简单的字典查找实现 O(1) 的单任务查询和 O(n) 的全量遍历。
二、各工具详解
2.1 TaskCreateTool:任务的入口
TaskCreateTool 是 Task 系列中唯一一个创建型工具。与其他工具不同,它通常不是由 LLM 直接调用的,而是作为内部基础设施被 AgentTool 和 BashTool 在后台模式下间接使用。
输入 Schema
TaskCreateTool 的输入参数需要指定任务的基本元数据:
// TaskCreateTool.tsx 输入 Schema(基于架构文档还原)
const inputSchema = z.strictObject({
task_type: z.enum(['local_bash', 'local_agent', 'remote_agent'])
.describe('The type of task to create'),
description: z.string()
.describe('Short description of the task (3-5 words)'),
command: z.string().optional()
.describe('Command or prompt for the task'),
run_in_background: z.boolean().default(true)
.describe('Whether to run this task in the background')
});核心执行逻辑
当 TaskCreateTool 被调用时,它会执行以下操作:
- 生成唯一 Task ID:使用
createTaskId()生成全局唯一的任务标识符。 - 创建任务状态对象:根据
task_type初始化对应的TaskState子类型,状态设为pending。 - 写入 AppState:将任务对象注册到
appState.tasks字典中。 - 触发执行器:如果是
local_bash类型,委托给LocalShellTask执行器;如果是local_agent,委托给LocalAgentTask执行器;如果是remote_agent,委托给RemoteAgentTask执行器。
sequenceDiagram
participant LLM as LLM/主Agent
participant TC as TaskCreateTool
participant AS as AppState
participant TE as TaskExecutor
LLM->>TC: TaskCreate({task_type, description, command})
TC->>TC: 生成 task_id
TC->>TC: 初始化 TaskState(status: pending)
TC->>AS: 注册 tasks[task_id]
TC-->>LLM: {task_id, status: pending}
alt local_bash
TC->>TE: LocalShellTask.spawn()
else local_agent
TC->>TE: LocalAgentTask.spawn()
else remote_agent
TC->>TE: RemoteAgentTask.spawn()
end
TE->>AS: 更新 status: running值得注意的是,TaskCreateTool 在正常的 Claude Code 交互流程中很少被 LLM 直接调用。更常见的路径是:
AgentTool.call()在run_in_background: true时内部调用registerAsyncAgent(),后者隐式创建LocalAgentTask。BashTool.call()在run_in_background: true时内部调用spawnBackgroundTask(),后者隐式创建LocalShellTask。
TaskCreateTool 的存在主要是为 MCP 扩展 和 程序化 API 提供显式的任务创建能力。
2.2 TaskGetTool:精准查询单个任务
TaskGetTool 是一个只读工具,用于根据 task_id 获取特定任务的完整状态信息。
输入 Schema
// TaskGetTool.tsx 输入 Schema(基于架构文档还原)
const inputSchema = z.strictObject({
task_id: z.string()
.describe('The unique identifier of the task to retrieve')
});输出结构
TaskGetTool 返回的是对应 TaskState 的序列化表示,核心字段包括:
// TaskGetTool 输出类型(示意)
type TaskGetOutput = {
task_id: string;
task_type: TaskType;
status: 'pending' | 'running' | 'completed' | 'failed' | 'stopped';
description: string;
created_at: number;
updated_at: number;
// 类型特定字段
exitCode?: number | null;
error?: string;
prompt?: string; // Agent 任务特有
result?: string; // Agent 任务特有
};TaskGetTool 的实现极为简洁——它本质上就是一次字典查找:
// TaskGetTool.tsx 核心逻辑(基于架构还原)
export const TaskGetTool = buildTool({
name: TASK_GET_TOOL_NAME,
searchHint: 'get details of a specific task',
isReadOnly: () => true,
async call({ task_id }, { getAppState }) {
const state = getAppState();
const task = state.tasks?.[task_id];
if (!task) {
return { status: 'not_found', task: null };
}
return {
status: 'success',
task: serializeTaskState(task)
};
}
});这种简洁性体现了 Claude Code 设计中的一个重要原则:状态集中化。所有任务状态都存储在单一的 AppState 树中,任何工具都可以通过 getAppState() 获取一致的状态视图,无需跨进程通信或数据库查询。
2.3 TaskUpdateTool:动态修改任务元数据
TaskUpdateTool 允许 LLM 修改已存在任务的元数据。这在多 Agent 协作场景中尤为重要——例如,一个协调者 Agent 可能需要标记某个子任务为高优先级,或更新其描述以反映最新的执行目标。
输入 Schema
// TaskUpdateTool.tsx 输入 Schema(基于架构文档还原)
const inputSchema = z.strictObject({
task_id: z.string()
.describe('The task ID to update'),
description: z.string().optional()
.describe('New description for the task'),
status: z.enum(['pending', 'running', 'completed', 'failed', 'stopped']).optional()
.describe('New status (use with caution)')
});TaskUpdateTool 的实现依赖于 updateTaskState 工具函数:
// src/utils/task/framework.ts(基于架构还原)
export function updateTaskState(
taskId: string,
updates: Partial<TaskState>,
setAppState: SetAppStateFn
): void {
setAppState(prev => {
const task = prev.tasks?.[taskId];
if (!task) return prev;
return {
...prev,
tasks: {
...prev.tasks,
[taskId]: { ...task, ...updates, updated_at: Date.now() }
}
};
});
}这里使用了不可变更新模式——通过展开运算符创建新的状态对象,确保 React 的渲染系统能够正确检测到状态变化。TaskUpdateTool 也会调用此函数来完成实际的更新操作。
2.4 TaskListTool:任务全景扫描
TaskListTool 是一个只读工具,返回当前会话中所有任务的概览列表。它是 LLM 进行任务调度决策的信息基础——在决定创建新任务之前,LLM 通常会先调用 TaskListTool 了解当前有哪些任务正在运行。
输入 Schema
// TaskListTool.tsx 输入 Schema(基于架构还原)
const inputSchema = z.strictObject({
filter: z.enum(['all', 'running', 'pending', 'completed', 'failed']).optional()
.describe('Filter tasks by status')
});输出与背景任务判定
TaskListTool 返回的任务列表会经过 isBackgroundTask() 函数的筛选。这个函数定义在 src/tasks/types.ts 中,是背景任务指示器(Background Tasks Indicator)的核心逻辑:
// src/tasks/types.ts:25-38
export function isBackgroundTask(task: TaskState): task is BackgroundTaskState {
if (task.status !== 'running' && task.status !== 'pending') {
return false
}
// Foreground tasks (isBackgrounded === false) are not yet "background tasks"
if ('isBackgrounded' in task && task.isBackgrounded === false) {
return false
}
return true
}这里有两个关键判断条件:
- 状态过滤:只有
running或pending状态的任务才可能被视为背景任务。已完成、失败或已停止的任务不会出现在背景任务指示器中。 - 前景任务排除:如果任务的
isBackgrounded属性显式为false,则不会被视为背景任务。这用于区分那些技术上在后台运行、但逻辑上属于前景交互的任务(如某些挂起的 Shell 命令)。
TaskListTool 的输出通常会被格式化为一个简洁的表格,供 LLM 快速理解当前的任务负载:
当前任务列表(共 3 个):
- task_abc123 [local_agent] running - "分析代码依赖图"
- task_def456 [local_bash] running - "运行测试套件"
- task_ghi789 [remote_agent] pending - "远程部署验证"2.5 TaskStopTool:强制终止
TaskStopTool 是任务生命周期的"紧急制动"。当某个后台任务不再需要、运行超时或出现失控迹象时,LLM 可以调用此工具强制终止。
输入 Schema
// TaskStopTool.tsx 输入 Schema(基于架构文档还原)
const inputSchema = z.strictObject({
task_id: z.string()
.describe('The task ID to stop')
});终止机制
TaskStopTool 的终止逻辑因任务类型而异:
- LocalShellTask:调用底层
ShellCommandImpl的kill()方法,发送SIGTERM信号。如果进程不响应,可进一步升级为SIGKILL。 - LocalAgentTask:调用任务关联的
AbortController.abort()方法,触发子 Agent 的query()循环中的取消逻辑。 - RemoteAgentTask:向远程 CCR(Claude Code Remote)服务发送终止请求。
终止后,任务状态会被更新为 stopped,并触发清理逻辑(如删除 worktree、注销 MCP 连接等)。
// TaskStopTool.tsx 核心逻辑(基于架构还原)
export const TaskStopTool = buildTool({
name: TASK_STOP_TOOL_NAME,
searchHint: 'stop a running background task',
async call({ task_id }, { getAppState, setAppState }) {
const state = getAppState();
const task = state.tasks?.[task_id];
if (!task) {
return { status: 'not_found' };
}
if (task.status !== 'running' && task.status !== 'pending') {
return { status: 'already_stopped', previous_status: task.status };
}
// 类型特定的终止逻辑
await stopTaskByType(task);
// 更新状态
updateTaskState(task_id, { status: 'stopped' }, setAppState);
return { status: 'stopped', task_id };
}
});2.6 TaskOutputTool:结果的收割者
TaskOutputTool 是六个工具中实现最复杂的一个,因为它需要处理多种任务类型的输出差异,并支持阻塞等待模式。我们已经在 TaskOutputTool.tsx 的源码中看到了它的完整实现。
输入 Schema
// TaskOutputTool.tsx:35-40
const inputSchema = lazySchema(() => z.strictObject({
task_id: z.string().describe('The task ID to get output from'),
block: semanticBoolean(z.boolean().default(true)).describe('Whether to wait for completion'),
timeout: z.number().min(0).max(600000).default(30000).describe('Max wait time in ms')
}));三个参数的设计非常精妙:
task_id:目标任务的唯一标识。block:是否阻塞等待任务完成。默认为true,意味着如果任务仍在运行,TaskOutputTool会轮询等待,直到任务完成或超时。timeout:最大等待时间,默认 30 秒,上限 10 分钟(600,000 毫秒)。
输出类型
// TaskOutputTool.tsx:42-58
type TaskOutput = {
task_id: string;
task_type: TaskType;
status: string;
description: string;
output: string;
exitCode?: number | null;
error?: string;
// For agents
prompt?: string;
result?: string;
};
type TaskOutputToolOutput = {
retrieval_status: 'success' | 'timeout' | 'not_ready';
task: TaskOutput | null;
};retrieval_status 字段是 TaskOutputTool 特有的三层状态:
success:成功获取到任务的输出(任务可能已完成,也可能是运行中的中间输出)。timeout:在timeout时间内任务未完成,返回当前已捕获的部分输出。not_ready:任务尚未产生任何可读取的输出。
核心实现:getTaskOutputData
TaskOutputTool 的核心是 getTaskOutputData 函数,它统一了不同任务类型的输出提取逻辑:
// TaskOutputTool.tsx:62-118
async function getTaskOutputData(task: TaskState): Promise<TaskOutput> {
let output: string;
if (task.type === 'local_bash') {
const bashTask = task as LocalShellTaskState;
const taskOutputObj = bashTask.shellCommand?.taskOutput;
if (taskOutputObj) {
const stdout = await taskOutputObj.getStdout();
const stderr = await taskOutputObj.getStderr();
output = [stdout, stderr].filter(Boolean).join('\n');
} else {
output = await getTaskOutput(task.id);
}
} else {
output = await getTaskOutput(task.id);
}
const baseOutput: TaskOutput = {
task_id: task.id,
task_type: task.type,
status: task.status,
description: task.description,
output
};
// 类型特定字段的添加
if (task.type === 'local_bash') {
const bashTask = task as LocalShellTaskState;
return { ...baseOutput, exitCode: bashTask.result?.code ?? null };
}
if (task.type === 'local_agent') {
const agentTask = task as LocalAgentTaskState;
// 优先使用内存中的干净结果,而非磁盘上的原始 JSONL 转录
const cleanResult = agentTask.result
? extractTextContent(agentTask.result.content, '\n')
: undefined;
return {
...baseOutput,
prompt: agentTask.prompt,
result: cleanResult || output,
output: cleanResult || output,
error: agentTask.error
};
}
if (task.type === 'remote_agent') {
const remoteTask = task as RemoteAgentTaskState;
return { ...baseOutput, prompt: remoteTask.command };
}
return baseOutput;
}这段代码中有几个值得深入分析的设计点:
1. 内存优先策略(Local Agent)
对于 local_agent 类型的任务,代码优先使用 agentTask.result 中的内容,而不是直接从磁盘读取。原因在注释中说得很清楚:
"The disk output is a symlink to the full session transcript (every message, tool use, etc.), not just the subagent’s answer. The in-memory result contains only the final assistant text content blocks."
磁盘上的输出文件是一个完整的会话转录(JSONL 格式,包含每条消息、每次工具调用等),而内存中的 result 只包含子 Agent 最终的助手文本内容块。通过 extractTextContent() 函数提取干净的文本结果,可以避免将大量中间推理过程暴露给主 Agent。
2. Shell 任务的双路径输出
对于 local_bash 任务,代码首先尝试从内存中的 taskOutputObj 获取 stdout 和 stderr。如果内存对象不可用(例如任务已完成且对象被清理),则回退到 getTaskOutput(task.id) 从磁盘读取。这种双路径设计确保了无论任务处于什么阶段,都能获取到输出。
3. 统一的 getTaskOutput 磁盘读取
getTaskOutput 函数(来自 src/utils/task/diskOutput.ts)是任务输出的统一磁盘读取入口。它负责:
- 根据
task_id计算输出文件路径(通常位于临时目录中)。 - 读取文件内容并返回字符串。
- 处理文件不存在或读取失败的情况。
阻塞等待:waitForTaskCompletion
当 block: true 时,TaskOutputTool 会进入轮询等待模式:
// TaskOutputTool.tsx:120-143
async function waitForTaskCompletion(
taskId: string,
getAppState: () => { tasks?: Record<string, TaskState> },
timeoutMs: number,
abortController?: AbortController
): Promise<TaskState | null> {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
if (abortController?.signal.aborted) {
throw new AbortError();
}
const state = getAppState();
const task = state.tasks?.[taskId] as TaskState | undefined;
if (!task) return null;
if (task.status !== 'running' && task.status !== 'pending') {
return task;
}
await sleep(100); // 100ms 轮询间隔
}
const finalState = getAppState();
return finalState.tasks?.[taskId] as TaskState ?? null;
}这是一个简单的**轮询(Polling)**机制,每 100 毫秒检查一次任务状态。虽然轮询在分布式系统中通常被认为效率低下,但在 Claude Code 的单机/单进程架构中,100ms 的轮询间隔对性能的影响微乎其微,同时实现简单可靠。
向后兼容的别名
TaskOutputTool 还注册了向后兼容的别名:
// TaskOutputTool.tsx:145-155
export const TaskOutputTool = buildTool({
name: TASK_OUTPUT_TOOL_NAME,
searchHint: 'read output/logs from a background task',
maxResultSizeChars: 100_000,
shouldDefer: true,
aliases: ['AgentOutputTool', 'BashOutputTool'],
// ...
});AgentOutputTool 和 BashOutputTool 是旧版本中的独立工具名称。在架构演进过程中,这些功能被统一到了 TaskOutputTool 中,但保留了别名以确保旧版 prompt 和会话历史的兼容性。
三、任务状态机
3.1 状态定义
Claude Code 的任务系统定义了五种核心状态,覆盖了一个任务从诞生到消亡的完整生命周期:
| 状态 | 含义 | 可转换至 |
|---|---|---|
pending | 任务已创建,但尚未开始执行 | running, stopped |
running | 任务正在执行中 | completed, failed, stopped |
completed | 任务成功完成 | (终态) |
failed | 任务执行失败(抛出异常或非零退出码) | (终态) |
stopped | 任务被外部强制终止 | (终态) |
stateDiagram-v2
[*] --> pending: TaskCreateTool
pending --> running: 执行器启动
pending --> stopped: TaskStopTool
running --> completed: 自然完成
running --> failed: 异常/错误退出码
running --> stopped: TaskStopTool
completed --> [*]
failed --> [*]
stopped --> [*]3.2 状态转换规则
状态转换由以下实体触发:
执行器驱动
pending→running:任务执行器(如LocalShellTask.spawn())成功启动任务。running→completed:Shell 命令返回 0 退出码,或 Agent 正常结束对话循环。running→failed:Shell 命令返回非零退出码且被判定为错误,或 Agent 抛出未捕获异常。
工具驱动
pending→stopped:TaskStopTool在任务启动前终止。running→stopped:TaskStopTool向运行中的任务发送终止信号。
框架驱动
pending→failed:任务执行器初始化失败(如无法创建子进程)。
3.3 终态任务的处理
当任务进入 completed、failed 或 stopped 终态后,系统会执行一系列清理操作:
- 输出持久化:将任务的最终输出写入磁盘文件(
getTaskOutputPath(taskId)),确保即使内存中的任务对象被清理,输出仍然可访问。 - 资源释放:注销
AbortController、关闭文件描述符、清理 MCP 连接。 - UI 更新:从背景任务指示器中移除(因为
isBackgroundTask()对终态返回false)。 - 通知(可选):对于异步 Agent 任务,如果配置了通知,向主 Agent 发送
<task-notification>事件。
3.4 任务间的依赖关系
虽然 Task 系列工具本身不直接支持任务依赖(如"任务 B 等待任务 A 完成"),但 Claude Code 通过 LLM 的推理能力实现了逻辑依赖:
- LLM 调用
TaskListTool查看任务状态。 - LLM 调用
TaskOutputTool(block: true)等待特定任务完成。 - LLM 在确认前置任务完成后,再创建后续任务。
这种"显式编排"模式与 Workflow 引擎的"隐式编排"形成对比。Claude Code 的设计哲学是:让 LLM 扮演编排者的角色,而不是在系统中硬编码依赖图。这提供了更大的灵活性,同时也要求 LLM 具备足够的推理能力来正确管理任务顺序。
四、与任务系统的集成
4.1 tasks/ 目录的模块化设计
src/tasks/ 目录是 Claude Code 任务系统的核心实现区域。每个任务类型都有独立的子目录,遵循统一的接口契约:
src/tasks/
├── types.ts # 联合类型、类型守卫、isBackgroundTask
├── LocalShellTask/
│ ├── LocalShellTask.ts # 后台 Shell 任务执行器
│ └── guards.ts # LocalShellTaskState 类型守卫
├── LocalAgentTask/
│ ├── LocalAgentTask.ts # 本地子 Agent 任务管理
│ └── registerAsyncAgent.ts # AgentTool 使用的注册入口
├── RemoteAgentTask/
│ ├── RemoteAgentTask.ts # 远程 Agent 任务管理
│ └── registerRemoteAgent.ts # 远程 Agent 注册入口
├── InProcessTeammateTask/
│ └── types.ts # 队友 Agent 的类型定义
├── DreamTask/
│ └── DreamTask.ts # 后台"思考"任务
└── LocalWorkflowTask/
└── LocalWorkflowTask.ts # 工作流任务这种模块化设计使得新增任务类型变得非常简单——只需创建新的目录,实现对应的 TaskState 子类型,并在 types.ts 的联合类型中添加即可。
4.2 LocalAgentTask:本地子 Agent 的后台执行
LocalAgentTask 是 Claude Code 中最常用的后台任务类型。当 AgentTool 以异步模式创建子 Agent 时,底层就是通过 LocalAgentTask 来管理的。
从 AgentTool.tsx 的源码可以看到注册流程:
// AgentTool.tsx:640-650(引自 cc-28 分析)
const agentBackgroundTask = registerAsyncAgent({
agentId: asyncAgentId,
description,
prompt,
selectedAgent,
setAppState: rootSetAppState,
toolUseId: toolUseContext.toolUseId
});registerAsyncAgent 函数(位于 src/tasks/LocalAgentTask/registerAsyncAgent.ts)负责:
- 创建
LocalAgentTaskState对象,初始状态为pending。 - 将任务注册到 AppState。
- 返回一个包含
agentId和abortController的控制句柄。 - 在后台启动
runAsyncAgentLifecycle(),进入实际的 Agent 执行循环。
LocalAgentTaskState 的核心字段包括:
// LocalAgentTaskState 类型定义(基于架构还原)
type LocalAgentTaskState = {
id: string;
type: 'local_agent';
status: TaskStatus;
description: string;
prompt: string;
result?: Message; // 最终助手消息(内存中)
error?: string; // 错误信息
isBackgrounded: boolean; // 是否为后台任务
abortController: AbortController;
// ... 其他元数据
};4.3 RemoteAgentTask:远程执行的桥梁
RemoteAgentTask 用于管理在远程 CCR 环境中执行的 Agent。当 AgentTool 的 isolation 参数设置为 'remote' 时,系统会走这条路径。
// AgentTool.tsx:375-395(引自 cc-28 分析)
if ("external" === 'ant' && effectiveIsolation === 'remote') {
const eligibility = await checkRemoteAgentEligibility();
if (!eligibility.eligible) {
throw new Error(`Cannot launch remote agent:\n${reasons}`);
}
const session = await teleportToRemote({ initialMessage: prompt, description, ... });
const { taskId, sessionId } = registerRemoteAgentTask({
remoteTaskType: 'remote-agent',
session: { id: session.id, title: session.title || description },
command: prompt,
context: toolUseContext,
toolUseId: toolUseContext.toolUseId
});
}RemoteAgentTask 的特殊之处在于它的执行环境不在本地进程中。因此:
- 输出需要通过远程会话 API 拉取,而非本地文件系统。
- 终止操作需要向远程服务发送信号,而非本地
AbortController。 - 状态同步存在延迟,需要额外的轮询机制。
TaskOutputTool 对远程任务的处理也体现了这种差异——对于 remote_agent 类型,它会在输出中额外包含 prompt 字段(即远程 Agent 接收到的原始命令),但不提供 result 字段(因为远程结果需要通过专门的远程 API 获取)。
4.4 任务调度:从创建到清理
Claude Code 的任务调度并非由一个中心化的调度器控制,而是采用了一种分布式协作模式:
- 创建阶段:
AgentTool或BashTool决定创建后台任务,调用对应的注册函数。 - 执行阶段:任务执行器(
LocalShellTask、LocalAgentTask等)独立运行,通过setAppState更新自身状态。 - 监控阶段:UI 层通过
useAppState()订阅任务状态变化,更新背景任务指示器。 - 交互阶段:LLM 通过
TaskListTool、TaskGetTool、TaskOutputTool查询任务状态。 - 终止阶段:LLM 通过
TaskStopTool终止任务,或任务自然到达终态。 - 清理阶段:任务进入终态后,执行器负责资源释放,输出被持久化到磁盘。
flowchart LR
A[创建阶段
AgentTool/BashTool] --> B[注册到
AppState.tasks]
B --> C[执行阶段
独立运行]
C --> D[状态更新
setAppState]
D --> E[UI 渲染
背景任务指示器]
D --> F[LLM 查询
Task系列工具]
F --> G[终止/完成]
G --> H[输出持久化
磁盘文件]
H --> I[资源清理]
style A fill:#4a90d9,color:#fff
style C fill:#e67e22,color:#fff
style F fill:#27ae60,color:#fff这种设计的优势在于松耦合——任务执行器不需要知道 UI 如何渲染,Task 工具不需要了解执行器的内部实现,LLM 只需要通过统一的接口与任务交互。所有状态变化都通过单一的 AppState 树进行传播,确保了数据一致性。
五、源码总结与设计思想
通过分析 Task 系列工具的源码和架构文档,我们可以提炼出 Claude Code 任务系统的几个核心设计思想:
5.1 状态集中化
所有任务状态都存储在 AppState 的 tasks 字典中,任何组件都可以通过 getAppState() 获取一致的视图。这种集中化消除了分布式状态同步的复杂性,使得 TaskGetTool 可以实现为简单的字典查找,TaskListTool 可以实现为字典遍历。
5.2 类型安全的联合类型
TaskState 使用 TypeScript 的联合类型(Union Type)来区分不同任务类型:
// src/tasks/types.ts:8-15
export type TaskState =
| LocalShellTaskState
| LocalAgentTaskState
| RemoteAgentTaskState
| InProcessTeammateTaskState
| LocalWorkflowTaskState
| MonitorMcpTaskState
| DreamTaskState这使得 TaskOutputTool 可以通过类型守卫(Type Guard)为不同任务类型提供差异化的输出处理,同时保持编译时的类型安全。
5.3 工具级别的权限控制
Task 系列工具在权限系统中有明确的分类:
- 只读工具(
TaskGetTool、TaskListTool、TaskOutputTool):isReadOnly返回true,在严格权限模式下不会被拦截。 - 写工具(
TaskCreateTool、TaskUpdateTool、TaskStopTool):isReadOnly返回false,可能触发权限确认。
这种分类让 LLM 可以在受限环境中安全地查询任务状态,而无需担心意外的状态修改。
5.4 向后兼容性
TaskOutputTool 保留 AgentOutputTool 和 BashOutputTool 别名,TaskCreateTool 支持多种任务类型的统一创建接口。这些设计决策体现了对现有用户习惯和会话历史的尊重,避免因工具重命名而破坏已有的 prompt 工程成果。
结语
Task 系列工具是 Claude Code 从"单线程对话助手"进化为"多任务 Agent 平台"的关键基础设施。六个工具虽然各自简单,但组合起来形成了一套完整的任务生命周期管理协议。
TaskCreateTool 和 TaskStopTool 提供了生命周期的起点和终点;TaskListTool 和 TaskGetTool 提供了监控能力;TaskUpdateTool 提供了动态调控能力;TaskOutputTool 则作为结果的统一收割者,屏蔽了 Shell 输出、Agent 结果和远程响应之间的差异。
在 Claude Code 的架构中,Task 系统不仅是一个功能模块,更是一种设计哲学的体现:让 LLM 成为任务的编排者,让工具成为任务的执行者,让状态机成为任务的契约。三者通过统一的接口协作,共同支撑起复杂的多 Agent、多任务并行场景。