news 2026/5/20 14:55:46

【与我学 ClaudeCode】规划与协调篇 之 TodoWrite 的 神奇之处

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【与我学 ClaudeCode】规划与协调篇 之 TodoWrite 的 神奇之处

作者:逆境不可逃

技术永无止境

希望我的内容可以帮助到你!!!!!


大家吼 ! 我是 逆境不可逃 今天给大家带来文章《【与我学 ClaudeCode】规划与协调篇 之 TodoWrite 的 神奇之处》.

Learn-Claude-Code 官方地址 :https://github.com/shareAI-lab/learn-claude-code

前言

顺着前面s01 Agent循环s02工具分发的底层架构一路进阶,本篇正式进入规划与协调核心体系。 从简单任务清单、子智能体上下文隔离,到按需技能加载,再到磁盘持久化任务依赖图,一步步让 Claude 智能体从 “只会被动执行” 进化为会规划、会拆分、懂依赖、能协同的成熟工程级 Agent。

整体学习路线:s01 > s02>s03> s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12


TodoWrite 任务清单机制:先定计划,再动手执行

1、问题根源:为什么早期 Agent 长任务必崩

几乎所有 v0/v1 版本的 Agent 都面临同一个致命问题:

多步任务中,模型会逐渐丢失进度 —— 重复做过的事、跳步、跑偏。对话越长越严重:工具结果不断填满上下文,系统提示的影响力逐渐被稀释。一个 10 步重构可能做完 1-3 步就开始即兴发挥,因为 4-10 步已经被挤出注意力窗口了。

传统的内部思维链 (CoT) 规划完全无法解决这个问题,因为:

  • 计划是不可见的:用户和开发者都不知道模型在想什么
  • 计划是短暂的:一旦思维链滚出上下文窗口,计划就永久丢失
  • 计划是不可控的:模型随时可能偏离计划,没有任何约束

2、三大核心设计决策

TodoWrite 通过三个反直觉但极其有效的设计决策,从根本上解决了上述问题。每个决策都明确对比了替代方案的缺陷。

a. 强制计划外化:让计划可见可追踪

核心设计:不让模型在思维链里默默规划,而是强制通过todo工具将计划写入独立于 LLM 上下文的外部状态 (TodoManager)。每个计划项都有明确的状态:pending(待办)、in_progress(进行中)、completed(已完成)。

三大不可替代的好处

  1. 用户可见:执行前就能知道 Agent 打算做什么,彻底打破 "黑盒" 运行,出问题可提前干预
  2. 开发者可调试:通过检查计划状态就能精准定位 Agent 卡在哪一步,大幅降低调试难度
  3. Agent 自身可引用:即使早期上下文已经滚出窗口,计划仍存在于外部状态中,永远不会丢失

替代方案的致命缺陷

用内部 CoT 规划(v0/v1 的做法),计划是不可见且短暂的。Claude 的扩展思考也是一样 —— 一旦思考内容滚出上下文,计划就没了,而且用户和下游工具永远检查不到。

b. 单任务强制聚焦:同一时间只做一件事

核心设计:TodoManager 通过代码硬约束,强制任何时候最多只能有一个任务处于in_progress状态。如果模型想开始第二个任务,必须先完成或放弃当前任务。

解决的隐蔽失败模式

试图交替处理多个任务的模型,往往会丢失状态,产出大量半成品。LLM 的上下文切换能力极差,会搞混不同任务的细节,记不清自己在做哪个。

替代方案的致命缺陷

允许多个进行中的任务看起来更灵活,但在实践中会导致输出质量灾难性下降。单聚焦约束是一个简单但极其有效的护栏,能显著提升任务完成率。

c. 20 条计划上限:防止过度规划

核心设计:TodoWrite 将计划项数量严格限制在 20 条以内,这是对 "过度规划" 的刻意约束。

为什么 20 条是黄金数字

  • 不加限制时,模型倾向于把任务分解成越来越细粒度的步骤,产出 50 条甚至更多计划,每一步都微不足道
  • 冗长的计划极其脆弱:如果第 15 步失败,剩下的 35 步可能全部作废
  • 20 条以内的短计划保持在正确的抽象层级,更容易在现实偏离计划时做出调整
  • 经验证明:绝大多数真实的编码任务都可以用 5-15 个有意义的步骤表达

替代方案的致命缺陷

