拆.
工具——AI 怎么'做事' · 第 09

TodoWrite

让 AI 给自己写待办清单

4500读完约 23 分钟craft:B+发布于 2026-06-21

一、原理:LLM 的 working memory 局限

LLM 处理多步骤任务有个特别的弱点——它会"忘"

不是真的忘——LLM 没有"记忆"概念。它的"记忆"是 message 历史。但当历史长起来之后,LLM 的注意力会"散开"——它能看到所有内容,但真正记得在做什么、做到哪一步、还剩什么这件事会失准。

具体表现:

表现 1:任务断章

用户让 LLM 做 5 件事:"修 auth、加 logging、改 README、跑测试、commit"。LLM 做完 1、2 后开始 3——做着做着把它做成了"还要改 docs"——任务漂移了。原计划的 4、5 被忘了。

表现 2:循环回头

LLM 在跑长任务中突然"想起来"应该先做某件事——回去做。但回去做的时候发现已经做过了——又往前继续。这种"原地打转"是常见现象。

表现 3:完成度自报失真

LLM 完成 3/5 任务时跟你说"已完成所有任务"——它真的觉得做完了。这是 1.4 章讲的"谄媚倾向"的延伸——LLM 倾向"声称完成"而不是"确认完成"。

这三个表现都跟一件事相关——LLM 缺乏外部化的状态追踪。它的"任务进度"全在 message 历史里——但 message 历史既冗长又分散,LLM 自己读起来不容易抓重点。

解决方案是给 LLM 一个外部待办清单

TodoWrite 就是这样的工具。它让 LLM 写一个 markdown 格式的待办清单——每项任务有 status(pending、in_progress、completed)。LLM 在每个 turn 开始时看到这个清单、做完任务后更新状态。

这个清单是 LLM 的"外部 working memory"——它显式化任务进度。LLM 不再需要从冗长 message 历史里推断"我做到哪了"——清单直接告诉它。

效果立竿见影:

  • 任务漂移减少(清单上不在的事 LLM 不会做)
  • 循环回头消失(已完成项不会再做)
  • 完成度自报准确(必须勾掉所有项才算完)

这是个用工具补 LLM 认知短板的经典案例。LLM 缺什么——给它什么。

二、案例:opencode 的 TodoWrite 设计

打开 packages/opencode/src/tool/todowrite.txt,description 长达 44 行——是 opencode 工具里第二长(仅次于 task.txt)。

为什么 TodoWrite 的 description 这么长?因为它不是"被调用的工具"——它是"被 LLM 主动使用的工具"。LLM 不会被外部触发去 TodoWrite,是 LLM 自己决定何时用。

这意味着 LLM 要学会的不只是"怎么调"——还要学会"何时调"。description 必须教 LLM 在合适时机主动使用这个工具。

我们看几个设计细节。

细节 1:明确的状态机

TodoWrite 的核心是 4 个状态:pending、in_progress、completed、cancelled。

转换规则:

  • pending → in_progress(开始做)
  • in_progress → completed(做完了)
  • in_progress → cancelled(决定不做了)
  • pending → cancelled(直接放弃)

明确状态机让 LLM 有"工作方式的脚本"——它知道每个任务的当前状态、下一步是什么。

细节 2:限制 in_progress 只有一个

description 强调:

"in_progress: Currently working on (limit to ONE task at a time)"

这条规则避免 LLM "同时开 5 个任务"——结果一个都没做完。强制一次专注一项。

这跟人类工作方式一致——多任务实际效率低。LLM 也是。

细节 3:实时更新而非批量

description 里强调:

"Mark completed only after actually completing the work, not preemptively."

每个 task 真做完了再标 completed。不要先把所有的都标 completed 再做——那是骗自己。

细节 4:全量替换而非增量

TodoWrite 的接口是"传入完整清单"——每次 LLM 调用都传整个 todo list。

为什么不是"add a task"、"update task 3 to in_progress"这种细粒度 API?因为细粒度让 LLM 容易出 race condition——它以为状态是 X,实际是 Y。

全量替换让 LLM 每次都"看到完整清单 → 写新完整清单"——没有"我以为是什么样"的歧义。

实现上 opencode 在每次 TodoWrite 调用时完整覆盖之前的清单——这是设计的选择。

细节 5:没有 TodoRead 工具

这是个有意思的设计——opencode 有 TodoWrite 但没有 TodoRead

为什么?因为 LLM 每个 turn 开始时,opencode 会自动把当前 todo list 注入到 context——LLM 不需要主动读。

这种"自动注入"让 LLM 时刻看到当前进度——不需要它"想起来去查"。

但同时,TodoWrite 必须是 LLM 主动写——它要决定什么时候更新清单。

读自动 + 写主动——这种不对称的设计让 LLM 不会忘记"还有任务"(自动看到),但又能控制"什么时候完成"(自己确认)。

细节 6:示例驱动学习

description 里有完整使用示例——什么场景应该用、清单应该长什么样、状态应该怎么转。

一个典型示例:

User: I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when you're done!

