write_todos 模式把目标外化为可读写的 todo 列表——同时解决"agent 忘了在做什么"和"调试时看不见 plan"两个老问题。
建议先读《本章索引》;如果你是跳读,至少先看本章索引与本页 TL;DR。
课程中段的能力层,用来回答“一个 harness 为什么需要这些补丁”。
把目标外化为 可读写的 todo 列表 ——同时解决"agent 忘了在做什么"和"调试时看不见 plan"两个老问题。
先看失败模式,再看能力补丁;能力不是越多越好,而是要能对症下药。
课程主线:稳定能力抽象 + 官方机制 + 跨框架共性。
write_todos 工具:参数是一份完整 todo list,返回是 ack。每次调用都是"把当前完整 plan 写一遍"——不是 patch、不是 append。这个看起来奇怪的设计是它能稳定工作的关键。设想一个 agent 接到这样一个任务:"给我们的 Postgres 慢查询出一份分析报告,包含 top 10 慢 SQL、对应表与索引、改写建议、估算收益。"
没有 planning 的 agent 会发生什么:
问题不是模型不够聪明。问题是——在第 13 轮,它的 prompt 已经有几万 token,原始 user 目标在 prompt 中部,注意力衰减早就让它"忘了完整 10 条"这个数字了(参考 00 章 05 节 Lost-in-the-Middle)。
write_todos 的整个目的,就是不让这个事发生——把"还剩几条没做"做成可读写的状态,让模型每轮都能稳定看见它。
write_todos 的真实样子tool schema 极度简单——它就是一个"完整覆盖式"的写操作:
{
"name": "write_todos",
"description": "Maintain the task plan. Call this whenever the plan changes:
initially to create it; after each meaningful step to update status; when
scope changes to add/remove items. Always pass the COMPLETE list.",
"parameters": {
"type": "object",
"properties": {
"todos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"content": {"type": "string"},
"status": {"type": "string",
"enum": ["pending", "in_progress", "completed", "blocked"]}
},
"required": ["id", "content", "status"]
}
}
},
"required": ["todos"]
}
}
设计上你完全可以做 update_todo(id, status)。但实际上"每次写完整 list"这种"贵但稳"的设计,效果显著好——它强迫模型每次都把当前状态从头检查一遍,变相成了一个 reflection step。Patch 模式则常出现"模型以为某条已 done 但其实没动"的状态漂移。这是一个用冗余换稳定的经典工程权衡。
# state schema:把 todos 放到 agent 全局 state 的一个字段
from typing import Annotated, TypedDict, Literal
class Todo(TypedDict):
id: str
content: str
status: Literal["pending", "in_progress", "completed", "blocked"]
class AgentState(TypedDict):
messages: list # 对话历史
todos: list[Todo] # planner state
# write_todos 的 tool 实现:覆盖式写入 + 把"当前 plan"序列化进 message
def write_todos(state: AgentState, todos: list[Todo]) -> AgentState:
st