没有上限会导致荒谬的详细计划;动态上限(与任务复杂度成正比)更聪明,但会大幅增加系统复杂度。固定 20 条是一个简单的启发式规则,经验上效果极好。

3、系统整体架构与工作原理

a. 架构流程图
+--------+ +-------+ +---------+ | User | ---> | LLM | ---> | Tools | | prompt | | | | + todo | +--------+ +---+---+ +----+----+ ^ | | tool_result | +----------------+ | +-----------+-----------+ | TodoManager state | | [ ] task A | | [>] task B <- doing | | [x] task C | +-----------------------+ | if rounds_since_todo >= 3: inject <reminder> into tool_result
b. 核心工作机制

TodoWrite 由三个相互配合的核心组件构成:

(1) TodoManager:外部状态管理器
  • 存储所有带状态的任务项
  • 强制执行三大约束:最多 20 条任务、同一时间最多一个进行中任务、状态只能是三个合法值
  • 提供update()方法验证并更新任务列表,违反约束会抛出明确错误
  • 提供render()方法将任务列表渲染成人类可读的格式,返回给 LLM
(2) Todo 工具:与 LLM 的交互接口
  • todo工具和其他基础工具(bash、read_file、write_file 等)完全平等
  • LLM 通过调用todo工具来制定、更新和跟踪计划
  • 工具调用的结果(渲染后的任务列表)会被加入上下文,让 LLM 始终知道当前进度
(3) Nag 提醒系统:问责机制
  • 系统维护一个rounds_since_todo计数器,记录 LLM 连续多少轮没有调用 todo 工具
  • 每轮循环结束后,如果调用了 todo 工具,计数器重置为 0;否则加 1
  • 当计数器达到 3 时,系统会自动在工具结果中注入<reminder>Update your todos.</reminder>
  • 这个机制制造了持续的 "问责压力":模型不更新计划,系统就会一直提醒它

4、完整代码逐行解析

a. 初始化与配置
import os import subprocess from pathlib import Path from anthropic import Anthropic from dotenv import load_dotenv # 加载环境变量,支持自定义API端点 load_dotenv(override=True) if os.getenv("ANTHROPIC_BASE_URL"): os.environ.pop("ANTHROPIC_AUTH_TOKEN", None) # 工作目录:所有文件操作都限制在此目录下,防止路径逃逸 WORKDIR = Path.cwd() client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL")) MODEL = os.environ["MODEL_ID"]
b. 系统提示词
SYSTEM = f"""You are a coding agent at {WORKDIR}. Use the todo tool to plan multi-step tasks. Mark in_progress before starting, completed when done. Prefer tools over prose."""
  • 明确告诉 LLM 必须使用 todo 工具来规划任务
  • 强调 "开始前标记为 in_progress,完成后标记为 completed"
  • 要求 "优先使用工具而不是文字描述",这是 Agent 的核心原则
