Todo tracking provides a structured way to manage tasks and display progress to users. The Claude Agent SDK includes built-in todo functionality that helps organize complex workflows and keep users informed about task progression.Documentation Index
Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
TodoWrite is the current default in the Agent SDK and the examples on this page use it. The replacement Task tools are available now behind CLAUDE_CODE_ENABLE_TASKS=1 and will become the default in a future release. See Migrate to Task tools for how monitoring code changes.Todo Lifecycle
Todos follow a predictable lifecycle:- Created as
pendingwhen tasks are identified - Activated to
in_progresswhen work begins - Completed when the task finishes successfully
- Removed when all tasks in a group are completed
When Todos Are Used
The SDK automatically creates todos for:- Complex multi-step tasks requiring 3 or more distinct actions
- User-provided task lists when multiple items are mentioned
- Non-trivial operations that benefit from progress tracking
- Explicit requests when users ask for todo organization
Examples
Monitoring Todo Changes
Real-time Progress Display
Migrate to Task tools
The Task tools split the singleTodoWrite call into TaskCreate for each new item and TaskUpdate for each status change, with TaskList and TaskGet available for the model to read back the current list. Your monitoring code still inspects tool_use blocks in the assistant stream, but maintains a map keyed by task ID instead of replacing the whole list on every call. To opt in before the Task tools become the default, set CLAUDE_CODE_ENABLE_TASKS=1 in options.env.
With TodoWrite | With Task tools |
|---|---|
One tool call rewrites the full todos array | TaskCreate adds one item, TaskUpdate patches one item by taskId |
Match block.name === "TodoWrite" | Match block.name === "TaskCreate" or "TaskUpdate" |
Item shape: { content, status, activeForm } | TaskCreate input: { subject, description, activeForm?, metadata? }. TaskUpdate input: { taskId, status?, subject?, description?, activeForm?, addBlocks?, addBlockedBy?, owner?, metadata? }. status is "pending", "in_progress", or "completed"; set status: "deleted" to delete |
Render block.input.todos directly | Accumulate items across calls, or read a snapshot from a TaskList tool result |
TaskCreate input. It comes back in the matching tool_result as { task: { id, subject } }, so capture it from the result block to key your map. The following example shows the minimal change to the Monitoring Todo Changes loop. To render a complete list, watch for a TaskList tool result in the stream or accumulate TaskCreate results and TaskUpdate inputs into a map: