Skip to content

Claude Agent SDK 的 Todo 跟踪功能自动管理复杂任务的状态(pending→in_progress→completed),并通知调用端。示例演示如何通过 stream 消息中的 tool_use 块(TodoWrite 或 TaskCreate/TaskUpdate)监控实时进展。要回退到旧 TodoWrite 以便监控,需设置环境变量 CLAUDE_CODE_ENABLE_TASKS=0——新版 SDK(TypeScript 0.3.142+ / Claude Code v2.1.142+)默认使用 Task 工具,不再触发 TodoWrite。

怎么在 Claude Agent SDK 监控 Todo 任务并迁移到 Task 工具

Track and display todos using the Claude Agent SDK for organized task management

Todo 跟踪提供了一种结构化方式来管理任务并向用户展示进度。Claude Agent SDK 内置了 Todo 功能,帮助组织复杂工作流并让用户随时了解任务进度。

自 TypeScript Agent SDK 0.3.142 和 Claude Code v2.1.142 起,会话使用结构化 Task 工具 TaskCreateTaskUpdateTaskGetTaskList 代替 TodoWrite。监控代码的迁移方式详见 迁移到 Task 工具。本页示例中设置 CLAUDE_CODE_ENABLE_TASKS=0 以继续显示 TodoWrite,方便尚未迁移的会话。

Todo 生命周期

Todo 遵循可预测的生命周期:

  1. 创建:任务被识别时状态为 pending
  2. 激活:工作开始后变为 in_progress
  3. 完成:任务成功结束时完成
  4. 移除:当一组中所有任务都完成时移除

何时使用 Todo

SDK 会自动为以下情况创建 Todo:

  • 复杂多步骤任务:需要 3 个或更多不同操作
  • 用户提供的任务列表:当提到多个项目时
  • 非平凡操作:适合用进度跟踪的场景
  • 明确请求:用户要求用 Todo 组织任务时

示例

监控 Todo 变化

typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Optimize my React app performance and track progress with todos",
  // Re-enable TodoWrite, which this example monitors. Without it, the SDK uses
  // Task tools instead and these tool_use blocks never appear.
  options: { maxTurns: 15, env: { ...process.env, CLAUDE_CODE_ENABLE_TASKS: "0" } }
})) {
  // Todo updates are reflected in the message stream
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "tool_use" && block.name === "TodoWrite") {
        const todos = block.input.todos;

        console.log("Todo Status Update:");
        todos.forEach((todo, index) => {
          const status =
            todo.status === "completed" ? "✅" : todo.status === "in_progress" ? "🔧" : "❌";
          console.log(`${index + 1}. ${status} ${todo.content}`);
        });
      }
    }
  }
}
python
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock

async for message in query(
    prompt="Optimize my React app performance and track progress with todos",
    # Re-enable TodoWrite, which this example monitors. Without it, the SDK uses
    # Task tools instead and these tool_use blocks never appear.
    options=ClaudeAgentOptions(max_turns=15, env={"CLAUDE_CODE_ENABLE_TASKS": "0"}),
):
    # Todo updates are reflected in the message stream
    if isinstance(message, AssistantMessage):
        for block in message.content:
            if isinstance(block, ToolUseBlock) and block.name == "TodoWrite":
                todos = block.input["todos"]

                print("Todo Status Update:")
                for i, todo in enumerate(todos):
                    status = (
                        "✅"
                        if todo["status"] == "completed"
                        else "🔧"
                        if todo["status"] == "in_progress"
                        else "❌"
                    )
                    print(f"{i + 1}. {status} {todo['content']}")

实时进度显示

typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

class TodoTracker {
  private todos: any[] = [];

  displayProgress() {
    if (this.todos.length === 0) return;

    const completed = this.todos.filter((t) => t.status === "completed").length;
    const inProgress = this.todos.filter((t) => t.status === "in_progress").length;
    const total = this.todos.length;

    console.log(`\nProgress: ${completed}/${total} completed`);
    console.log(`Currently working on: ${inProgress} task(s)\n`);

    this.todos.forEach((todo, index) => {
      const icon =
        todo.status === "completed" ? "✅" : todo.status === "in_progress" ? "🔧" : "❌";
      const text = todo.status === "in_progress" ? todo.activeForm : todo.content;
      console.log(`${index + 1}. ${icon} ${text}`);
    });
  }

  async trackQuery(prompt: string) {
    for await (const message of query({
      prompt,
      // Re-enable TodoWrite, which this tracker watches for.
      options: { maxTurns: 20, env: { ...process.env, CLAUDE_CODE_ENABLE_TASKS: "0" } }
    })) {
      if (message.type === "assistant") {
        for (const block of message.message.content) {
          if (block.type === "tool_use" && block.name === "TodoWrite") {
            this.todos = block.input.todos;
            this.displayProgress();
          }
        }
      }
    }
  }
}

// Usage
const tracker = new TodoTracker();
await tracker.trackQuery("Build a complete authentication system with todos");
python
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
from typing import List, Dict