c. TodoManager 核心实现
class TodoManager: def __init__(self): self.items = [] # 存储所有任务项 def update(self, items: list) -> str: # 约束1:最多20个任务 if len(items) > 20: raise ValueError("Max 20 todos allowed") validated = [] in_progress_count = 0 for i, item in enumerate(items): text = str(item.get("text", "")).strip() status = str(item.get("status", "pending")).lower() item_id = str(item.get("id", str(i + 1))) # 验证任务文本不能为空 if not text: raise ValueError(f"Item {item_id}: text required") # 验证状态只能是三个合法值 if status not in ("pending", "in_progress", "completed"): raise ValueError(f"Item {item_id}: invalid status '{status}'") # 统计进行中的任务数量 if status == "in_progress": in_progress_count += 1 validated.append({"id": item_id, "text": text, "status": status}) # 约束2:同一时间最多一个进行中的任务 if in_progress_count > 1: raise ValueError("Only one task can be in_progress at a time") self.items = validated return self.render() def render(self) -> str: """将任务列表渲染成易读的格式""" if not self.items: return "No todos." lines = [] for item in self.items: marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}[item["status"]] lines.append(f"{marker} #{item['id']}: {item['text']}") # 显示完成进度 done = sum(1 for t in self.items if t["status"] == "completed") lines.append(f"\n({done}/{len(self.items)} completed)") return "\n".join(lines) # 全局唯一的TodoManager实例 TODO = TodoManager()
d. 基础工具实现
def safe_path(p: str) -> Path: """安全路径检查:防止路径逃逸攻击""" path = (WORKDIR / p).resolve() if not path.is_relative_to(WORKDIR): raise ValueError(f"Path escapes workspace: {p}") return path def run_bash(command: str) -> str: """执行shell命令,过滤危险操作""" dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"] if any(d in command for d in dangerous): return "Error: Dangerous command blocked" try: r = subprocess.run(command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=120) out = (r.stdout + r.stderr).strip() return out[:50000] if out else "(no output)" except subprocess.TimeoutExpired: return "Error: Timeout (120s)" # 其他工具:read_file、write_file、edit_file(略)
e. 工具注册
# 工具处理函数映射 TOOL_HANDLERS = { "bash": lambda **kw: run_bash(kw["command"]), "read_file": lambda **kw: run_read(kw["path"], kw.get("limit")), "write_file": lambda **kw: run_write(kw["path"], kw["content"]), "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]), "todo": lambda **kw: TODO.update(kw["items"]), } # 工具描述和输入schema,传给Anthropic API TOOLS = [ # 其他工具(略) {"name": "todo", "description": "Update task list. Track progress on multi-step tasks.", "input_schema": {"type": "object", "properties": {"items": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string"}, "text": {"type": "string"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}}, "required": ["id", "text", "status"]}}}, "required": ["items"]}}, ]
f. Agent 主循环(含 Nag 提醒)
def agent_loop(messages: list): rounds_since_todo = 0 # Nag提醒计数器 while True: # 调用LLM生成回复 response = client.messages.create( model=MODEL, system=SYSTEM, messages=messages, tools=TOOLS, max_tokens=8000, ) messages.append({"role": "assistant", "content": response.content}) # 如果不是工具调用,结束本轮循环 if response.stop_reason != "tool_use": return results = [] used_todo = False # 执行所有工具调用 for block in response.content: if block.type == "tool_use": handler = TOOL_HANDLERS.get(block.name) try: output = handler(**block.input) if handler else f"Unknown tool: {block.name}" except Exception as e: output = f"Error: {e}" print(f"> {block.name}:") print(str(output)[:200]) results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)}) # 标记是否使用了todo工具 if block.name == "todo": used_todo = True # 更新Nag计数器 rounds_since_todo = 0 if used_todo else rounds_since_todo + 1 # 触发Nag提醒:连续3轮没调用todo if rounds_since_todo >= 3: results.append({"type": "text", "text": "<reminder>Update your todos.</reminder>"}) # 将工具结果和提醒加入上下文 messages.append({"role": "user", "content": results})
g. 交互式命令行入口
if __name__ == "__main__": history = [] # 全局对话历史 while True: try: query = input("\033[36ms03 >> \033[0m") except (EOFError, KeyboardInterrupt): break if query.strip().lower() in ("q", "exit", ""): break history.append({"role": "user", "content": query}) agent_loop(history) # 打印最终回复 response_content = history[-1]["content"] if isinstance(response_content, list): for block in response_content: if hasattr(block, "text"): print(block.text) print()
h.整个程序的执行流程
  1. 程序启动,加载环境变量,初始化 Claude 客户端和 TodoManager
  2. 显示命令行提示符,等待用户输入
  3. 用户输入一个任务(比如 "创建一个计算器程序")
  4. 把用户输入添加到对话历史,调用agent_loop
  5. agent_loop把对话历史发给 Claude
  6. Claude 返回响应,说要调用todo工具,创建任务列表
  7. 程序执行todo工具,更新任务状态,把结果返回给 Claude
  8. Claude 返回响应,说要调用write_file工具,创建文件
  9. 程序执行write_file工具,写入代码,把结果返回给 Claude
  10. Claude 返回响应,说要调用todo工具,把第一个任务标记为已完成,第二个标记为进行中
  11. 重复步骤 5-10,直到所有任务完成
  12. Claude 返回最终回答,程序把回答打印到控制台
  13. 回到步骤 2,等待用户下一个输入

