第 5 章 / 共 21 章
计划可见性:让长任务不跑偏
上一章留下的问题:现在这个 Agent 能用工具、有护栏、可扩展。给它一个十步的重构任务试试——前三步干得漂亮,第四步遇到一个报错,它开始排查,排查中又发现另一个问题,顺手改了,然后……然后它忘了原来要干什么。最后给你一份”我修好了那个报错”的总结,而十步里还有六步没动。
原因不神秘:随着工具输出不断堆进 messages,最初那句任务描述在整个上下文里的占比越来越小。目标被自己的执行过程淹没了。
5.1 一个什么也不做的工具
解法有点反直觉:加一个不产生任何实际效果的工具,让模型把计划写下来。
CURRENT_TODOS = []
def run_todo_write(todos: list) -> str:
global CURRENT_TODOS
CURRENT_TODOS = todos
lines = ["\n## 当前任务清单"]
for t in CURRENT_TODOS:
icon = {"pending": " ", "in_progress": "▸", "completed": "✓"}[t["status"]]
lines.append(f" [{icon}] {t['content']}")
print("\n".join(lines))
return f"已更新 {len(CURRENT_TODOS)} 条任务"
工具定义里给 status 加上枚举约束,防止模型自创状态:
{
"name": "todo_write",
"description": "写下或更新当前的任务清单。开始一个复杂任务前先调用它列出所有步骤,"
"每完成一步就更新状态。这是保持工作可见、不遗漏步骤的方式。",
"input_schema": {
"type": "object",
"properties": {
"todos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {"type": "string"},
"status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
},
"required": ["content", "status"],
},
}
},
"required": ["todos"],
},
}
它不读文件、不跑命令、不改任何东西。它唯一的作用是:把计划写进对话历史,让计划和执行过程一起被模型看见。
这就是 Agent 工程里一个反复出现的模式:增加”可见性”,往往比增加”能力”更能提升成功率。

5.2 提醒机制:把纪律做成默认值
模型不一定每次都自觉更新清单。加一个简单的计数器来推它一把:
rounds_since_todo = 0
# 循环里,模型返回后:
rounds_since_todo += 1
if any(b.type == "tool_use" and b.name == "todo_write" for b in response.content):
rounds_since_todo = 0
# 准备下一轮之前:
if rounds_since_todo >= 3:
results.append({"type": "text",
"text": "<reminder>请更新你的任务清单。</reminder>"})
rounds_since_todo = 0
连着三轮没碰清单,就插一条提醒。这是教学版的做法——直白、可观察。生产实现里同样的意图会做得更细致(比如只在长任务、多步任务上触发),但核心一致:把好习惯做成系统默认,而不是指望模型自律。
5.3 走一遍:加了清单前后的十步任务
任务:“把 utils/ 下所有函数补上类型注解,并保证测试通过。”
没有清单时的典型轨迹:读文件 → 改第一个文件 → 跑测试 → 报错 → 排查报错 → 发现一个无关的 bug → 修它 → 再跑测试 → 通过 → 输出”完成”。剩下六个文件根本没被碰过,而模型是真心以为自己做完了。
有清单时的典型轨迹:
| 轮次 | 动作 | 清单状态 |
|---|---|---|
| 1 | glob: utils/**/*.py | — |
| 2 | todo_write:列出 7 个文件 + 1 条”跑测试” | 8 条 pending |
| 3 | 把第 1 条置为 in_progress,读+改 utils/text.py | 1 ▸ / 7 待办 |
| 4 | 第 1 条 completed,第 2 条 in_progress | 1 ✓ / 1 ▸ / 6 待办 |
| … | ||
| 12 | 跑测试报错 → 排查 → 修复 | 清单仍在,明确显示还剩 2 条 |
| 15 | 全部 completed | 8 ✓ |
差别不在于第 12 轮那次报错处理得更好——两版处理得一样好。差别在于报错处理完之后,有清单的那一版知道自己该回到哪里。
5.4 工程现实:清单不是任务系统
有一件事现在就要说清楚,免得第12章你会混淆:**这份清单活在进程内存里,会话一结束就没了,也没有依赖关系、没有归属人。**它解决的是”当前这一次对话里,别跑偏”。
跨会话的持久化、任务之间的依赖顺序、多个 Agent 认领同一批工作——那是第12章”任务系统”的活儿。两者在成熟系统里是并存的,不是替代关系:清单是 Agent 自己的便签,任务图是团队共享的看板。