class TodoTracker:
    def __init__(self):
        self.todos: List[Dict] = []

    def display_progress(self):
        if not self.todos:
            return

        completed = len([t for t in self.todos if t["status"] == "completed"])
        in_progress = len([t for t in self.todos if t["status"] == "in_progress"])
        total = len(self.todos)

        print(f"\nProgress: {completed}/{total} completed")
        print(f"Currently working on: {in_progress} task(s)\n")

        for i, todo in enumerate(self.todos):
            icon = (
                "✅"
                if todo["status"] == "completed"
                else "🔧"
                if todo["status"] == "in_progress"
                else "❌"
            )
            text = (
                todo["activeForm"]
                if todo["status"] == "in_progress"
                else todo["content"]
            )
            print(f"{i + 1}. {icon} {text}")

    async def track_query(self, prompt: str):
        async for message in query(
            prompt=prompt,
            # Re-enable TodoWrite, which this tracker watches for.
            options=ClaudeAgentOptions(max_turns=20, env={"CLAUDE_CODE_ENABLE_TASKS": "0"}),
        ):
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, ToolUseBlock) and block.name == "TodoWrite":
                        self.todos = block.input["todos"]
                        self.display_progress()


# Usage
tracker = TodoTracker()
await tracker.track_query("Build a complete authentication system with todos")

迁移到 Task 工具

Task 工具将单一的 TodoWrite 调用拆分为 TaskCreate(新增每个项目)、TaskUpdate(更新每个状态变化),并提供了 TaskListTaskGet 供模型读取当前列表。你的监控代码仍然在 assistant 流中检查 tool_use 块,但需要用任务 ID 作为键维护一个映射,而不是每次调用替换整个列表。

默认情况下,TypeScript Agent SDK 0.3.142 和 Claude Code v2.1.142 已使用 Task 工具,因此无需通过 options.env 进行变更。

使用 TodoWrite使用 Task 工具
一次工具调用重写整个 todos 数组TaskCreate 添加一个项目,TaskUpdatetaskId 更新一个项目
匹配 block.name === "TodoWrite"匹配 block.name === "TaskCreate""TaskUpdate"
项目结构: { content, status, activeForm }TaskCreate 输入: { subject, description, activeForm?, metadata? }TaskUpdate 输入: { taskId, status?, subject?, description?, activeForm?, addBlocks?, addBlockedBy?, owner?, metadata? }status"pending""in_progress""completed";设置 "deleted" 可删除
直接渲染 block.input.todos跨调用积累项目,或从 TaskList 工具结果读取快照

分配的任务 ID 不在 TaskCreate 输入中,而是在对应的 tool_result 中以 { task: { id, subject } } 返回。因此需要从结果块中捕获该 ID 以作为映射键。下面的示例展示了 监控 Todo 变化 循环的最小改动。要渲染完整列表,可以在流中监听 TaskList 工具结果,或将 TaskCreate 结果和 TaskUpdate 输入累积到一个映射中:

typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Optimize my React app performance",
})) {
  if (message.type !== "assistant") continue;
  for (const block of message.message.content) {
    if (block.type !== "tool_use") continue;
    if (block.name === "TaskCreate") {
      const input = block.input as { subject: string };
      console.log(`+ ${input.subject}`);
    } else if (block.name === "TaskUpdate") {
      const input = block.input as { taskId: string; status?: string };
      if (input.status) console.log(`  ${input.taskId} -> ${input.status}`);
    }
  }
}
python
from claude_agent_sdk import query, AssistantMessage, ToolUseBlock

async for message in query(
    prompt="Optimize my React app performance",
):
    if not isinstance(message, AssistantMessage):
        continue
    for block in message.content:
        if not isinstance(block, ToolUseBlock):
            continue
        if block.name == "TaskCreate":
            print(f"+ {block.input['subject']}")
        elif block.name == "TaskUpdate" and block.input.get("status"):
            print(f"  {block.input['taskId']} -> {block.input['status']}")

常见问题

为什么我的 TodoWrite 在 stream 中收不到?

新版 SDK(TS Agent SDK ≥ 0.3.142 或 Claude Code ≥ v2.1.142)默认使用 Task 工具(TaskCreate/TaskUpdate),不再发出 TodoWrite 调用。如果仍然需要监控旧格式的 Todo,请在 queryoptions.env 中设置 CLAUDE_CODE_ENABLE_TASKS=0

如何监控使用 Task 工具后的实时进度?

仍然监听 assistant 消息中的 tool_use 块。匹配 block.name === "TaskCreate""TaskUpdate",从输入中获取 taskIdstatus,并累积在本地映射中。如果需要完整快照,可以额外监听 TaskList 工具结果。

迁移到 Task 工具后,任务 ID 从哪里获取?

TaskCreate 的输入中不包含任务 ID。你需要捕获对应的 tool_result 块,其中包含 { task: { id, subject } },然后用这个 ID 作为后续 TaskUpdate 等操作的键。

相关文档