fix(tasks): plan panel showed only the latest step, not the full plan

Root cause: proposePlan unconditionally deleted and replaced the whole
session_plan_steps list on every call. The model isn't strictly held to
"call propose_plan once with the full list" — nothing stopped it (and
production evidence + live testing showed it happening) from calling
propose_plan once per step as it worked. Each such call wiped every
already-completed step, so the operator only ever saw the model's latest
single step ("1/1") instead of the real, growing plan.

Fix, two layers:
- store.go: proposePlan now only does a destructive replace when no step
  has left 'pending' yet (a genuine pre-execution revision). Once any step
  has started, a new call APPENDS after the current max seq instead of
  wiping — so the panel accumulates the full history regardless of how the
  model chooses to call the tool. plan.proposed now carries `appended` so
  the frontend knows whether to replace or append.
- workspace.ts: plan.proposed handler respects `appended` (update vs set).
- tasks.go / SOUL.md: strengthened the propose_plan description and task-
  loop guidance to call it ONCE with the complete step list end-to-end,
  using update_plan_step (not re-calling propose_plan) to advance — fixing
  the root behavioral cause, with the store-side append as a safety net
  that holds even if the model still calls it incrementally.

Verified: forced the exact incremental-call pattern (propose_plan with 1
step, mark it running, propose_plan again with 1 more step) — the second
call appended at seq 2 instead of erasing seq 1, and its plan.proposed
event carried appended=true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 14:07:10 +02:00
parent 991e7d0900
commit 5384499903
4 changed files with 63 additions and 26 deletions

View File

@@ -33,11 +33,15 @@ func taskToolDefs() []toolDef {
},
{
Name: "propose_plan",
Description: "Lay out the ordered steps you'll take to reach the goal. The " +
"operator sees these in the context panel and watches them progress. " +
"Call this before you start executing (after gathering what you need); " +
"re-call it to revise the plan. As you work, call update_plan_step to " +
"advance each one.",
Description: "Lay out ALL the ordered steps you'll take to reach the goal, in ONE " +
"call, listing every step end-to-end — not just the next one. The operator " +
"sees the full list in the context panel and watches it progress; a plan " +
"with only 1 step looks broken to them even if you intend to add more later. " +
"Call this ONCE, before you start executing (after gathering what you need). " +
"As you work, call update_plan_step (not propose_plan again) to advance each " +
"step. Only re-call propose_plan if the plan itself has fundamentally changed " +
"(e.g. a new approach is needed) — in that case new steps are appended after " +
"whatever already ran, never erasing completed work.",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{