5、TodoWrite 的核心优势与创新

  1. 状态外化是根本突破:计划存储在独立于 LLM 上下文的外部状态中,彻底解决了长任务进度丢失问题
  2. 硬约束远胜软提示:通过代码层面的强制约束来引导 LLM 行为,比系统提示词的软约束有效 100 倍
  3. 极致的可观测性:用户和开发者都能清晰看到 Agent 的计划和进度,不再是黑盒
  4. 简单到极致:整个系统只有 177 行代码,没有复杂的架构,但是经验上效果极好
  5. 保留模型自主性:系统只提供规划框架和约束,具体计划内容完全由模型自己制定,保留了 LLM 的灵活性和创造力

6、运行示例

假设用户输入:"给我写一个简单的计算器程序,支持加减乘除"

Agent 的典型执行流程:

  1. LLM 首先调用todo工具,制定初始计划:
    [>] #1: 创建calculator.py文件 [ ] #2: 实现加法函数 [ ] #3: 实现减法函数 [ ] #4: 实现乘法函数 [ ] #5: 实现除法函数(处理除零错误) [ ] #6: 添加命令行交互界面 [ ] #7: 测试程序
  2. 调用write_file工具创建文件
  3. 完成后调用todo工具,将 #1 标记为completed,#2 标记为in_progress
  4. 调用edit_file工具添加加法函数
  5. 以此类推,直到所有任务完成
  6. 如果 LLM 连续 3 轮没有调用todo工具,系统会自动注入提醒
  7. LLM 看到提醒后,会立即调用todo工具更新进度

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/20 14:55:46

UVM验证中m_sequencer与p_sequencer的区别与实战应用

1. 项目概述&#xff1a;一个困扰UVM初学者的经典问题如果你刚开始接触UVM验证方法学&#xff0c;或者已经写过一些测试平台&#xff0c;那么你很可能在某个深夜&#xff0c;对着代码里同时出现的m_sequencer和p_sequencer这两个句柄陷入沉思。它们看起来都指向一个序列执行器&…

作者头像 李华
网站建设 2026/5/20 14:55:44

合宙Air153C硬件看门狗芯片:物联网设备可靠性的终极守护方案

1. 项目概述&#xff1a;为什么我们需要一颗独立的看门狗芯片&#xff1f;在物联网设备&#xff0c;尤其是那些部署在野外、长期无人值守的物流追踪器、智能安防传感器里&#xff0c;最怕的不是信号不好&#xff0c;而是设备“死机”了没人知道。你可能遇到过这种情况&#xff…

作者头像 李华
网站建设 2026/5/20 14:55:33

SystemVerilog中virtual关键字的本质:多态、抽象与验证架构设计

1. 从“为什么”开始&#xff1a;理解virtual的本质在SystemVerilog和UVM的世界里&#xff0c;virtual这个关键字就像一位经验丰富的项目经理&#xff0c;它不直接写代码&#xff0c;但它定义了团队协作的规则和接口。很多刚接触验证的朋友&#xff0c;包括我自己在早期&#x…

作者头像 李华
网站建设 2026/5/20 14:55:33

RabbitMQ 简单模式2026/5/19

我给你整理了零基础最快上手路线&#xff0c;从环境搭建 → 基础收发 → 7 大工作模式 → Spring Boot 整合 → 微服务实战&#xff0c;全程可直接复制代码运行&#xff0c;不绕弯、不讲废话&#xff0c;最快 1 小时学会核心用法。 一、先搞懂&#xff1a;RabbitMQ 是什么&…

作者头像 李华
网站建设 2026/5/20 14:55:33

联想笔记本BIOS隐藏设置解锁:终极指南与完整教程

联想笔记本BIOS隐藏设置解锁&#xff1a;终极指南与完整教程 【免费下载链接】LEGION_Y7000Series_Insyde_Advanced_Settings_Tools 支持一键修改 Insyde BIOS 隐藏选项的小工具&#xff0c;例如关闭CFG LOCK、修改DVMT等等 项目地址: https://gitcode.com/gh_mirrors/le/LEG…

作者头像 李华
网站建设 2026/5/20 14:55:26

别再一个个画引脚了!用Excel+Cadence OrCAD快速搞定STM32芯片原理图库

用ExcelOrCAD高效构建STM32原理图库的工程实践 在嵌入式硬件开发中&#xff0c;原理图设计是连接芯片规格与实际PCB布局的关键桥梁。面对STM32这类具有复杂引脚配置的现代微控制器&#xff0c;传统手动绘制原理图符号的方式不仅耗时耗力&#xff0c;还容易引入人为错误。本文将…

作者头像 李华