Skip to content

03:待办写入

核心路径 · 上一课:[[02-tool-use/02-工具使用|02 工具使用]] · 下一课:[[04-subagent/04-子智能体|04 子智能体]]

"没有计划的 agent 走哪算哪" —— 先列步骤再动手,完成率翻倍。

Harness 层:规划 —— 让模型不偏航,但不替它画航线。

学习目标

  • 理解模型在多步任务中丢失进度的根本原因
  • 掌握 TodoManager 的设计与实现
  • 理解 "nag reminder" 机制及其触发条件
  • 体会 in_progress 独占约束对顺序聚焦的作用
  • 能够独立扩展 tool dispatch map 加入新工具

问题:模型会丢进度

[[01-agent-loop/01-智能体循环|01 智能体循环]] 建立了核心循环,[[02-tool-use/02-工具使用|02 工具使用]] 将工具数从 1 扩展到 4。但多步任务中有一个微妙的问题:

模型会丢失进度。

一个 10 步重构任务,模型做完 1-3 步后就开始「即兴发挥」——跳步、重复、跑偏。这不是模型笨,而是架构问题:

  1. 上下文稀释:工具结果不断填充上下文,系统提示的影响力逐渐被稀释
  2. 缺少外部记忆:模型只能依赖内部注意力来记住"做到哪了",注意力不会专门记住进度
  3. 没有问责压力:模型跑偏了也没人提醒它

关键洞察

模型需要 一个自己能读写的进度板,而 Harness 需要 一个提醒机制。进度板给模型方向感,提醒机制制造问责压力。

核心概念

1. TodoManager:结构化的任务状态

python
class TodoManager:
    def __init__(self):
        self.items = []

    def update(self, items: list) -> str:
        validated = []
        in_progress_count = 0
        for item in items:
            status = item.get("status", "pending")
            if status == "in_progress":
                in_progress_count += 1
            validated.append({
                "id": item["id"],
                "text": item["text"],
                "status": status
            })
        if in_progress_count > 1:
            raise ValueError("Only one task can be in_progress")
        self.items = validated
        return self.render()

关键设计决策:

  • in_progress 独占:同一时间只允许一个任务 in_progress,强制模型顺序聚焦,避免并行贪心
  • 三个状态pendingin_progresscompleted,简单够用
  • 校验优先:所有输入先校验再存储,text 必填、status 必须在枚举范围、最多 20 项

渲染输出格式:

[ ] #1: 添加类型提示
[>] #2: 添加文档字符串
[x] #3: 添加 main guard

(1/3 completed)

这种格式模型一看就懂,而且能通过 todo 工具随时更新。

2. 注册 todo 工具

[[02-tool-use/02-工具使用|02 课]] 建立了 TOOL_HANDLERS 分发模式。加入 todo 只需要 加一行

python
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:

python
TOOLS = [
    # ... 之前的工具 schema ...
    {
        "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"]
        }
    }
]

循环体 不变——这是 [[02-tool-use/02-工具使用|02 课]] 的核心原则:加工具不用改循环。

3. Nag Reminder:问责压力

引入一个计数器 rounds_since_todo。每一轮:

  1. 检查本轮是否调用了 todo 工具
  2. 如果调用了,计数器归零
  3. 如果没调用,计数器 +1
  4. 当计数器 >= 3 时,在下一轮的 tool_result 中注入 <reminder>
python
rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
if rounds_since_todo >= 3:
    results.insert(0, {
        "type": "text",
        "text": "<reminder>Update your todos.</reminder>"
    })

为什么是 3 轮? 1 轮太激进(模型可能正在做一件事的中途),2 轮稍紧,3 轮是经验值——给模型一定的自由度,又不会让它完全放飞。

系统提示语

python
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."""

三句话对应三个信息:身份、行为期望、偏好。

实验:运行 s03

bash
cd learn-claude-code
python agents/s03_todo_write.py

试试这些 prompt(英文 prompt 对 LLM 效果更好,也可以用中文):

  1. Refactor the file hello.py: add type hints, docstrings, and a main guard
  2. Create a Python package with __init__.py, utils.py, and tests/test_utils.py
  3. Review all Python files and fix any style issues

观察模型行为:是否在开始时创建了 todo 列表?是否在每完成一个子任务后更新状态?如果连续多轮不更新,是否看到了 <reminder> 提醒?

与 s02 的对比

组件s02(之前)s03(之后)
工具数量45(+todo)
规划能力带状态的 TodoManager
Nag 注入3 轮后注入 <reminder>
Agent loop简单分发+ rounds_since_todo 计数器

架构图

+--------+      +-------+      +---------+
|  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

关键原则

  1. 进度是外部状态,不是内部记忆:不要把进度放在模型注意力中,要放在模型能读写的外部结构中
  2. 独占约束强制顺序in_progress 独占让模型必须线性执行,避免多任务切换的认知开销
  3. Nag 制造问责压力:模型不更新计划,系统就追着问——这是 Harness 对模型行为最直接的约束
  4. 加工具不改循环:继续验证 [[02-tool-use/02-工具使用|02 课]] 的核心原则

预告:下一课

规划有了(s03),工具多了(s02),循环跑起来了(s01)。但如果任务太大,一个 agent 的上下文不够怎么办?

[[04-subagent/04-子智能体|04 子智能体]]:大任务拆小,每个子任务有自己独立的上下文。

mermaid
graph LR
    s01[智能体循环] --> s02[工具使用]
    s02 --> s03[待办写入]
    s03 --> s04[子智能体]
    s03 -.-> s05[技能加载]
    s03 -.-> s06[上下文压缩]
    s04 --> s07[任务系统]
    style s03 fill:#ffd700,stroke:#333,stroke-width:3px

参考

[[00-课程概览/教学大纲|📋 返回教学大纲]]

基于 Learn Claude Code 项目改编