Assistant: Creates todo list with the following items:

  1. Create dark mode toggle component in Settings page
  2. Add dark mode state management (context/store)
  3. Implement CSS-in-JS styles for dark theme
  4. Update existing components to support theme switching
  5. Run tests and build process, ensure they pass Begins working on the first task

这个示例展示了:用户提出复杂多步骤需求 → LLM 主动 TodoWrite 创建清单 → 立刻开始第一项。

LLM 看到这个示例就学会"哦,复杂需求来了我应该先 TodoWrite"——不是"先 read 文件、再写代码、再回头才想起来 TodoWrite"。

细节 7:保留用户原话

description 里有一条特别的规则:

"Preserve the user's original request verbatim — don't paraphrase commands"

如果用户说"修 auth bug",LLM 应该 todo 写"修 auth bug"——不要改写成"修复授权模块的缺陷"。

为什么?因为重写会引入误解——LLM 自己改写成的语义可能跟用户原意不一致。保留原话避免这种误解。

细节 8:跟 plan 阶段的关系

opencode 有 plan mode(1.8 章讲过)。在 plan mode 下 LLM 写 plan file,里面有"实施步骤"。这些步骤跟 TodoWrite 的清单是什么关系?

opencode 的设计是——plan file 是"高层方案",TodoWrite 清单是"立刻要做的具体步骤"。两者有重叠但不完全一样:

  • Plan:架构决定、文件级别的改动方向
  • Todo:实施时的具体任务,按顺序执行

进入 build mode 后,LLM 通常会基于 plan 创建 todo 清单——把 plan 的高层方案分解成可执行的 todo 项。

三、设计启示:用工具补 LLM 的认知短板

这一章的核心论点:TodoWrite 是个特殊工具——它不操作外部世界,它操作 LLM 自己的认知

这种"自我操作工具"是 AI Agent 设计里一个被低估的模式。它的本质是——给 LLM 一个外部脚手架。LLM 自己记不住的、容易漏的、容易忘的——用工具显式化出来。

如果你做 AI 产品,下面几条原则有用:

1. 识别 LLM 的认知短板

观察 LLM 在你的产品场景里"经常出错"的模式:

  • 多步骤任务忘了某一步?→ TodoWrite 这种清单工具
  • 长 session 忘了用户的偏好?→ Preferences 工具(让 LLM 主动记忆)
  • 跨 turn 的"上下文断片"?→ Notes 工具(让 LLM 留 memo 给自己)
  • 工具调用历史模糊?→ 自动注入工具调用 summary

每个短板对应一个外部脚手架工具。

2. 自我操作工具的接口要简单

TodoWrite 只有一个参数——todos(清单)。简单到 LLM 不可能用错。

如果你设计自我操作工具有 10 个参数——LLM 会困惑。保持简单。

3. 全量替换而非增量

类似 TodoWrite,自我操作工具最好是"传入完整状态 → 完整替换"。避免增量更新的复杂度(race condition、状态不一致)。

4. 自动注入相关信息

LLM 写的清单/笔记/偏好——下次 turn 开始时自动注入到 context。不要让 LLM 主动 read——它会忘。

opencode 没有 TodoRead 是这种设计的体现。

5. 让 LLM 自然学会用

description 里给丰富示例——LLM 通过模仿学会"何时该用"。不要写复杂规则——LLM 在长 prompt 里会忽略。

6. 状态机要明确

每个自我操作工具应该有明确状态。TodoWrite 是 4 态。你的工具可能是 2 态(draft/sent)或者 3 态(new/active/archived)。

明确状态让 LLM 有清晰的操作指引。

7. 限制并发避免冲突

TodoWrite 的"只能一个 in_progress"是个约束。这种约束帮 LLM 聚焦。

如果你的工具支持并发——给个上限(最多 3 个 active 项),不要让 LLM "全部开起来"。

8. UI 端可视化

LLM 的自我操作要让用户看见——todo 清单在 UI 上显示、preferences 列在 settings 里。

让用户感受到"AI 在自我组织"——这是产品信任的来源。用户看到 AI 写清单、勾完成——会觉得"AI 在认真做"。如果这一切藏在后台,用户感受不到。

最后一个观察。TodoWrite 这种"自我操作工具"是 AI Agent 2024 年才普及的设计模式。Claude Code 推 TodoWrite、Cursor 有类似的 Plan view、Devin 有 Plan + Steps——这是个跨产品的共识。

为什么这两年才有?因为 LLM 的能力到一个程度后,"长任务能力"成了主要瓶颈。早期 LLM 连单步都做不好——没人在乎"它会不会忘任务"。现在 LLM 能跑 50 步——任务管理成了关键问题。

预计未来 1-2 年会有更多"自我操作工具"涌现——Memory 工具(让 LLM 长期记忆)、Reflection 工具(让 LLM 反思决策)、Goal 工具(让 LLM 显式化目标)。

理解 TodoWrite 这个模式,你就有了设计这一类工具的框架——识别 LLM 短板、给外部脚手架、状态机明确、自动注入相关信息、用户能看见。

下一章 3.10 是第 3 篇的收尾——我们看 opencode 15 个工具是怎么形成一个决策网的。description 里"何时用别的工具"是这个决策网的核心。