DeerFlow 2.0 — TodoMiddleware & LangChain TodoListMiddleware
DeerFlow 2.0 — TodoMiddleware & LangChain TodoListMiddleware
Two GitHub sources read together:
| File | Commit |
|---|---|
langchain-ai/langchain · libs/langchain_v1/langchain/agents/middleware/todo.py |
311675a |
bytedance/deer-flow · backend/packages/harness/deerflow/agents/middlewares/todo_middleware.py |
259a684 |
LangChain TodoListMiddleware (base class)
Data models
Todo— TypedDict:content: str,status: "pending" | "in_progress" | "completed"PlanningState— extendsAgentState; addstodos: list[Todo](excluded from input)WriteTodosInput— Pydantic model validatingwrite_todostool input
write_todos tool
- Replaces the entire todo list atomically (no partial edits).
- Returns a
Commandupdatingtodosin state and appending aToolMessagewith confirmation. - Sync (
_write_todos) and async (_awrite_todos) variants; both injecttool_call_idfromToolRuntime.
System prompt injection
wrap_model_call / awrap_model_call append guidance to every model request:
- When to use: complex multi-step tasks (≥ 3 steps), non-trivial objectives, explicit user request.
- When to skip: single straightforward tasks, trivial operations.
- Rules: mark
in_progressbefore starting each task; markcompletedimmediately upon finishing; never callwrite_todosin parallel.
after_model enforcement
Detects when the model issues multiple parallel write_todos tool calls (ambiguous because the tool replaces the whole list). Returns error ToolMessages for each violating call.
DeerFlow TodoMiddleware (extends TodoListMiddleware)
Adds two new failure-mode mitigations on top of the base class.
Helper functions
| Function | Purpose |
|---|---|
_todos_in_messages(messages) |
True if any AIMessage has a write_todos tool call |
_reminder_in_messages(messages) |
True if a HumanMessage(name="todo_reminder") exists |
_completion_reminder_count(messages) |
Count of HumanMessage(name="todo_completion_reminder") |
_format_todos(todos) |
Format todos as - [status] content lines |
before_model — context-loss recovery
When context is truncated (e.g. by summarization), the original write_todos call disappears from the window. The hook fires when:
todosis non-empty in state, AND- No
write_todoscall is visible in messages, AND - No
todo_reminderalready injected
Injects a HumanMessage(name="todo_reminder") with the current todo list inside <system_reminder> tags.
after_model — premature-exit prevention
Fires when the model produces a final AIMessage (no tool calls) but incomplete todos remain. Decorated with @hook_config(can_jump_to=["model"]).
- Cap:
_MAX_COMPLETION_REMINDERS = 2(avoids infinite loops). - Injects
HumanMessage(name="todo_completion_reminder")listing incomplete items. - Returns
{"jump_to": "model", "messages": [reminder]}to force another iteration.
Design notes
- Uses LangGraph's
jump_tostate field (standard middleware contract, not a hack). - Both
before_modelandafter_modelare idempotent: repeated calls check for existing reminders before injecting. - The retry cap is a hard upper bound; the base class's parallel-call check still applies via
super().after_model().