Task 系列工具:任务管理

📑 目录

在 Claude Code 的工具体系中,如果说 AgentTool 是"子 Agent 调度器",那么 Task 系列工具就是整个任务系统的"仪表盘与遥控器"。它们不直接创建子 Agent 或执行 Shell 命令,而是提供了一套完整的 CRUD(创建、读取、更新、删除/停止)接口,让 LLM 能够主动管理后台运行的任务集合。

这六个工具——TaskCreateToolTaskGetToolTaskUpdateToolTaskListToolTaskStopToolTaskOutputTool——共同构成了 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
  | DreamTaskState

AppState 中的任务存储结构是一个以 task_id 为键的字典:

// AppState 中的任务存储(示意)
tasks?: Record<string, TaskState>;

这使得 TaskGetToolTaskListTool 可以通过简单的字典查找实现 O(1) 的单任务查询和 O(n) 的全量遍历。


二、各工具详解

2.1 TaskCreateTool:任务的入口

TaskCreateTool 是 Task 系列中唯一一个创建型工具。与其他工具不同,它通常不是由 LLM 直接调用的,而是作为内部基础设施AgentToolBashTool 在后台模式下间接使用。

输入 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 被调用时,它会执行以下操作:

  1. 生成唯一 Task ID:使用 createTaskId() 生成全局唯一的任务标识符。
  2. 创建任务状态对象:根据 task_type 初始化对应的 TaskState 子类型,状态设为 pending
  3. 写入 AppState:将任务对象注册到 appState.tasks 字典中。
  4. 触发执行器:如果是 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
}

这里有两个关键判断条件:

  1. 状态过滤:只有 runningpending 状态的任务才可能被视为背景任务。已完成、失败或已停止的任务不会出现在背景任务指示器中。
  2. 前景任务排除:如果任务的 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:调用底层 ShellCommandImplkill() 方法,发送 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'],
  // ...
});

AgentOutputToolBashOutputTool 是旧版本中的独立工具名称。在架构演进过程中,这些功能被统一到了 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 状态转换规则

状态转换由以下实体触发:

执行器驱动

  • pendingrunning:任务执行器(如 LocalShellTask.spawn())成功启动任务。
  • runningcompleted:Shell 命令返回 0 退出码,或 Agent 正常结束对话循环。
  • runningfailed:Shell 命令返回非零退出码且被判定为错误,或 Agent 抛出未捕获异常。

工具驱动

  • pendingstoppedTaskStopTool 在任务启动前终止。
  • runningstoppedTaskStopTool 向运行中的任务发送终止信号。

框架驱动

  • pendingfailed:任务执行器初始化失败(如无法创建子进程)。

3.3 终态任务的处理

当任务进入 completedfailedstopped 终态后,系统会执行一系列清理操作:

  1. 输出持久化:将任务的最终输出写入磁盘文件(getTaskOutputPath(taskId)),确保即使内存中的任务对象被清理,输出仍然可访问。
  2. 资源释放:注销 AbortController、关闭文件描述符、清理 MCP 连接。
  3. UI 更新:从背景任务指示器中移除(因为 isBackgroundTask() 对终态返回 false)。
  4. 通知(可选):对于异步 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)负责:

  1. 创建 LocalAgentTaskState 对象,初始状态为 pending
  2. 将任务注册到 AppState。
  3. 返回一个包含 agentIdabortController 的控制句柄。
  4. 在后台启动 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。当 AgentToolisolation 参数设置为 '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 的任务调度并非由一个中心化的调度器控制,而是采用了一种分布式协作模式:

  1. 创建阶段AgentToolBashTool 决定创建后台任务,调用对应的注册函数。
  2. 执行阶段:任务执行器(LocalShellTaskLocalAgentTask 等)独立运行,通过 setAppState 更新自身状态。
  3. 监控阶段:UI 层通过 useAppState() 订阅任务状态变化,更新背景任务指示器。
  4. 交互阶段:LLM 通过 TaskListToolTaskGetToolTaskOutputTool 查询任务状态。
  5. 终止阶段:LLM 通过 TaskStopTool 终止任务,或任务自然到达终态。
  6. 清理阶段:任务进入终态后,执行器负责资源释放,输出被持久化到磁盘。
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 系列工具在权限系统中有明确的分类:

  • 只读工具TaskGetToolTaskListToolTaskOutputTool):isReadOnly 返回 true,在严格权限模式下不会被拦截。
  • 写工具TaskCreateToolTaskUpdateToolTaskStopTool):isReadOnly 返回 false,可能触发权限确认。

这种分类让 LLM 可以在受限环境中安全地查询任务状态,而无需担心意外的状态修改。

5.4 向后兼容性

TaskOutputTool 保留 AgentOutputToolBashOutputTool 别名,TaskCreateTool 支持多种任务类型的统一创建接口。这些设计决策体现了对现有用户习惯和会话历史的尊重,避免因工具重命名而破坏已有的 prompt 工程成果。


结语

Task 系列工具是 Claude Code 从"单线程对话助手"进化为"多任务 Agent 平台"的关键基础设施。六个工具虽然各自简单,但组合起来形成了一套完整的任务生命周期管理协议。

TaskCreateToolTaskStopTool 提供了生命周期的起点和终点;TaskListToolTaskGetTool 提供了监控能力;TaskUpdateTool 提供了动态调控能力;TaskOutputTool 则作为结果的统一收割者,屏蔽了 Shell 输出、Agent 结果和远程响应之间的差异。

在 Claude Code 的架构中,Task 系统不仅是一个功能模块,更是一种设计哲学的体现:让 LLM 成为任务的编排者,让工具成为任务的执行者,让状态机成为任务的契约。三者通过统一的接口协作,共同支撑起复杂的多 Agent、多任务并行场景。