Agent 处理短任务时,可以直接读取、修改和验证。但任务一旦包含十几个步骤,新的报错和工具输出会不断进入上下文,模型很容易只顾眼前问题,忘记原始目标中尚未完成的部分。

TodoWrite 的作用不是让模型“思考得更多”,而是把计划转换成运行时能够保存、展示和校验的结构化状态。

用户目标

模型调用 todo_write

Harness 校验并保存计划

模型执行普通工具

持续更新 pending → in_progress → completed

它没有增加读写文件或运行命令的能力,增加的是进度可见性和任务恢复线索。

计划为什么要放在模型之外

自然语言中的“我准备先做 A,再做 B”很容易被后续消息淹没,也不方便程序判断当前进度。结构化 Todo 则可以被:

  • 用户直接检查;
  • Harness 校验状态是否合法;
  • UI 渲染为进度列表;
  • reminder 机制检测是否长期未更新;
  • 会话恢复或外部存储系统持久化。

这不是要求暴露模型的私有推理过程。Todo 应记录用户可理解的任务、状态和结果标准,而不是逐字保存内部思维链。

最小状态模型

当前课程为每个任务保留两个字段:

{
  "content": "运行测试并修复失败",
  "status": "pending"
}

状态只有三种:

  • pending:尚未开始;
  • in_progress:当前正在处理;
  • completed:已经完成。

TodoManager 持有整个任务数组,todo_write 每次提交一份新数组,用新列表替换旧列表。它更接近“提交当前计划快照”,而不是逐条发送增量事件。

这种设计很简单,但调用者必须把仍然有效的旧任务一并传回。若只提交当前任务,其他任务会从计划中消失。

更新前先验证,再替换

一个可靠的更新函数不应边验证边修改正式状态。更安全的顺序是:

def update(todos):
    validated = validate_all(todos)
    self.items = validated
    return self.render()

先生成完整的 validated 列表,所有项目都通过后再替换 self.items。这样某一项非法时,原有计划不会停留在“前半部分已经更新”的中间状态。

当前教学实现检查:

  • 输入必须是列表;
  • 每项必须是对象;
  • content 不能为空;
  • status 必须属于三个允许值;
  • 同一时刻最多一个任务是 in_progress
  • 一次最多提交 20 项。

“最多一个进行中任务”体现的是串行专注模型。若系统支持多个并发子 Agent,就可能需要允许多个 in_progress,并增加负责人或执行 ID。状态约束应服务于实际执行模型,而不是机械照搬。

20 项上限同样只是课程中的防膨胀策略。真实系统可以按界面容量、上下文成本和任务复杂度配置,并在计划过大时引导模型拆分里程碑。

Schema 和运行时校验缺一不可

工具定义通过 JSON Schema 告诉模型参数结构:

{
    "name": "todo_write",
    "input_schema": {
        "type": "object",
        "properties": {
            "todos": {
                "type": "array",
                "maxItems": 20,
                "items": {
                    "type": "object",
                    "properties": {
                        "content": {"type": "string", "minLength": 1},
                        "status": {
                            "type": "string",
                            "enum": ["pending", "in_progress", "completed"],
                        },
                    },
                    "required": ["content", "status"],
                },
            }
        },
        "required": ["todos"],
    },
}

Schema 能减少模型生成错误,但不能替代 handler 内的验证。调用可能来自旧客户端、测试、手工请求或绕过 schema 的其他入口,运行时仍应把输入当作不可信数据。

课程还兼容字符串形式的 Todo:先尝试 json.loads,再尝试 ast.literal_evalliteral_eval 只解析 Python 字面量,不会像 eval 那样执行任意表达式,但如果接口契约本来就要求数组,生产 API 通常更适合拒绝字符串,保持单一输入格式。

render() 只负责展示

状态存储和展示应分开。render() 读取 self.items,把状态映射为标记,并计算完成数量:

marker = {
    "pending": "[ ]",
    "in_progress": "[>]",
    "completed": "[x]",
}[todo["status"]]
 
