Todo tracking cung cấp cách có cấu trúc để quản lý tác vụ và hiển thị tiến độ cho người dùng. Claude Agent SDK có sẵn chức năng todo tích hợp giúp tổ chức workflow phức tạp và giữ người dùng nắm được tiến độ tác vụ.
Vòng đời Todo
Phần tiêu đề “Vòng đời Todo”Claude di chuyển mỗi todo qua một vòng đời có thể dự đoán:
- Created: Claude thêm todo ở trạng thái
pendingkhi xác định một tác vụ - Activated: Claude đặt todo thành
in_progresskhi bắt đầu làm - Completed: Claude đánh dấu completed khi tác vụ hoàn thành thành công
- Removed: Claude xoá todo không còn cần bằng cách đặt
status: "deleted"trong lệnh gọiTaskUpdate
Khi nào Todo được dùng
Phần tiêu đề “Khi nào Todo được dùng”Claude tạo todo cho hầu hết công việc nhiều bước, như:
- Tác vụ nhiều bước phức tạp cần 3 hành động riêng biệt trở lên
- Danh sách tác vụ do người dùng cung cấp khi nhiều mục được nhắc tới
- Thao tác không tầm thường hưởng lợi từ việc theo dõi tiến độ
- Yêu cầu tường minh khi người dùng hỏi về tổ chức todo
Claude có thể bỏ qua todo cho yêu cầu rất ngắn hoặc một bước.
Ví dụ
Phần tiêu đề “Ví dụ”Trước khi chạy các ví dụ này, cài Claude Agent SDK theo quickstart.
Mỗi ví dụ chạy tới khi agent hoàn tất và trả về message kết quả cuối. Nếu phiên chạm giới hạn lượt trước, message kết quả đó có subtype error_max_turns. Kiểm tra subtype để phát hiện kết thúc kiểu này.
Các ví dụ này dùng lệnh gọi query() một lần. Sau khi trả về kết quả error_max_turns, query() raise một lỗi kèm Reached maximum number of turns. Mỗi ví dụ bọc vòng lặp trong try block để thoát gọn khi điều đó xảy ra.
Giám sát thay đổi Todo
Phần tiêu đề “Giám sát thay đổi Todo”import { query } from "@anthropic-ai/claude-agent-sdk";
try { for await (const message of query({ prompt: "Optimize my React app performance and track progress with todos", // Bật lại TodoWrite, thứ ví dụ này giám sát. Không có nó, SDK dùng // Task tools thay vào đó và các tool_use block này sẽ không xuất hiện. options: { maxTurns: 15, env: { ...process.env, CLAUDE_CODE_ENABLE_TASKS: "0" } } })) { // Cập nhật todo phản ánh trong 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}`); }); } } } }} catch (error) { // query() một lần throw sau khi trả kết quả lỗi, // ví dụ khi chạm giới hạn maxTurns. console.log(`Session ended with an error: ${error}`);}import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
async def main(): try: async for message in query( prompt="Optimize my React app performance and track progress with todos", # Bật lại TodoWrite, thứ ví dụ này giám sát. Không có nó, SDK dùng # Task tools thay vào đó và các tool_use block này sẽ không xuất hiện. options=ClaudeAgentOptions(max_turns=15, env={"CLAUDE_CODE_ENABLE_TASKS": "0"}), ): # Cập nhật todo phản ánh trong 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']}") except Exception as error: # query() một lần raise sau khi trả kết quả lỗi, # ví dụ khi chạm giới hạn max_turns. print(f"Session ended with an error: {error}")
asyncio.run(main())Hiển thị tiến độ real-time
Phần tiêu đề “Hiển thị tiến độ real-time”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) { try { for await (const message of query({ prompt, // Bật lại TodoWrite, thứ tracker này theo dõi. 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(); } } } } } catch (error) { // query() một lần throw sau khi trả kết quả lỗi, // ví dụ khi chạm giới hạn maxTurns. console.log(`Session ended with an error: ${error}`); } }}
// Cách dùngconst tracker = new TodoTracker();await tracker.trackQuery("Build a complete authentication system with todos");import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlockfrom 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): try: async for message in query( prompt=prompt, # Bật lại TodoWrite, thứ tracker này theo dõi. 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() except Exception as error: # query() một lần raise sau khi trả kết quả lỗi, # ví dụ khi chạm giới hạn max_turns. print(f"Session ended with an error: {error}")
# Cách dùngasync def main(): tracker = TodoTracker() await tracker.track_query("Build a complete authentication system with todos")
asyncio.run(main())Migrate sang Task tools
Phần tiêu đề “Migrate sang Task tools”Task tools tách một lệnh gọi TodoWrite duy nhất thành TaskCreate cho mỗi mục mới và TaskUpdate cho mỗi thay đổi trạng thái, với TaskList và TaskGet để model đọc lại danh sách hiện tại. Code giám sát của bạn vẫn kiểm tra tool_use block trong assistant stream, nhưng duy trì một map theo task ID thay vì thay toàn bộ danh sách mỗi lần gọi. Task tools là mặc định từ TypeScript Agent SDK 0.3.142 và Claude Code v2.1.142 trở đi, nên không cần đổi options.env.
Với TodoWrite | Với Task tools |
|---|---|
Một lệnh gọi tool ghi lại toàn bộ mảng todos | TaskCreate thêm một mục, TaskUpdate patch một mục theo taskId |
Khớp block.name === "TodoWrite" | Khớp block.name === "TaskCreate" hoặc "TaskUpdate" |
Hình dạng mục: { content, status, activeForm } | Input TaskCreate: { subject, description, activeForm?, metadata? }. Input TaskUpdate: { taskId, status?, subject?, description?, activeForm?, addBlocks?, addBlockedBy?, owner?, metadata? }. status là "pending", "in_progress", hoặc "completed"; đặt status: "deleted" để xoá |
Render block.input.todos trực tiếp | Tích luỹ mục qua các lệnh gọi, hoặc đọc snapshot từ kết quả tool TaskList |
Task ID được gán không nằm trong input của TaskCreate. Nó đến trong tool_result khớp dạng { task: { id, subject } }, nên bắt lấy ID từ result block để key cho map của bạn. Ví dụ dưới cho thấy thay đổi tối thiểu với vòng lặp Giám sát thay đổi Todo ở trên. Nó chỉ đọc input của tool_use và bỏ qua việc bắt ID từ tool_result block. Để render một danh sách đầy đủ, theo dõi kết quả tool TaskList trong stream hoặc tích luỹ kết quả TaskCreate và input TaskUpdate vào một map.
Input tool_use được stream là hình dạng thô model đã tạo ra. Claude Code sửa một số tên key gần đúng nhưng sai trước khi thực thi, map id hoặc task_id thành taskId và active_form thành activeForm, nhưng việc sửa đó không phản ánh trong stream. Đọc field input của TaskUpdate một cách phòng thủ, như các ví dụ dưới đây làm, thay vì giả định tên chuẩn luôn có mặt.
import { query } from "@anthropic-ai/claude-agent-sdk";
try { for await (const message of query({ prompt: "Optimize my React app performance and track progress with todos", options: { maxTurns: 15 }, })) { 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; id?: string; task_id?: string; status?: string; }; const taskId = input.taskId ?? input.id ?? input.task_id; if (taskId && input.status) console.log(` ${taskId} -> ${input.status}`); } } }} catch (error) { // query() một lần throw sau khi trả kết quả lỗi. console.log(`Session ended with an error: ${error}`);}import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
async def main(): try: async for message in query( prompt="Optimize my React app performance and track progress with todos", options=ClaudeAgentOptions(max_turns=15), ): 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"): task_id = ( block.input.get("taskId") or block.input.get("id") or block.input.get("task_id") ) if task_id: print(f" {task_id} -> {block.input['status']}") except Exception as error: # query() một lần raise sau khi trả kết quả lỗi. print(f"Session ended with an error: {error}")
asyncio.run(main())Tài liệu liên quan
Phần tiêu đề “Tài liệu liên quan”- SDK Reference cho TypeScript
- SDK Reference cho Python
- Streaming vs Single Mode
- Custom Tools
lượt xem