diff --git a/internal/context/prompt_test.go b/internal/context/prompt_test.go index b442d67e..70517671 100644 --- a/internal/context/prompt_test.go +++ b/internal/context/prompt_test.go @@ -125,6 +125,12 @@ func TestDefaultToolUsagePromptIncludesPermissionAndAntiLoopGuidance(t *testing. if !strings.Contains(toolUsage, "`todo_write`") { t.Fatalf("expected Tool Usage to mention todo_write for task state, got %q", toolUsage) } + if !strings.Contains(toolUsage, "If the user clearly switches to a different task") { + t.Fatalf("expected Tool Usage to cover task switches, got %q", toolUsage) + } + if !strings.Contains(toolUsage, "otherwise mark it `canceled` before starting the new task") { + t.Fatalf("expected Tool Usage to explain canceled vs completed on task switches, got %q", toolUsage) + } if !strings.Contains(toolUsage, "Execute todos sequentially in the main loop") { t.Fatalf("expected Tool Usage to enforce sequential todo execution, got %q", toolUsage) } diff --git a/internal/context/source_todos.go b/internal/context/source_todos.go index 50f7f55c..5b900603 100644 --- a/internal/context/source_todos.go +++ b/internal/context/source_todos.go @@ -81,8 +81,8 @@ func (todosSource) Sections(ctx context.Context, input BuildInput) ([]promptSect } lines = append(lines, "", - "stale_todo_reminder: If any todo above is no longer relevant to the current task,", - "cancel it via todo_write set_status=canceled before signaling completion.", + "stale_todo_reminder: If the user clearly switches to a different task, do not carry old unfinished todos forward.", + "Mark each old todo completed only if the work is actually done; otherwise cancel it via todo_write before starting the new task.", ) return []promptSection{ diff --git a/internal/context/source_todos_test.go b/internal/context/source_todos_test.go index e467ea95..cc80a233 100644 --- a/internal/context/source_todos_test.go +++ b/internal/context/source_todos_test.go @@ -54,13 +54,19 @@ func TestTodosSourceSections(t *testing.T) { if sections[0].Title != "Todo State" { t.Fatalf("title = %q, want %q", sections[0].Title, "Todo State") } - if strings.Contains(sections[0].Content, "done") { + if strings.Contains(sections[0].Content, `id="done"`) { t.Fatalf("expected terminal todo filtered, got %q", sections[0].Content) } lines := strings.Split(sections[0].Content, "\n") if len(lines) < 2 || !strings.Contains(lines[0], "in-progress") { t.Fatalf("expected in_progress todo first, got %q", sections[0].Content) } + if !strings.Contains(sections[0].Content, "If the user clearly switches to a different task") { + t.Fatalf("expected task-switch reminder in english, got %q", sections[0].Content) + } + if !strings.Contains(sections[0].Content, "completed only if the work is actually done") { + t.Fatalf("expected completed-vs-canceled guidance, got %q", sections[0].Content) + } } func TestTodosSourceSectionsBoundaries(t *testing.T) { diff --git a/internal/promptasset/templates/core/tool_usage.md b/internal/promptasset/templates/core/tool_usage.md index e80cf99f..88d7349e 100644 --- a/internal/promptasset/templates/core/tool_usage.md +++ b/internal/promptasset/templates/core/tool_usage.md @@ -29,6 +29,7 @@ - `todo_write` `set_status` requires: `{"action":"set_status","id":"","status":"pending|in_progress|blocked|completed|failed|canceled"}`. - `todo_write` `update` requires: `{"action":"update","id":"","patch":{...}}`; include `expected_revision` when known to prevent concurrent overwrite. - Mark todos `completed` only after the relevant artifact or verification exists. +- If the user clearly switches to a different task, do not carry old unfinished todos forward. Mark each old todo `completed` only if the work is actually done; otherwise mark it `canceled` before starting the new task. - Mark todos `blocked` with a concrete reason when waiting on permission, user input, external resources, or an internal dependency. - Execute todos sequentially in the main loop unless the user explicitly asks for another strategy. - `spawn_subagent` only supports `mode=inline`: the subagent runs now and returns structured output in the same turn.