done = sum(todo["status"] == "completed" for todo in self.items)

在 Python 中,布尔值可以参与整数求和,因此每个 True 贡献 1,最终得到已完成数量。

渲染层可以换成图标、终端颜色或网页组件,但不应借此修改任务状态。模型也不应从终端截断后的预览反推真实列表;Harness 保存的结构化状态才是事实源。

Reminder 是软约束,不是调度器

当前课程统计“连续多少个工具调用轮次没有使用 todo_write”:

rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
 
if rounds_since_todo >= 3:
    results.append(
        {
            "type": "text",
            "text": "<reminder>Update your todos.</reminder>",
        }
    )
    rounds_since_todo = 0

这里的“三轮”指包含工具调用的 Agent Loop 轮次,不是三个工具,也不是三次用户对话。一次响应即使调用多个普通工具,计数通常也只增加一次;只要其中用了 todo_write,计数就会清零。

提醒作为文本 block 与工具结果一起回传模型。它能提高模型更新计划的概率,但不能保证模型一定照做,也不能证明任务真的完成。

更成熟的系统可以根据风险采用不同强度:

  • 软提醒:提示模型更新计划;
  • 协议约束:没有合法计划时拒绝执行高风险工具;
  • 工作流状态机:由运行时决定哪些步骤可以开始;
  • 人工检查点:关键步骤完成后等待用户批准。

不要把提示词层面的 reminder 当作事务、权限或调度保证。

contenttext 和消息层级

在 Messages API 风格的消息中,字段名取决于所在层级和 block 类型:

{"role": "user", "content": [...]}
{"type": "text", "text": "Update your todos."}
{"type": "tool_result", "tool_use_id": call_id, "content": output}

外层 message 使用 content 承载 block 数组;文本 block 使用 text;工具结果 block 使用 content 并通过调用 ID 与对应的 tool_use 关联。

不宜把某个 SDK 版本中 block 类型的数量写成稳定知识。API 和 SDK 会演进,代码应依赖当前官方类型定义,并只处理自身实际支持的 block。

这份状态还缺少什么

课程中的 TodoManager 是进程内单例,适合演示,却不是完整任务系统:

  • 进程退出后计划丢失;
  • 多个会话可能共享或覆盖同一份状态;
  • 整体替换没有版本号,存在并发丢失更新;
  • completed 由模型声明,不代表测试或验收真的通过;
  • Todo 没有稳定 ID、依赖关系、负责人、时间戳或失败原因。

如果要把它升级为产品能力,可以逐步加入:

  • 按会话或任务 ID 隔离状态;
  • 持久化与恢复;
  • 乐观锁或版本号;
  • 稳定 Todo ID 和变更事件;
  • 完成条件及验证证据;
  • 取消、阻塞、失败等状态;
  • 大任务拆分和子任务依赖。

但字段越多,模型正确维护状态的成本也越高。先确定用户需要观察和控制什么,再扩展模型。

建议覆盖的测试

TodoWrite 至少应验证:

  • 空列表和正常状态流转;
  • 非列表输入、非对象项目和空内容;
  • 未知状态及大小写规范化策略;
  • 多个 in_progress 是否被拒绝;
  • 超过配置上限的计划;
  • 某项非法时旧状态保持不变;
  • 更新是否按约定整体替换;
  • reminder 的计数、清零和注入位置;
  • 一轮多个工具调用时的计数语义;
  • 会话隔离、并发更新和进程恢复;
  • UI 截断不影响真实结构化状态;
  • completed 是否需要外部验证证据。

小结

TodoWrite 的关键价值,是把易被上下文冲淡的自然语言计划变成 Harness 可保存、校验和展示的状态。它能帮助模型维持方向,也让用户看见当前进度。

但 Todo 仍然只是计划记录:模型写下 completed 不等于任务已经通过验收,三轮 reminder 也不是强制调度。真正可靠的 Agent 系统还需要权限控制、验证命令、持久化、并发隔离和必要的人工检查点。

参考资料