Skip to content

fix(agent): seed agent todoState from plan-seed so complete_step advances counter#4230

Closed
ashishexee wants to merge 1 commit into
esengine:main-v2from
ashishexee:fix/4170-todo-progress-stuck
Closed

fix(agent): seed agent todoState from plan-seed so complete_step advances counter#4230
ashishexee wants to merge 1 commit into
esengine:main-v2from
ashishexee:fix/4170-todo-progress-stuck

Conversation

@ashishexee

Copy link
Copy Markdown
Contributor

Fixes #4170

Also addresses #4169 (UI shows only 1 completion instead of all).

Problem

When a plan is approved, the controller emits a plan-seed todo_write event so the frontend task panel populates immediately. However, the controller did not initialize the agent's todoState — that internal state is only set when the model calls todo_write itself.

Since the plan-seed already displays the list, the model often skips calling todo_write and goes straight to complete_step. When complete_step runs, advanceCanonicalTodo checks len(todoState) == 0 and returns early — no synthetic todo_write update event is emitted, so the frontend counter stays at 0/x.

Root Cause

seedPlanTodos in controller.go emits UI events but does not initialize the agent's canonical todo state. The agent and the frontend were out of sync: the frontend had the list, but the agent did not.

Fix

  1. Agent.SeedTodoState() — new public method that initializes todoState when empty (no-op if already set, so a model's own todo_write is never overwritten)
  2. Controller.seedAgentTodoState() — parses the plan-seed args into []evidence.TodoItem and calls SeedTodoState
  3. Called from seedPlanTodos after emitting the plan-seed events

Now when complete_step runs after a plan-seed:

  • todoState is populated → advanceCanonicalTodo finds the matching item
  • The item is marked completed + next item promoted → synthetic todo_write emitted
  • Frontend updates the counter: 1/5 → 2/5 → ... → 5/5

Tests

  • TestSeedTodoState — seeds empty agent
  • TestSeedTodoStateNoOverwrite — no-op when agent already has state
  • TestSeedTodoStateAllowsAdvanceAfterSeed — proves the full flow: seed → advance → counter updates
  • TestSeedPlanTodosSeedsAgentState — controller integration: plan-seed also seeds agent
  • TestSeedPlanTodosEmptyPlanNoOp — empty plan is a no-op

Files Changed

  • internal/agent/agent.goSeedTodoState() method
  • internal/agent/canonical_todo_test.go — 3 new agent tests
  • internal/control/controller.goseedAgentTodoState() helper + call from seedPlanTodos
  • internal/control/plan_seed_test.go — 2 new controller tests

@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development agent Core agent loop (internal/agent, internal/control) labels Jun 13, 2026
…nces counter

Fixes esengine#4176

When a plan is approved, the controller emits a plan-seed todo_write event
(frontend shows the list) but did not initialize the agent's todoState.
The agent's todoState is only set when the model calls todo_write itself,
which it may skip since the plan-seed already displays the list.

When the model then calls complete_step, advanceCanonicalTodo checks
len(todoState) == 0 and returns early — no synthetic todo_write event is
emitted, so the frontend counter stays at 0/x.

Fix: after seedPlanTodos emits the plan-seed events, also call the new
Agent.SeedTodoState() to initialize the agent's canonical todo list from
the parsed plan. SeedTodoState is a no-op when the agent already has a
todoState (e.g. the model already called todo_write), avoiding overwrites.

Copy link
Copy Markdown
Collaborator

Thanks again for this contribution. I opened #4313 as an integration PR that includes this PR's plan-seed fix: approved plan todos now seed the agent's canonical todoState, so later complete_step calls can advance the visible todo panel.

#4313 combines that contribution with the related restored-history and completed-panel work from #4159, #4271, and #4309, plus the additional success/error replay guards found during review. To keep review focused, I’m closing this PR as superseded by #4313. You are credited in the integration commit and PR body.

@SivanCola SivanCola closed this Jun 13, 2026
esengine pushed a commit that referenced this pull request Jun 14, 2026
…#4313)

Seed the agent's canonical todo state from approved plans so later complete_step calls advance the visible panel, and rebuild restored desktop history by replaying todo_write/complete_step only on explicitly successful tool results (missing or errored results no longer count as completed). Hide the panel once all todos are done, keyed to stable content rather than array identity. Integrates #4159, #4230, #4271, and #4309.

Closes #4259
Closes #4105
Closes #4170
Closes #4241

Co-authored-by: JesonChou <51042251+JesonChou@users.noreply.github.com>
Co-authored-by: ashishexee <144021866+ashishexee@users.noreply.github.com>
Co-authored-by: GTC2080 <140309575+GTC2080@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Core agent loop (internal/agent, internal/control) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] TODO progress counter never updates — stuck at 0/x

2 participants