fix: improve todo completion reliability#2048
Conversation
There was a problem hiding this comment.
Review Summary
Assessment: 🟢 APPROVE
This PR successfully addresses the LLM attention problem by adding reminder strings that alert the agent about incomplete todos. The implementation is sound with proper control flow:
✅ Clear separation between completed/pending/in-progress states
✅ Correct conditional logic (reminders only shown when needed)
✅ Storage clearing happens at the right time (after all completed)
✅ Test updated to reflect new output format
The changes effectively solve the problem of LLMs forgetting to mark todos as completed by bringing incomplete items back into their attention window.
Minor optimization opportunities (informational only, not blocking):
- Two small inefficiencies noted below could be addressed in a future cleanup
Posted via docker-agent PR review
| // incompleteReminder returns a reminder string listing any non-completed todos, | ||
| // or an empty string if all are completed (or storage is empty). | ||
| func (h *todoHandler) incompleteReminder() string { | ||
| all := h.storage.All() |
There was a problem hiding this comment.
[LOW] Minor inefficiency: storage.All() called twice
When incompleteReminder() is called, allCompleted() has already called h.storage.All() to check completion status. This results in iterating the storage twice. Consider refactoring to pass the slice or cache the result.
This doesn't affect correctness, just performance (minimal impact in practice given todo lists are typically small).
pkg/tools/builtin/todo.go
Outdated
| if todo.Status == "completed" { | ||
| completed++ | ||
| } | ||
| total++ |
There was a problem hiding this comment.
[LOW] Redundant counter: total is always len(all)
The total variable is incremented for every item but will always equal len(all). You can simplify this:
var completed int
for _, todo := range all {
if todo.Status == "completed" {
completed++
}
}
fmt.Fprintf(&output, "Current todos (%d/%d completed):\n", completed, len(all))This is a minor code clarity improvement with no functional impact.
LLMs frequently create todos but fail to mark all of them as completed, leaving the todo sidebar in a partially-done state. This happens because the instruction to complete todos is far back in the system prompt by the time the LLM finishes its work. Add an incomplete-todo reminder to update_todos and list_todos tool output so the LLM sees unfinished items directly in its immediate context. Also strengthen the system instructions to emphasize that every todo must be completed before responding.
01218b3 to
9c1cab4
Compare
Add AllTodos field to CreateTodoOutput, CreateTodosOutput, and UpdateTodosOutput so every response includes the complete current state of all todo items. This gives the LLM full visibility into the todo list without needing a separate list_todos call. Also removes the auto-clear-on-all-completed behavior so that completed items remain visible, and adds CreateTodoOutput as a dedicated output type for create_todo (replacing bare Todo).
Problem
Agents create todos (e.g. 5 items), do the work, but only mark a subset as completed (e.g. 3 out of 5). The remaining items stay "pending" in the sidebar even though the underlying tasks were actually done.
This is an LLM attention problem. The CRUD model (
update_todoswith just an ID) lets the LLM update one item without ever seeing the rest. As the conversation grows and old tool results get truncated by the 40k-token budget, the LLM loses awareness of what it originally planned.Changes
Full-state responses on every tool call.
create_todo,create_todos, andupdate_todosnow include anall_todosfield containing the complete current todo list, plus areminderlisting any incomplete items. This means the LLM sees the full state every time it touches its todos — it cannot marktodo_3as completed without also seeing thattodo_1andtodo_5are still pending. This mirrors the approach used by OpenCode, where every todo write forces the full list into the response as a cognitive forcing function.Stronger instructions. The
Instructions()prompt now explicitly requires the LLM to calllist_todosbefore its final response and to never leave items in a pending/in-progress state.Remove auto-clear on all-completed. Previously, marking all items as completed would call
storage.Clear(), wiping the list. This destroyed the audit trail and madelist_todosreturn empty, which could confuse the LLM if it tried to verify its work. Completed items now remain visible in storage.New
CreateTodoOutputtype.create_todopreviously returned a bareTodo. It now returnsCreateTodoOutputwithcreated,all_todos, andreminderfields, consistent with the other tool responses.