This is the tenth article in my series on Claude Code tools. The first nine covered:
- The interaction primitive trio—AskUserQuestion, EnterPlanMode, and ExitPlanMode.
- The execution primitive chain—Grep + Glob → Read → Edit / Write.
- The general-purpose fallback, Bash.
- The meta-tool, Agent.
The first nine tools are about Claude doing the thing happening now. Each tool call performs one immediate action. Real projects also require Claude to remember what needs to happen, track progress, decompose a large task, and share one checklist across multiple Claudes.
That requires a task-management system. Claude Code’s answer is the Task family—six tools that form a todo system.
This series begins with a prerequisite article explaining what tools are and how Claude uses them. Like the other articles, this one follows the four-layer framework introduced there.
The Task family: TaskCreate / TaskList / TaskGet / TaskUpdate / TaskStop / TaskOutput
This is the first time the series examines six tools in one article. Why group them? Because they share one data model—a task list—and are semantically coupled. Discussing one in isolation would shift attention from the system to a single operation. It would be like explaining how to create one Jira ticket without explaining the Jira system around it.
Family overview
| Tool | Responsibility | Typical moment |
|---|---|---|
| TaskCreate | Create a task | Decomposing a complex request or receiving multiple requirements |
| TaskList | List all tasks | Finding the next available task or reporting progress |
| TaskGet | Retrieve one task’s details | Before starting work or inspecting dependencies |
| TaskUpdate | Change task status or metadata | Starting, completing, or linking tasks |
| TaskStop | Stop a background task | Aborting a background Bash process or subagent |
| TaskOutput | Retrieve background output | Deprecated; use Read on the output file instead |
The family actually contains two groups:
- The first four are CRUD for conceptual todo tasks—something Claude remembers needs to happen.
- The last two control runtime tasks—a real Bash process or subagent that is currently running.
They all say Task, but they operate on different things. This is the family’s most confusing design choice and will matter throughout the article.
What it does
The Task family—especially the four todo tools—solves how Claude can manage multi-step work across tool calls and across time:
- Make decomposition visible: complex requirements become entries whose progress the user can see.
-
Track progress: every task has a
pending,in_progress, orcompletedstate. - Model dependencies: “A blocks B” becomes explicit and enforces order.
- Coordinate multiple Claudes: the main Claude decomposes work, subagents claim owners, and everyone shares one list.
- Compress context: a short subject can stand for an entire chunk of work, reducing what the main Claude must keep in mind.
The crucial difference from earlier tools is that Task is the only family with persistent state. Read, Edit, and Bash return a result once in a tool call. A TaskCreate entry remains in the runtime and appears in future TaskList calls until it is completed or deleted.
A concrete example
Scenario: The user says, “Add a user-profile page. It needs a backend API, a frontend component, a database schema, tests, and permission checks.”
This is a multi-task requirement.
The bad alternative: no Task family
Without tasks, Claude can only:
- Keep the plan in short-term memory while working.
- Announce every next step in chat as a textual progress log.
- Eventually lose a requirement—perhaps the tests—when the conversation becomes long.
- Reconstruct the entire conversation whenever the user asks, “How far along are you?”
The core problem is that the checklist exists only in Claude’s short-term context. Context compression, a subagent handoff, or session recovery can make it disappear.
How the Task family solves it
Step 1: Create tasks immediately
TaskCreate(subject: "Design database schema", description: "Add profile fields to users or create a profiles table")
TaskCreate(subject: "Write migration", description: "Generate the Knex migration")
TaskCreate(subject: "Implement backend API", description: "GET/PATCH /api/profile through the auth middleware")
TaskCreate(subject: "Build ProfilePage", description: "Add the /profile route, form, and API submission")
TaskCreate(subject: "Add tests", description: "API tests plus frontend component tests")
Each call returns an ID, such as task_001 through task_005.
Step 2: Add dependencies
The schema must exist before the API, and the API must exist before the frontend. TaskUpdate expresses those relationships through blockedBy:
TaskUpdate(taskId: "task_002", addBlockedBy: ["task_001"])
TaskUpdate(taskId: "task_003", addBlockedBy: ["task_002"])
TaskUpdate(taskId: "task_004", addBlockedBy: ["task_003"])
TaskUpdate(taskId: "task_005", addBlockedBy: ["task_003"])
The list now forms a dependency graph: schema → migration → API → (frontend + tests).
Step 3: Find the next available task
TaskList returns:
task_001 · pending · Design database schema · blockedBy: []
task_002 · pending · Write migration · blockedBy: [task_001]
task_003 · pending · Implement backend API · blockedBy: [task_002]
task_004 · pending · Build ProfilePage · blockedBy: [task_003]
task_005 · pending · Add tests · blockedBy: [task_003]
Only task_001 is pending and unblocked, so it is the next task.
Step 4: Claim, execute, and complete
TaskUpdate(taskId: "task_001", status: "in_progress")
# Claude designs the schema and records the decision
TaskUpdate(taskId: "task_001", status: "completed")
Once task_001 is complete, the migration task is unblocked.
Step 5: Delegate a task to a subagent
The frontend task can be delegated:
Agent(
description: "Build ProfilePage",
prompt: "Task task_004: build the ProfilePage at /profile. Use TaskGet for the full details."
)
The subagent can use the task ID to call TaskGet, claim the task with TaskUpdate, and mark it completed. The main Claude and the subagent coordinate through the shared task system rather than sending ad hoc messages.
Step 6: Report progress
Whenever the user asks for an update, TaskList is enough:
✅ task_001 · completed · Design database schema
✅ task_002 · completed · Write migration
🔄 task_003 · in_progress · Implement backend API (Claude)
⏸️ task_004 · pending · Build ProfilePage (blocked by 003)
⏸️ task_005 · pending · Add tests (blocked by 003)
One list makes the state immediately legible.
The key insight: Task externalizes Claude’s working memory
Earlier tools are about doing. The Task family is about remembering. It moves Claude’s short-term plan into runtime storage.
That creates two major effects:
- Persistence across contexts: tasks survive context compression, switching, and recovery.
- Sharing across Claudes: the main Claude and subagents synchronize through the Task system instead of messaging each other manually.
This resembles a human engineering team writing work into Jira. It is not a rejection of personal memory; memory is individual, while tasks are shared. Writing them down enables collaboration, tracking, and completeness.
When it is triggered
The family’s prompts provide fairly strict guidance:
Use Task tools for:
- Complex work with three or more steps. A one-step task does not need a task entry.
- Nontrivial multi-operation work. Planning and tracking have value here.
- An explicit user request for a todo list.
- Multiple requirements in one instruction. Create them together.
- Plan mode. Track the plan’s steps.
-
Starting work. Claim the task and mark it
in_progressbefore acting. -
Finishing work. Mark it
completedimmediately and inspect newly unblocked tasks.
Do not use Task tools for:
- one direct operation
- a trivial task where tracking creates more noise than value
- a simple job with fewer than three steps
- pure conversation or an informational answer
The core judgment is: the Task family is for work with meaningful scale. If a single tool call completes the task, a Task entry is noise. If the work has decomposition, dependencies, or progress worth tracking, failing to create one is a process failure.
Technical design
1. Naming
TaskCreate / TaskList / TaskGet / TaskUpdate / TaskStop / TaskOutput
The shared Task prefix replaces alternatives such as Todo, Ticket, or Job. “Task” implies a clear execution owner; a todo can merely mean “look at this someday.” The name itself hints that an owner field exists.
The CRUD suffixes—Create, List, Get, Update—are standard database-like verbs: create one, list all, retrieve one, update one. The four names immediately establish a mental model of an enumerable, addressable, mutable entity collection.
There is deliberately no TaskDelete. Hard deletion is represented by TaskUpdate(status: "deleted"). Deletion is treated as a terminal state in the state machine rather than a separate operation, concentrating all status transitions in TaskUpdate and reducing decision overhead.
TaskStop and TaskOutput introduce semantic drift. They reuse the Task namespace but operate on running background processes—Bash or subagents—rather than conceptual todos. The designers chose one namespace over a separate runtime-task family, but this is also the family’s most obvious source of confusion.
activeForm is the most ambitious field name in the family. It is not called presentContinuous, verbForm, or spinnerLabel; activeForm sounds grammatical. When Claude writes it, the name nudges Claude to convert the action into the present progressive rather than enter a generic UI label.
2. Tool-level descriptions
Each Task tool has its own description, but they share a positioning: these are members of one collaboration contract, not isolated utilities.
TaskCreate: a quantitative threshold
Use this tool proactively in these scenarios: Complex multi-step tasks—when a task requires 3 or more distinct steps or actions.
“Three or more” is an explicit threshold. It trains Claude not to create tasks for every small operation and replaces the vague word “complex” with a measurable rule.
A three-stage timing protocol
After receiving new instructions—immediately capture requirements as tasks.
When you start working—mark the taskin_progressbefore beginning.
After completing—mark itcompletedand add follow-up tasks.
The rhythm is exact: receive → create; start → in progress; finish → completed. It wraps every work segment and prevents work from silently beginning or ending.
TaskUpdate: a strict completion standard
Only mark a task completed when it is fully accomplished. If there are errors, blockers, or unfinished work, keep it
in_progress. Never mark it completed when tests fail, implementation is partial, or unresolved errors remain.
This blocks “fake completion”—the tendency to mark something done because the broad direction looks right while leaving half-finished work behind.
TaskList: a default scheduling intuition
Prefer working on tasks in ID order (lowest ID first) when multiple tasks are available.
Earlier tasks are often prerequisites for later tasks, so ID order makes the default schedule match creation order.
TaskGet before TaskUpdate: staleness awareness
Make sure to read a task’s latest state using
TaskGetbefore updating it.
Another agent may have changed the task, especially in a multi-Claude workflow. Fetching the latest state before writing is a simple form of optimistic concurrency control: read before write, never overwrite stale state blindly.
TaskOutput: transparent deprecation
DEPRECATED: Background tasks return their output file path in the tool result and in the completion notification. For Bash tasks, prefer Read on that output path.
The tool description directly says not to use it and gives the replacement. This reflects a broader design principle: if an existing primitive can cover a capability, do not maintain a separate tool for it. Fewer tools mean less API surface and less decision burden.
A family-specific reminder hook
If Claude goes a long time without using task tools, the harness can insert a system reminder:
The task tools haven’t been used recently. If your work would benefit from tracking progress, consider using TaskCreate and TaskUpdate.
This nudges Claude toward progress tracking without making it mandatory. The reminder ends with the equivalent of “only use these if relevant.” Earlier tools do not need this hook because their value is immediate; Task tools need a cross-time nudge.
3. Field-level descriptions
A complete Task object includes:
-
id: system-generated unique identifier -
subject: short imperative title, such as “Run tests” -
description: detailed explanation -
activeForm: present-progressive form, such as “Running tests,” for a spinner -
status:pending,in_progress,completed, ordeleted -
owner: the agent doing the work; empty means unclaimed -
blocks: tasks blocked by this task -
blockedBy: tasks blocking this task -
metadata: arbitrary key-value data
Four design choices stand out.
Three representations: subject, description, activeForm
The same task appears in three forms:
-
subject: a short imperative, “Run tests” -
description: “Run the unit tests and confirm all four auth tests pass” -
activeForm: “Running tests”
They map to different UI locations:
- list view shows the short subject
- detail view shows the full description
- a spinner uses the present-progressive active form
The forced progressive form is more than cosmetic. Claude must provide both “what to do” and “what is happening now,” encoding the distinction between intending to start and having started.
blocks and blockedBy: bidirectional dependencies
They are two views of the same relationship:
A blocks B ⇔ B is blockedBy A
The runtime keeps both directions consistent. Claude can add one side with addBlocks or addBlockedBy, and the other side synchronizes automatically.
This is redundancy in favor of readable scheduling semantics: “what do I block?” and “what blocks me?” are different questions for Claude even though they describe one edge.
Incremental merge semantics
TaskUpdate accepts addBlocks and addBlockedBy, not a replacement-style blocks: [...]. Adding one dependency therefore cannot accidentally erase existing dependencies. Incremental updates are safer and naturally idempotent.
Status: a linear state machine with a deleted escape hatch
The normal path is:
pending → in_progress → completed
Completed tasks cannot move backward to in_progress; if the work must be redone, create a new task. This prevents unpredictable state oscillation.
deleted is a terminal cleanup state for mistaken tasks. Deleted tasks disappear from normal lists while their IDs remain reserved, preventing reuse. Deletion is therefore part of the state machine rather than disappearance from the database.
blockedBy also constrains status transitions. A task with incomplete dependencies cannot be claimed as in_progress. Status is not an isolated field; it is a multi-field transition governed by the current dependency graph.
owner and metadata: two switches for multi-Claude work
owner identifies which agent currently owns a task:
- the main Claude creates it with no owner
- a subagent claims it and records its agent name
- TaskList shows which work is claimed and which is available
- after completion, another agent can take the next task
This is the basic pattern of a distributed work queue, with Claude instances as consumers.
metadata is a free-form key-value escape hatch for file paths, reference links, subagent context, or temporary notes. owner is a core contract; metadata leaves room for extension.
4. Schema validation
The family combines schema-level static checks with runtime state-machine checks:
| Constraint | Layer | Meaning |
|---|---|---|
activeForm required |
Schema | TaskCreate requires the progressive form |
status enum |
Schema | Only pending, in_progress, completed, or deleted
|
subject length |
Schema | Short title has a version-dependent maximum |
| Backward status transition | Runtime |
completed → in_progress is rejected |
Unresolved blockedBy → in_progress
|
Runtime | A locked task cannot be claimed |
| TaskGet before TaskUpdate | Runtime guidance | Strongly recommended, but primarily prompt-enforced |
Static constraints belong in the schema; dynamic constraints such as dependencies, concurrency, and legal state transitions belong in runtime. The Task family is more balanced than Read or Edit: schemas protect inputs while runtime protects transitions.
TaskOutput’s deprecation also illustrates transparent fallback. Output retrieval is not replaced by a new specialized tool; it is reduced to Read on the existing output path. Capabilities that existing primitives can cover do not need another tool.
Division of responsibility among neighboring tools
| Dimension | Interaction trio | Locate + perceive + execute | Bash | Agent | Task family |
|---|---|---|---|---|---|
| Role | Collaborative alignment | Modify code | Execute commands | Derive Claude | Externalize working memory |
| Time model | Present, one interaction | Present, one operation | Present, command lifecycle | Present, fork/join | Persistent across time |
| State location | None, conversation-driven | Disk + harness | Gone after command | Inside the subagent | Runtime storage |
| Main benefit | User alignment | Precise code changes | Engineering workflow | Context space | Against forgetting; visible collaboration |
| Naming pattern | Enter / Exit pair | Read / Edit / Write family | Single tool | Single tool | CRUD + Stop / Output |
The Task family is most tightly coupled with Agent:
- Agent delegates work whose outcome may fail, hang, or need stopping.
- Tasks provide the work-item container that makes subagent work trackable.
- TaskStop accepts a subagent ID or task ID, creating a unified stop entry point.
- TaskOutput used to retrieve subagent results directly; now Read handles the output file.
Task and Bash also have a useful analogy. Bash’s run_in_background puts a command in the background; TaskCreate puts a todo in persistent storage. Both prevent the main loop from blocking, but they solve different problems: Bash is asynchronous machine I/O, while Task is asynchronous human-AI coordination.
The family’s position in the ecosystem is therefore unique. The first nine tools perform one immediate operation per call. Task is a meta-primitive that stores what should happen across time.
Summary
The Task family’s elegance is not the existence of a todo list. It is the way its signals span four layers and form a complete dual system:
-
Naming: six tools—four CRUD operations plus Stop and Output.
activeFormencodes grammar in a field name;TaskDeleteis omitted in favor ofstatus: "deleted"; output retrieval falls back to Read. - Tool-level descriptions: each tool has an independent prompt, but they reference one another and encode the collaboration contract—three-step threshold, timing protocol, false-completion prohibition, staleness warning, deprecation notice, and reminder hook.
- Field-level descriptions: subject, description, and activeForm map to three UI contexts; blocks and blockedBy expose both directions of a dependency; add-prefixed fields prevent destructive replacement; owner is a hard collaboration field while metadata is a flexible escape hatch.
- Schema validation: static constraints such as required activeForm and status enums live in the schema; dynamic constraints such as state transitions, dependency locks, and stale updates live in runtime.
Task extends Claude Code from the present tense to the future tense. The first nine tools do something now; Task stores what must happen in runtime storage across tool calls, time, and Claude instances. The result is a shift from relying on mental effort against forgetting to using a system against forgetting. Forgetting is no longer catastrophic because the list remains.
The deeper insight is that a dual tool family needs both a complete lifecycle and an exit path. CRUD is not “create without delete”: completed ends the normal lifecycle, deleted cleans up mistaken tasks, and dependency updates release blocked work. Every task has a defined way to end.
The forced progressive activeForm is the family’s boldest field design. It turns “fill in a UI label” into a grammar transformation and trains Claude to see work as currently happening, not merely intended. That distinction is the difference between having started and planning to start—and the difference between a static todo list and a live work rhythm.
The next article will examine WebFetch + WebSearch, the sister tools that take Claude beyond the filesystem and asynchronous tasks into the external web: one is “curl with AI,” the other “search with filters.”
Top comments (0)