Task Planning and write_todos: Make the Agent Think Before It Acts

Task Planning and write_todos: Make the Agent Think Before It Acts

5 min read

Have you ever used AI for a slightly complex task and found halfway through that it was going in circles — doing the same thing twice, or skipping a critical step, then quietly tacking on "you may also want to verify X yourself" at the end?

This isn't a hallucination, and the model isn't too weak — the agent executing the task simply has no plan in its head. It only knows "what to do right now," not how many steps the whole task has, where it is in the process, or what's still left to do.

write_todos is the tool that externalizes that plan.

What write_todos Is

write_todos is one of Deep Agents' built-in tools. It lets the agent:

  1. Break the task into concrete steps: before starting execution, list out "what this task needs to accomplish"
  2. Track the status of each step: in progress, completed, pending
  3. Dynamically adjust the plan: if it discovers a step needs to be added or reordered during execution, it can update

The plan lives externally — it's not maintained by text in the context. This means even when the context is long and has drifted far from the initial plan, the agent can still look back at the plan and know where it is.

When the Agent Calls It on Its Own

You don't need to manually call write_todos — the agent decides whether to plan based on the task's complexity.

Generally, the agent tends to call write_todos first when the task has these characteristics:

  • There are clearly "multiple steps" that need to be completed in sequence
  • Later steps depend on the results of earlier ones
  • The task's scope is broad with multiple sub-objectives

Conversely, for simple Q&A or single-step tasks, the agent typically won't call write_todos — it just responds directly.

Watching It in Action

Give it a task that requires multiple steps:

from deepagents import create_deep_agent
 
def read_codebase(path: str) -> str:
    """Read the code file at the specified path."""
    return f"Code content: contents of {path}..."
 
def run_tests(test_file: str) -> str:
    """Run tests and return results."""
    return "Test results: 3 passed, 1 failed"
 
def write_report(content: str, filename: str) -> str:
    """Write report content to a file."""
    return f"Report saved to {filename}"
 
agent = create_deep_agent(
    tools=[read_codebase, run_tests, write_report],
    system_prompt="You are a code review assistant responsible for analyzing code, running tests, and generating reports.",
)
 
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "Analyze the code quality in the src/ directory, run the tests, then generate a review report saved to review.md"}
    ]
})

This task has three phases — analyze, test, report. After receiving this request, the agent will typically call write_todos first to record the three steps, then execute them one by one, updating the status after each step completes.

Running it with streaming lets you observe this process:

for chunk in agent.stream({
    "messages": [
        {"role": "user", "content": "Analyze the code quality..."}
    ]
}):
    # chunks will include write_todos calls and their results
    if "tool_calls" in str(chunk):
        print(chunk)

The Real Difference "Planning First" Makes

Compare the two scenarios:

Agent without planning:

Receives task → immediately starts step one → finishes, thinks of next step → halfway through might remember something else → at the end might have missed things → user has to compare carefully to discover half of step 2 is missing

Agent with write_todos:

Receives task → writes plan (3 steps) → executes step 1 (marks complete) → executes step 2 (marks complete) → executes step 3 (marks complete) → confirms all steps done → done

The difference isn't speed — it's reliability. An agent with a plan is less likely to skip steps and is also easier to resume after being interrupted (because the plan is still there).

Guiding Planning Behavior in system_prompt

Even though the agent decides on its own whether to plan, you can influence this behavior through system_prompt:

agent = create_deep_agent(
    tools=[...],
    system_prompt="""You are a meticulous research assistant.
    
    Before handling any multi-step task, use write_todos to list the steps clearly, then start executing.
    After completing each step, update the todo status to ensure nothing is missed.""",
)

You don't need to hard-code the entire planning logic into the prompt — just making "plan first for complex tasks" an explicit habit is enough.

Task Planning Turns an "Impulsive" Agent into an "Organized" One

This analogy might be slightly exaggerated, but it's close to reality:

An agent without write_todos is like an engineer who starts coding the moment they receive requirements — high energy, but prone to overlooking things they haven't fully thought through.

With write_todos, the agent has a chance to stop and say: "Okay, this task has five steps: A, B, C, D, E. I'll start with A." Then it advances methodically.

The next article addresses another problem: where does all the information generated during a task go?


Previous: Quickstart: Build Your First Deep Agent in 5 Minutes Next: Filesystem Tools: Manage Context with Files, Don't Let Tokens Explode