write_todos: planning with TodoListMiddleware
write_todos is a planning tool that lets a deep agent write its task as a to-do list and mark each item pending, in progress or completed; in Deep Agents 0.7 you add it with TodoListMiddleware.
Last updated: 29 Sep, 2026 · Deep Agents 0.7
middleware=[TodoListMiddleware()] to create_deep_agent, and the agent gets the write_todos tool and a todos list in its state. Without it, neither exists.The video's Paris trip to-do list
The crash course explains planning with the trip this course builds. The request: a holiday in Paris, three nights and four days, for 100,000 rupees. Before any tool runs, the deep agent writes a to-do list: travel on day one and stay in a hotel at a set price, visit the Eiffel Tower on day two, another place on day three, fly back on day four, with each day's cost. Each item can go to a subagent, the system prompt says how the agents behave, and the file system is where they leave their work for each other.
Adding TodoListMiddleware
from langchain.agents.middleware import TodoListMiddleware
agent = create_deep_agent(model=model, tools=[...], middleware=[TodoListMiddleware()])
result["todos"] # [{"content": "...", "status": "completed"}, ...]A tool for exact totals
A trip plan adds up many prices, and a model's mental arithmetic is not reliable. add_costs adds a list of rupee amounts in Python and returns the exact total. Put it in trip.py under search_travel, the tool from Tools: a travel search the agent can call. The file now reads:
from langchain.tools import tool
CATALOG = {
"paris": {
"flight": ["Return flight Delhi to Paris: 42,000 rupees"],
"hotel": ["Seine Budget Inn, Latin Quarter: 5,200 rupees a night",
"Hotel Lumiere, Montmartre: 7,500 rupees a night",
"Le Grand Opera Hotel: 16,000 rupees a night"],
"sight": ["Eiffel Tower summit: 3,100 rupees", "Louvre Museum: 2,000 rupees",
"Seine river cruise: 1,500 rupees", "Versailles day trip: 2,600 rupees",
"Montmartre walking tour: free"],
"food": ["Cafe breakfast and bistro dinner: 3,000 rupees a day"],
},
}
@tool
def search_travel(city: str, kind: str) -> str:
"""Search the travel catalog. kind is "flight", "hotel", "sight" or "food". Prices are in rupees."""
entries = CATALOG.get(city.lower(), {}).get(kind)
return "\n".join(entries) if entries else f"The catalog has no {kind} entries for {city}."
@tool
def add_costs(amounts: list[int]) -> int:
"""Add rupee amounts and return the exact total. Use it for every sum."""
return sum(amounts)The planning agent
Create the model and the agent. The prompt asks for the plan first, a lookup for every price, and add_costs for every sum.
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model
model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0, max_retries=6)from langchain.agents.middleware import TodoListMiddleware
agent = create_deep_agent(
model=model,
tools=[search_travel, add_costs],
middleware=[TodoListMiddleware()], # adds the write_todos tool
system_prompt="You are a travel planner. Write a todo list with write_todos first, then work through it. "
"Look up every price with search_travel and use add_costs for every total. "
"Reply with one line per day and the total cost.",
)Planning the video's Paris trip
Send the video's request and print the finished to-do list, then the reply. A run makes about ten model calls, so it takes a minute or two on the free tier:
request = "Plan a 3-night, 4-day trip to Paris from Delhi for a budget of 100,000 rupees."
result = agent.invoke({"messages": [{"role": "user", "content": request}]})
for todo in result["todos"]:
print(f"[{todo['status']}] {todo['content']}")
print()
print(result["messages"][-1].text)[completed] Search round-trip flight cost from Delhi to Paris [completed] Search hotel cost per night in Paris [completed] Search average daily food cost in Paris [completed] Search average daily sightseeing cost in Paris [completed] Calculate total cost and ensure within budget [completed] Create 4-day itinerary with cost breakdown per day Day 1 – Arrive in Paris, check‑in, Montmartre walking tour (free) – Hotel ₹5,200 + Food ₹3,000 = ₹8,200 Day 2 – Eiffel Tower summit ₹3,100 + Seine river cruise ₹1,500 + Hotel ₹5,200 + Food ₹3,000 = ₹12,800 Day 3 – Louvre Museum ₹2,000 + Versailles day‑trip ₹2,600 + Hotel ₹5,200 + Food ₹3,000 = ₹12,800 Day 4 – Departure, breakfast/lunch – Food ₹3,000 = ₹3,000 **Total cost (including round‑trip flight ₹42,000)** = **₹78,800**, which is within the ₹100,000 budget.
What the to-do list shows
- The plan came first: the agent broke the trip into lookups (flight, hotel, food, sights), a total, and the itinerary itself.
- Every item is completed: the agent calls
write_todosagain as it works, each time sending the whole list with new statuses. The list you print is the last one. - The total is right: 78,800 rupees is the 42,000 flight, three nights at the cheapest hotel, four days of food and the four paid sights. The prompt told the agent to add with
add_costs; the Try it list shows how to print every tool call. - The trip fits: the plan comes in under the 100,000 rupee budget.
A plain deep agent vs one with write_todos
| create_deep_agent (0.7 default) | with TodoListMiddleware | |
|---|---|---|
| write_todos tool | Not offered | Offered to the model |
result["todos"] | Missing | The latest list, with statuses |
| Good for | Short tasks, strong models | Long tasks, weaker models, progress UIs |
When to add planning
- Tasks with five or more steps, such as a trip with flights, hotels, sights and a budget.
- Smaller or cheaper models, which follow a written list more reliably than a plan in their head.
- Apps that show progress: the
todoslist can be streamed to a UI as it changes.
write_todos. Say "write a todo list first" in the system prompt when you want a plan every time.Related
- Previous: Streaming a deep agent's steps
- Next: Virtual filesystem: files in the agent's state
- Reference: Deep Agents task planning
- Change the budget to 60,000 rupees and read how the plan and the reply change.
- Remove
middleware=[TodoListMiddleware()]and check that"todos" in resultis nowFalse. - Print
[c["name"] for m in result["messages"] for c in getattr(m, "tool_calls", [])]and find theadd_costscalls.
You understood something today that you didn't yesterday.