TodoTools collection manages the plan (the Planning panel in the UI): the agent’s own execution ledger of what it has done and what remains. One instance is created per thread, bound to its thread_id, so every tool targets the right conversation without thread_id being an LLM-facing parameter.
The plan is an arbitrarily-nested, depth-first (pre-order) tree. Every step is addressed by a stable 1-based flat index (its position in the pre-order list, exactly what list_todos prints). The dotted outline numbers (1, 1.1, 1.1.1, 2) are display-only; the tools always take the flat index. Every mutating tool is batch-shaped: it takes a list so the agent can act on many steps in one call (pass a one-element list to act on a single step). There are deliberately no singular variants.
Shared conventions (the @workspace_tool contract, the {success, summary} JSON envelope, and the injected action_and_reasoning argument) are documented in Tools. Every tool below also receives the injected action_and_reasoning argument described there; it is not listed as an argument row.
Source: backend/app/tools/todos.py
add_todos
Add one or more steps to the plan as a nested outline, either appended at the end or nested under an existing step.
Description shown to the agent:
Add todo steps to the plan in one call, passed as a nested outline (each step hasArgumentstextand adepth). Passparent_indexto nest the whole outline as sub-steps under an existing todo step; omit it to append at the end as top-level steps. Use this for every add, pass a one-element list to add a single step.
Each item in
steps is a PlanStep object:
Returns: a confirmation string with the number of steps added, e.g.
added 3 todo steps. This becomes the summary of the success envelope.
Implementation: maps each PlanStep to {text, depth} and calls WorkspaceRepository.add_todos_bulk(thread_id, items, parent_index=...), which inserts the outline into the pre-order tree at the chosen position. Source: backend/app/tools/todos.py.
toggle_todos
Flip one or more steps between done and not-done.
Description shown to the agent:
Toggle one or more todo steps done / not-done by their 1-based indices (pass a list; a single step is a one-element list).Arguments
Returns: a summary of the resulting statuses, e.g.
toggled 2 todo steps: #1 now done, #2 now pending, or no matching todo steps found when no index is in range.
Implementation: reads the current plan, resolves each in-range index to its row id up front (so the indices reference the plan as it stood at call time), then calls repo.toggle_todo(todo_id) per target. Source: backend/app/tools/todos.py.
update_todos
Rewrite the text of one or more existing steps, keeping their position, depth, and done status.
Description shown to the agent:
Rewrite the text of one or more existing todo steps. Pass a list of {index, text}; a single edit is a one-element list.
Arguments
Each item in
updates is a TodoTextUpdate object:
Returns: a confirmation string with the number of steps updated, e.g.
updated 2 todo steps, or no matching todo steps found when no index is in range.
Implementation: resolves indices against the plan at call time (before any edit, so they do not shift as steps are rewritten), then calls repo.update_todo_text(todo_id, text) per target. Source: backend/app/tools/todos.py.
remove_todos
Delete one or more steps; removing a parent step also removes all of its sub-steps.
Description shown to the agent:
Delete one or more todo steps by their 1-based indices (pass a list; a single step is a one-element list). NOTE: removing a parent todo step also removes ALL of its sub-steps. Use when the plan changes and todo steps no longer apply.Arguments
Returns: a confirmation string with the total number of steps removed, e.g.
removed 4 todo steps, or no matching todo steps found when no index is in range.
Implementation: resolves all target row ids up front, then calls repo.remove_todo_subtree(thread_id, todo_id) per target, which cascades to the step’s descendants. Because removal cascades, an index that named a sub-step already taken out by its parent is simply skipped. Source: backend/app/tools/todos.py.
list_todos
Read the current plan with its numbers, nesting, and statuses. Rarely needed, since the plan is injected into the agent’s context every turn.
Description shown to the agent:
List the current plan with numbers, nesting, and statuses.Arguments None (the agent still passes the injected
action_and_reasoning).
Returns: newline-separated lines of the form #N D.D [x] text, where N is the 1-based flat index that every todo tool expects and D.D is the dotted outline number reflecting nesting ([x] marks done, [ ] pending), or (no todo steps yet) when the plan is empty.
Implementation: reads repo.list_todos(thread_id) and renders each row through compute_dotted_numbers (in _planning_format.py) to produce the dotted outline numbers. Source: backend/app/tools/todos.py.