Planning with todos¶
Give your agent a multi-step job and it won't just dive in — it writes a plan first, then ticks items off as it goes. That plan is a real, inspectable todo list, and you didn't have to build it.
import asyncio
from pydantic_deep import create_deep_agent, DeepAgentDeps, StateBackend
async def main():
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
instructions="You are a helpful coding assistant.",
)
deps = DeepAgentDeps(backend=StateBackend())
result = await agent.run(
"Build a small CLI calculator: write calc.py with add/sub/mul/div, "
"add a tests file, run the tests, and fix anything that fails.",
deps=deps,
)
print(result.output)
# The plan the agent built and worked through:
for todo in deps.todos:
print(f"[{todo.status}] {todo.content}")
asyncio.run(main())
Run it¶
Save it to main.py and run:
A task with four-plus distinct steps trips the agent's planning instinct. It calls write_todos to lay out the work, marks one item in_progress, does it, marks it completed, and moves to the next — so the final loop prints something like:
[completed] Write calc.py with add/sub/mul/div
[completed] Write tests for the calculator
[completed] Run the test suite
[completed] Fix failing tests
Check it
Add a simpler prompt — "What's 2 + 2?" — and print deps.todos after.
It'll be empty. The agent only reaches for a plan when the work actually
has steps; trivial asks stay todo-free.
What's happening¶
You didn't register a todo tool — create_deep_agent() wired one in for you. Let's look at the two halves: the tools the model uses, and the list you read.
The tools the agent gets¶
Because include_todo=True is the default, every agent gets a small set of planning tools:
read_todos— view the current list with each item's ID and status.write_todos— replace the whole list (how the agent lays out a fresh plan).add_todo— append a single new task.update_todo_status— flip one task topending,in_progress, orcompletedby ID.remove_todo— drop a task by ID.
The agent's instructions nudge it to use them for anything non-trivial: break the work down, keep exactly one task in_progress at a time, and mark things completed the moment they're done — not in a batch at the end.
Three states
Every todo is pending, in_progress, or completed. That's the whole
state machine — enough to drive a focused agent without ceremony.
The list you read¶
deps = DeepAgentDeps(backend=StateBackend())
# ... after the run ...
for todo in deps.todos:
print(f"[{todo.status}] {todo.content}")
The todos don't vanish when the run ends — they live on deps.todos, a plain list of Todo objects you can read afterward. Each one has:
content— the task in imperative form ("Run the test suite").status—pending/in_progress/completed.active_form— the present-tense label ("Running the test suite"), handy for a live status line.id— a short identifier the tools use to target a single task.
Because the list is right there on your deps object, the same plan the model worked through is yours to inspect, log, or render in a UI.
Watch the plan live
deps.todos updates during the run, not just after it. Read it from a
background task or a streaming handler to drive a progress display — see
Streaming for the event-by-event view.
Sharing todos with subagents¶
When your agent delegates to a subagent, the subagent gets its own, empty todo list by default. That's deliberate: a subagent planning its slice of the work shouldn't scribble over the parent's plan.
If you do want one shared plan across the parent and its subagents, flip share_todos on the deps:
deps = DeepAgentDeps(
backend=StateBackend(),
share_todos=True, # subagents read and write the same todo list
)
Now a subagent's write_todos and update_todo_status land on the same list the parent sees — useful when you want a single, unified view of progress across a whole delegation tree.
Shared means shared
With share_todos=True, several agents can write to the same list
concurrently. It's the right call for a single coordinated plan, but if you
want each subagent to own its work independently, leave it False (the
default).
Recap¶
Your agent now plans its own work and you can see the plan:
create_deep_agent()includes the todo tools by default (include_todo=True) —read_todos,write_todos,add_todo,update_todo_status,remove_todo.- The agent breaks multi-step tasks into todos, keeps one
in_progress, and marks eachcompletedas it finishes. - The plan lives on
deps.todos— a list ofTodoobjects (content,status,active_form,id) you can read during or after the run. - The system prompt carries the todo workflow, not the live list. Instructions open the provider's prompt-cache prefix, so re-rendering them on every status change would invalidate the whole cached prefix mid-run — the list is already in the message history, and
read_todosfetches it on demand. Passinclude_current_todos=Trueif you want it in the prompt anyway. - Subagents get isolated todos by default; set
share_todos=Truefor one shared plan across the delegation tree.
Next, let's hand the agent tools of your own.