21 · Todo Is Not a Plan: What Survives Compaction?
Keep approval plans, execution todos, and durable tasks separate so compaction and resume preserve a verifiable next action.
Chapter brief
Question to answer
Among approval plans, execution todos, and durable tasks, which state must survive compaction and recovery?
By the end, you can
- Separate human-approved plans, loop-execution todos, and durable tasks
- Define owner, lifetime, and persistence for each state class
- Test compaction, restart, reorder, and completion claims
- Read this now if
- Engineers designing plans, progress lists, recovery state, or multi-agent task trees
- Prerequisites
- Understand context compaction and session recovery
- Deliverable
- A task-state schema and compaction-recovery acceptance suite
- Evidence boundary
- A visible todo is an execution surface, not the source of truth or proof of completion
A todo is not a plan: what survives compaction?
Section titled “A todo is not a plan: what survives compaction?”Scenario: a user approves a five-step migration. After three steps the agent compacts context, preserving only “3/5 complete” while stable todo IDs, verification state, and prerequisites disappear. On resume it may redeploy step three or skip step four’s rollback check.
Passing conditions: approval plans, execution todos, and durable tasks use distinct types and IDs; each item records owner, state, evidence, and dependencies; compaction creates a view without rewriting truth; completion claims link verifier results.
A todo tells the agent what to do next. A plan waits for approval. A durable task must continue across processes. Put them in one table and resume will turn “proposed” into “started”.
Keep three state surfaces separate
Section titled “Keep three state surfaces separate”| Surface | Stores | On compaction or resume |
|---|---|---|
| Approval plan | A proposal the user has not accepted | Show as proposal; do not mark it running |
| Execution todo | Unfinished steps and current focus | Re-inject pending and in_progress only |
| Durable task | Owner, dependencies, retries, terminal state | Restore from events or a task file |
Codex rejects update_plan in Plan Mode. Claude Code splits TodoWrite from Tasks V2. Hermes re-injects unfinished items after compaction. OpenClaw relies more on runtime events and child-session relay. Compare them only when you need one of those recovery contracts.
A minimum state that changes the next action
Section titled “A minimum state that changes the next action”{"id":"t-1","content":"run targeted tests","status":"in_progress","activeForm":"Running targeted tests","updated_at":"2026-08-10"}Completion should carry verification evidence, not just the model’s assertion. A single-agent CLI can replace the whole list. Add owner, blockers, claims, and locks when work crosses processes or people.
Before shipping, ask:
- Is there at most one
in_progressitem? - Do completed items leave active context while remaining auditable?
- Does resume use the latest successful structured state instead of guessing from prose?
- Can a reader distinguish Plan, Todo, and Task in the API and UI?
Source notebook: implementation details
Section titled “Source notebook: implementation details”Source notebook: implementation details
How four systems implement task state
Section titled “How four systems implement task state”Codex · checklist, not Plan Mode
Section titled “Codex · checklist, not Plan Mode”Codex keeps the protocol intentionally small:
Codex codex/codex-rs/protocol/src/plan_tool.rs:9-28 The checklist schema has three statuses and simple step/status items.
pub enum StepStatus { Pending, InProgress, Completed,}
pub struct PlanItemArg { pub step: String, pub status: StepStatus,}The important part is the boundary in the handler: if the turn is in Plan Mode, update_plan fails because it is a TODO/checklist tool.
Codex codex/codex-rs/core/src/tools/handlers/plan.rs:75-89 Codex rejects update_plan in Plan Mode and otherwise emits PlanUpdate.
if turn.collaboration_mode.mode == ModeKind::Plan { return Err(FunctionCallError::RespondToModel( "update_plan is a TODO/checklist tool and is not allowed in Plan mode".to_string(), ));}session.send_event(turn.as_ref(), EventMsg::PlanUpdate(args)).await;The TUI then counts completed / total, updates status surfaces, and renders an Updated Plan history cell. That makes progress a protocol event and UI state, not only model narration.
Codex has another easy-to-miss path: proposed plans emitted in Plan Mode are parsed into PlanDelta and streamed through the app-server v2 item/plan/delta notification, while successful update_plan calls become turn/plan/updated.
That is two protocol channels: streaming proposal text and tool-submitted execution checklist.
Claude Code · TodoWrite for session focus, Tasks V2 for durable work
Section titled “Claude Code · TodoWrite for session focus, Tasks V2 for durable work”Claude Code makes the behavior explicit in the TodoWrite prompt: use it for complex multi-step work, mark a task in_progress before starting it, mark it completed only when fully done, and provide both imperative content and present-continuous activeForm.
claude-code/src/tools/TodoWriteTool/prompt.ts:147-166 TodoWrite defines the states, activeForm, single in_progress rule, and completion standard.
- pending: Task not yet started- in_progress: Currently working on (limit to ONE task at a time)- completed: Task finished successfully- activeForm: The present continuous form shown during execution- ONLY mark a task as completed when you have FULLY accomplished itThe legacy TodoWrite path stores todos in AppState by agentId or sessionId, clears the list when all tasks are done, and can nudge verification when many tasks are closed without a verification item.
Tasks V2 is a different layer: file-backed tasks with ids, subjects, descriptions, owner fields, blockers, claim checks, and metadata. That belongs to IDE and team workflows rather than the minimal current-focus checklist.
Tasks V2 is not just a bigger TodoWrite. TaskUpdateTool is enabled only when Todo V2 is enabled, and its schema can change status, owner, activeForm, blocks, blockedBy, and metadata. attachments.ts also maintains separate stale-update reminders for TodoWrite and for TaskCreate / TaskUpdate.
Session focus and team task boards have different reminder loops.
Hermes · tiny state, compaction-aware injection
Section titled “Hermes · tiny state, compaction-aware injection”Hermes keeps a single in-memory TodoStore per agent/session. It supports replace and merge writes, adds a cancelled state, and exposes everything through one todo tool.
One notable implementation choice is its compaction behavior: only unfinished items return to active context.
Hermes hermes-agent/tools/todo_tool.py:90-118 Hermes only injects pending and in_progress todos after compaction.
visible = [ item for item in self._items if item["status"] in ("pending", "in_progress")]if not visible: return NoneThe runtime can hydrate the store from the latest todo tool response in history. That keeps todo state session-scoped without promoting it into long-term memory.
Hermes also adds runtime progress through its ACP adapter. make_tool_progress_cb() maps tool.started to ACP ToolCallStart and tracks duplicate same-name tool calls with a FIFO queue; make_step_cb() consumes completed tools from prev_tools and emits completion updates.
This is tool-fact progress, not model-authored todo state.
OpenClaw · runtime progress events instead of a model-owned checklist
Section titled “OpenClaw · runtime progress events instead of a model-owned checklist”OpenClaw focuses on progress projection. Its ACP translator emits tool_call with in_progress when a tool starts, and tool_call_update with completed or failed when a tool ends.
The auto-reply projector turns those events into visible tool summaries. The parent-stream relay forwards child-agent progress back to the parent session.
For a multi-channel operator surface, runtime events are a direct source of tool and child-session facts; whether they are more reliable than a model-authored checklist depends on the event coverage and trace.
OpenClaw also shows the guardrail this design needs: no-progress detection. tool-loop-detection.ts distinguishes unchanged polling loops, ping-pong loops, and global repeat circuit breakers; the parent-stream relay has a no-output watcher that emits a stall notice when a child session stops producing output.
Without those detectors, a progress UI can faithfully display that the system is stuck.
What todo systems already agree on
Section titled “What todo systems already agree on”The shared move is to pull progress out of free-form prose. Codex uses protocol events, Claude Code uses tool state and task files, Hermes uses tool JSON, and OpenClaw uses runtime events.
A final answer that says what the agent will do next is not task management because UI, resume, reminders, and compaction cannot reliably consume it.
The state machine stays small. pending / in_progress / completed is enough for the current execution surface. Add failed, cancelled, owner, or blockers only when the runtime really needs them.
One current focus makes a single todo table recoverable. Codex requires at most one in_progress; Claude Code and Hermes use stricter wording. Parallel work can live under a parent task or durable task layer instead of being treated as several top-level focuses.
Completion should lower attention. Claude Code only completes fully accomplished items, Codex crosses completed items out, Hermes does not re-inject completed items after compaction, and TodoWrite clears all-done lists.
Choose checklists, team tasks, or event projection
Section titled “Choose checklists, team tasks, or event projection”Model-owned checklist
- Fits a single-agent coding CLI current-focus surface
- The model knows the next focus
- Easy to render in UI
- Can be marked complete too early
- No owner/blocker semantics
- Needs reminders and resume
File-backed tasks
- Works across IDE windows and agents
- Supports owner and blockers
- Auditable after restart
- Requires locks and migrations
- More complex than current-focus todo
- Easy to overbuild
Runtime event stream
- Grounded in tool and child-agent facts
- Can support an operator UI
- Captures failure states when emitted
- Does not express remaining semantic work alone
- Needs summary continuity
- Needs aggregation and dedupe
Compaction injection
- Can help long-context agents
- Avoids repeating completed work
- Often small to implement
- Depends on history and compression
- Weak audit story
- IDs depend on model quality
The core split is three layers: approval plan, execution todo, and durable task. Plans are for user approval. Todos are for current execution focus. Durable tasks are for background or team work with owners, blockers, locks, and retries.
On resume, keep only state that changes the next action
Section titled “On resume, keep only state that changes the next action”Minimal implementation: keep task state recoverable
Section titled “Minimal implementation: keep task state recoverable”Todo List / Progress Surface
最小可行
- Define a small schema: `id?`, `content`, `status`, optional `activeForm`; start with `pending` / `in_progress` / `completed`.
- Expose an `update_todo` or `update_plan` tool and write updates to both session state and the event stream.
- Enforce at most one `in_progress` item.
- Render the checklist in UI and show completed / total in status surfaces.
- After compaction, inject only pending and in_progress items.
进阶
- Restore from transcript or the latest todo tool result.
- Add reminders when complex work goes many assistant turns without a todo update.
- Add verification nudges before closing many tasks.
- Introduce file-backed tasks only when owner/blocker/cross-process behavior is required.
- Project runtime tool start/update/terminal events separately.
- Add no-progress detectors for unchanged polling, ping-pong loops, or child sessions with no output.
一开始别做
- Do not merge Plan Mode and execution todo.
- Do not store current todos as long-term memory.
- Do not rely on final-answer prose as the progress source.
- Do not allow multiple top-level `in_progress` tasks.
- Do not re-inject completed items into active context.
How task state enters the architecture
Section titled “How task state enters the architecture”Follow task state through source
Section titled “Follow task state through source”What to carry forward and the next experiment
Section titled “What to carry forward and the next experiment”A todo is the current execution control surface, not the approved plan or background-task truth. The three may derive from one another, but they cannot share an ambiguous completed boolean.
Next experiment: build a five-step task with dependencies, reversible actions, and a human gate. After step three, trigger compaction, restart, reorder, and a user plan change. Pass when stable IDs, evidence, next action, and dependencies survive; committed effects do not repeat; old plans never overwrite newly approved content.
Appendix: exercises and review
Section titled “Appendix: exercises and review”Open the exercises and ten review questions
Exercises
Section titled “Exercises”- Add an
update_task_progresstool withcontent,status, and optionalactiveForm; reject multiplein_progressitems. - Emit todo updates as events and render completed / total in a CLI or Web UI.
- Run a compaction test: completed items should disappear from active context, unfinished items should remain.
- Simulate approval-only Plan Mode followed by execution and verify they use different state.
- Add an internal reminder after 10 assistant turns without a todo update, and ensure the reminder is not shown to the user.
Review questions
Section titled “Review questions”Q1 · Concept: Why is a todo list not Plan Mode?
Plan Mode is for approval before execution. A todo list is execution state after work has started. Codex enforces this distinction in code by rejecting update_plan while the collaboration mode is Plan Mode.
Q2 · Design: Why allow at most one in_progress item?
The todo list needs a current focus. Multiple active top-level tasks make resume, UI status, and next-action selection ambiguous. Parallel tool calls can happen inside one active task; that is not the same as several top-level tasks.
Q3 · UI: What does activeForm buy in Claude Code?
It gives UI a present-continuous phrase such as Running tests, separate from the imperative task content such as Run tests. That makes status bars and spinners read naturally without guessing grammar from the task title.
Q4 · Recovery: Why should completed items not be re-injected?
Completed items have audit value but little active execution value. Re-injecting them wastes context and can cause repeated work. Hermes only injects pending and in_progress items after compaction.
Q5 · Resume: Why restore TodoWrite from transcript?
Session AppState can disappear in SDK or non-interactive flows. Restoring from the last TodoWrite tool use gives the agent a stable unfinished-work surface after resume.
Q6 · Trade-off: When do you need file-backed Tasks?
Use file-backed tasks when work crosses processes, IDE windows, agents, or owners. You need ids, locks, owner fields, blockers, claim checks, and durable JSON. For a single-agent CLI, a small checklist is usually enough.
Q7 · OpenClaw: Why use progress events instead of a model-owned checklist?
In a multi-channel runtime, the most reliable progress facts come from tools, child sessions, and delivery state. Runtime events are easier to project to operators than a model-authored checklist.
Q8 · API: Replace or merge?
Replace is simpler and safer for a short current checklist because the model submits the whole truth each time.
Merge is useful for long-running lists where partial updates and discovered branches matter, but it requires stable ids and cleanup.
Q9 · Verification: Why add a verification nudge?
Agents often treat code written as work completed. A verification nudge forces completion to be backed by tests, lint, HTTP smoke, screenshot, or another concrete signal.
Q10 · Checklist: Which progress surfaces does a task shape need?
One testable starting point is a tiny schema, an explicit current focus, event updates to UI and transcript, unfinished-work recovery after compaction or resume, and evidence matched to the completion risk. A single-agent CLI may not need every additional surface.
Add reminders, durable tasks, and runtime progress events only when the product shape needs them.