Todo Write
Feature Definition
Section titled “Feature Definition”A todo-write tool gives the model a structured way to update task-progress state during execution.
Unlike a one-time plan proposal, todo write is a state mutation primitive that happens repeatedly during a turn:
- create initial task list
- mark active task
in_progress - mark finished tasks
completed - add/remove/cancel tasks as scope changes
The value is not only organization. It is observability: users can see what the agent thinks it is doing, in what order, and what remains.
Why Writing Todo State Is Hard
Section titled “Why Writing Todo State Is Hard”Todo-write looks simple, but production behavior is sensitive to update semantics:
- Is each write a full replacement or partial patch?
- Are statuses strongly typed or free-form strings?
- Is there a single-writer assumption (one agent) or multi-writer concurrency?
- How are write events propagated to UI, API, and headless clients?
If the contract is vague, todo state becomes inconsistent and misleading.
See Also
Section titled “See Also”- Planning for plan-authoring mode semantics vs checklist state mutation.
- Todo Read for read-path guarantees and consistency constraints.
- Chat History for persistence/compaction pressure that makes typed todo state useful.
Aider Implementation
Section titled “Aider Implementation”Aider at commit b9050e1d5faf8096eae7a46a9ecc05a86231384b has no first-class todo-write tool.
No Dedicated Todo Mutation Primitive
Section titled “No Dedicated Todo Mutation Primitive”Aider exposes planning-adjacent chat modes (/ask, /architect) in aider/commands.py, but no cmd_todo or model-callable todo writer.
State tracking is conversational rather than structured:
- model describes next steps in natural language
- host/runtime does not persist a typed checklist object
- UI cannot render task status transitions from a canonical event stream
Planning Text in Some Function Payloads Is Not Todo State
Section titled “Planning Text in Some Function Payloads Is Not Todo State”Legacy function-coder variants include explanation fields described as step-by-step plans:
aider/coders/single_wholefile_func_coder.pyaider/coders/wholefile_func_coder.pyaider/coders/editblock_func_coder.py
These fields are tied to edit payloads and not managed as an independent, mutable todo list.
Operational Consequence
Section titled “Operational Consequence”Aider can still complete complex tasks effectively, but task tracking is opaque to external clients. There is no typed todo.updated equivalent to drive structured progress UI.
Codex Implementation
Section titled “Codex Implementation”Codex at commit 4ab44e2c5cc54ed47e47a6729dfd8aa5a3dc2476 uses update_plan as its todo-write pathway.
update_plan Is the Todo Write Interface
Section titled “update_plan Is the Todo Write Interface”core/src/tools/handlers/plan.rs defines PLAN_TOOL named update_plan with parameters:
explanation?plan[]- each item has
stepandstatus
- each item has
Typed status enum in protocol/src/plan_tool.rs:
PendingInProgressCompleted
There is no cancelled status in this schema.
Handler Behavior
Section titled “Handler Behavior”Write flow:
- parse JSON arguments into
UpdatePlanArgs - reject call in collaboration
Planmode - emit
EventMsg::PlanUpdate(args) - return
Plan updated
The authoritative side effect is event emission, not tool output text.
Projection to Todo Items in Exec/SDK Layer
Section titled “Projection to Todo Items in Exec/SDK Layer”exec/src/event_processor_with_jsonl_output.rs transforms PlanUpdate events into todo_list item lifecycle for thread output:
- first write in turn ->
ItemStarted - subsequent writes ->
ItemUpdated - turn end ->
ItemCompleted
Mapping rule:
StepStatus::Completed=>TodoItem.completed=truePending/InProgress=>completed=false
This projection is what SDK consumers see (sdk/typescript/src/items.ts TodoListItem).
UI Rendering
Section titled “UI Rendering”TUI path:
tui/src/chatwidget.rsreceivesEventMsg::PlanUpdatetui/src/history_cell.rsrenders checklist-style cell
Visual semantics:
- completed: checked/struck style
- in-progress: highlighted open box
- pending: dimmed open box
App-Server Forwarding
Section titled “App-Server Forwarding”app-server/src/bespoke_event_handling.rs maps EventMsg::PlanUpdate to TurnPlanUpdatedNotification (API v2), with explicit code comment clarifying that this checklist update is distinct from plan-mode streaming.
Test Coverage
Section titled “Test Coverage”Codex explicitly tests todo-write semantics:
core/tests/suite/tool_harness.rs- emits PlanUpdate on valid payload
- rejects malformed payload
exec/tests/event_processor_with_json_output.rs- validates started/updated/completed todo lifecycle
Limitations
Section titled “Limitations”- prompt text recommends at most one
in_progress, but handler does not enforce cardinality - no priority field
- no cancelled/skipped status in typed contract
OpenCode Implementation
Section titled “OpenCode Implementation”OpenCode at commit 7ed449974864361bad2c1f1405769fd2c2fcdf42 has a dedicated todowrite tool plus storage/event pipelines.
Tool Contract
Section titled “Tool Contract”packages/opencode/src/tool/todo.ts defines:
- tool id:
todowrite - params:
todos: Todo.Info[]
Todo.Info schema (session/todo.ts) uses fields:
contentstatuspriority
Status and priority are currently free-form z.string() values (described expected values, not strict enums).
Permission Gate
Section titled “Permission Gate”Before writing, tool calls:
ctx.ask({ permission: "todowrite", patterns: ["*"], always: ["*"] })
So todo writes are permission-governed operations, not unconditional state mutations.
Storage Semantics: Full Replacement Transaction
Section titled “Storage Semantics: Full Replacement Transaction”Todo.update(...) in session/todo.ts performs replace-all in one DB transaction:
- delete all existing todos for session
- insert new list with positional ordering
Backed by TodoTable in session/session.sql.ts with composite primary key (session_id, position).
This is deterministic and simple, but means every write must include the complete desired list.
Event Publication and UI Sync
Section titled “Event Publication and UI Sync”After write, Todo.update publishes todo.updated bus event.
Consumers:
- TUI sync store (
cli/cmd/tui/context/sync.tsx) listens fortodo.updated - TUI session route (
cli/cmd/tui/routes/session/index.tsx) renderstodowritetool parts - CLI run mode (
cli/cmd/run.ts) prints checklist-style block fortodowrite - ACP bridge (
acp/agent.ts) parses todo output and forwards as plan entries
Prompt-Level Pressure to Use TodoWrite
Section titled “Prompt-Level Pressure to Use TodoWrite”Multiple provider/system prompt templates strongly encourage frequent todo updates, especially for complex tasks:
session/prompt/anthropic.txtsession/prompt/copilot-gpt-5.txt- tool guidance in
tool/todowrite.txt
So OpenCode couples runtime support with prompt policy to increase usage frequency.
Pitfalls & Hard Lessons
Section titled “Pitfalls & Hard Lessons”Replace-All Writes Can Accidentally Drop Tasks
Section titled “Replace-All Writes Can Accidentally Drop Tasks”If the model writes only “changed” tasks without re-sending unchanged tasks, replace-all semantics delete omitted entries.
Weak Status Typing Causes Drift
Section titled “Weak Status Typing Causes Drift”OpenCode describes expected statuses (pending, in_progress, completed, cancelled) but schema accepts any string, so typos can silently corrupt downstream UI logic.
Prompt Guidance Is Not Data Integrity
Section titled “Prompt Guidance Is Not Data Integrity”Codex and OpenCode both guide “one in_progress” behavior in prompts/docs. Without handler-level validation, invalid multi-active states still pass.
Multi-Agent Work Requires Ownership Rules
Section titled “Multi-Agent Work Requires Ownership Rules”If multiple agents can write todos concurrently without merge strategy, status thrash is unavoidable. Subagent permission-deny defaults for todo tools are a practical containment strategy.
Tool Output Is Not the Source of Truth
Section titled “Tool Output Is Not the Source of Truth”The write acknowledgement string is secondary. Durable truth should come from persisted state + emitted events.
OpenOxide Blueprint
Section titled “OpenOxide Blueprint”Tool Contract
Section titled “Tool Contract”Define todo_write with explicit update mode:
mode: replace | patchitems[]with strongly typed fields- optional
explanation
Default to replace for deterministic behavior, but support patch for safer incremental updates.
Strong Typing and Validation
Section titled “Strong Typing and Validation”Use enums:
- status:
pending | in_progress | completed | cancelled | blocked - priority:
high | medium | low
Enforce invariants server-side:
- max one
in_progress - unique task ids
- stable ordering
Persistence and Concurrency
Section titled “Persistence and Concurrency”Store todo rows with:
- stable
task_id position- optimistic concurrency field (
version)
Reject stale writes when version mismatches to prevent concurrent clobbering.
Event Model
Section titled “Event Model”Emit explicit events:
TodoListReplacedTodoPatchedTodoInvariantViolation
Provide a normalized TodoSnapshot event for easy client hydration.
UI + API
Section titled “UI + API”All clients should consume the same canonical snapshot model. Tool output text can remain human-readable, but rendering must come from structured event payloads.
Crates
Section titled “Crates”serde/schemarsfor schemasqlxorrusqlitefor persistencetokio::sync::watchor broadcast channels for updatesratatuiwidgets for checklist visualization
Key Design Decisions
Section titled “Key Design Decisions”- Treat todo write as a state mutation API, not a chat convenience.
- Enforce invariants in handler code.
- Support both deterministic replace and safe patch workflows.
- Use stable task ids to make updates mergeable and auditable.