Skip to content

课 2 · 会话规划与 TodoWrite

本课目标

用结构化待办工具让 Agent 在多步任务中保持方向,不跑偏、不漏步。课后你会拿到一个能自主规划、逐项执行、自我纠偏的 Agent。

这一课先掌握为什么计划会被后续上下文冲掉,以及为什么要把计划单独放进 todo 工具里,不要求一上来把规划系统做成复杂的任务管理平台。

上一课让 Agent 有了记忆。但记忆解决的是"记住过去",还有一个更紧迫的问题:记住计划

你让 Agent 做一个 10 步的任务,它往往做完前 3 步就开始即兴发挥——不是因为它笨,而是 Context Window 是有限的。随着工具调用结果不断堆积,最初的指令被推到越来越远的位置,模型"看不见"后面的步骤了。

所以这课的重点不是先记术语,而是先看清一个工程问题:如果计划只写在最开始那段 Prompt 里,它会被后面的上下文慢慢冲淡。

问题:多步任务中的漂移

让 Agent "重构这个模块:加类型标注、写文档注释、补测试",它可能:

  1. 完成类型标注 ✅
  2. 写好文档注释 ✅
  3. 开始"优化"你没要求的东西 ❌
  4. 忘了还有测试 ❌

原因不是模型能力不够,是工作记忆溢出。每次 tool call 的结果都在消耗 Context Window,原始计划逐渐淡出。

解决方案:给 Agent 一个 todo 工具

核心思路:让 Agent 把计划写进结构化状态,而不是自由文本。

TodoManager

管理待办状态,核心约束:同一时间只能有一个 in_progress。这迫使 Agent 完成当前步骤再推进下一步。

typescript
interface TodoItem {
  id: number
  title: string
  status: 'pending' | 'in_progress' | 'completed'
}

class TodoManager {
  private items: TodoItem[] = []

  update(items: TodoItem[]): string {
    // 校验:最多一个 in_progress
    const inProgress = items.filter(i => i.status === 'in_progress')
    if (inProgress.length > 1) {
      throw new Error('只能有一个任务处于 in_progress 状态')
    }
    this.items = items
    return this.render()
  }

  render(): string {
    if (this.items.length === 0) return '(暂无计划)'
    return this.items.map(item => {
      const icon = item.status === 'completed' ? '✅'
        : item.status === 'in_progress' ? '🔄' : '⬜'
      return `${icon} #${item.id} ${item.title}`
    }).join('\n')
  }
}

todo 工具

注册为普通 tool,和其他工具一样接入 Agent:

typescript
const todoManager = new TodoManager()

const todoTool = tool({
  description: `管理当前会话的任务计划。
收到复杂任务时,先调用此工具拆解为步骤。
每完成一步,更新状态。同一时间只能有一个 in_progress。`,
  inputSchema: z.object({
    items: z.array(z.object({
      id: z.number().describe('任务编号'),
      title: z.string().describe('任务描述'),
      status: z.enum(['pending', 'in_progress', 'completed']),
    })),
  }),
  execute: async ({ items }) => {
    const rendered = todoManager.update(items)
    return { plan: rendered }
  },
})

Nag 提醒:拉回跑偏的 Agent

光有 todo 工具还不够——Agent 可能忙着执行就忘了更新计划。

Nag 可以把它理解成一种“定期把 Agent 拉回当前计划”的提醒机制。解决方法是追踪 Agent 距离上次调用 todo 过了多少轮。超过 3 轮,在下一次工具返回时悄悄注入提醒。

typescript
let roundsSinceTodo = 0
const NAG_THRESHOLD = 3

function onStepFinish(event: { toolCalls?: any[] }) {
  const calledTodo = event.toolCalls?.some(tc => tc.toolName === 'todo')
  if (calledTodo) {
    roundsSinceTodo = 0
  } else {
    roundsSinceTodo++
  }
}

// 在 system prompt 末尾追加提醒
function getSystemPrompt(): string {
  const base = `你是一个能执行多步任务的 AI 助手。
收到复杂任务时,先用 todo 工具拆解计划,然后逐步执行。
每完成一步就更新 todo 状态。同一时间只能有一个任务处于 in_progress。`

  if (roundsSinceTodo >= NAG_THRESHOLD) {
    return base + '\n\n<reminder>你已经连续多轮没有更新计划了,请调用 todo 工具更新当前进度。</reminder>'
  }
  return base
}

这个机制很轻量,但效果显著:结构化计划 + 单任务约束 + 定期提醒,三者组合大幅减少 Agent 偏离。

为什么结构化状态比自由文本好

模型也可以在回答开头写一段"计划"文字,但自由文本有两个问题:

  1. 没有约束——模型可以随便改写、跳步、遗漏,没人校验
  2. 容易被冲走——随着对话推进,那段文字在 Context Window 中越来越远

结构化 todo 不同:

  • 状态机制可校验(同一时间只有一个 in_progress)
  • 每次调用 todo 工具,完整计划作为 tool result 回到上下文最新位置
  • 应用层代码可以追踪、提醒、干预

本课产物

  • ✅ TodoManager(结构化计划 + 单任务约束)
  • ✅ todo 工具(拆解、追踪、更新多步任务)
  • ✅ Nag 提醒机制(Agent 跑偏时自动拉回)

完整代码在 basic/examples/06-memory/02-todo/index.ts

并入主线项目

这一课的 Demo 聚焦在会话规划本身:让 Agent 把计划写进结构化 todo 状态,并通过单一 in_progress 约束和 nag 提醒减少跑偏。

基础项目里,basic/project 承接这套能力:新增 todo 工具管理当前计划,提供 /plan 命令查看会话规划状态,并在连续多轮不更新计划时自动注入 <reminder>

这里的 todo 是 Agent 内部的执行规划工具,不是面向用户的通用待办产品;主线当前落地的是“整包更新计划 + 查看当前计划 + 自动提醒”,不是更细粒度的任务管理系统。

试试看

bash
cd daqi-ai-agent
pnpm exec tsx basic/examples/06-memory/02-todo/index.ts
  1. 重构 hello.ts:加类型标注、写文档注释、补单元测试 — 观察 Agent 自动拆解计划
  2. 创建一个 TypeScript 包:包含 index.ts、utils.ts 和 tests/test_utils.ts — 观察逐步执行
  3. 如果 Agent 连续几轮没更新计划,会看到 <reminder> 出现

面试追问

Q:Agent 在多步任务中容易跑偏,怎么解决?

给 Agent 一个 todo 工具,把计划写成结构化状态而不是自由文本。加两个约束:同一时间只能有一个 in_progress(强制顺序执行),超过 3 轮没更新计划就注入 reminder 提醒。结构化状态每次更新都回到上下文最近位置,不会被 Context Window 冲走。

Q:为什么不直接在 System Prompt 里写计划?

System Prompt 是静态的,不能反映执行过程中的进度变化。todo 工具的 result 每次都回到对话最新位置,相当于"刷新"了 Agent 的工作记忆。而且结构化 schema 可以做约束校验(比如不允许多个 in_progress),自由文本做不到。

Q:这个 todo 是给用户管理待办的吗?

不是。这里的 todo 是 Agent 的会话规划工具——帮 Agent 自己在复杂任务中保持方向。和用户側的"帮我记一下明天要做的事"是两个层面。前者是 Agent 内部的执行策略,后者是面向用户的功能。

面向前端工程师和独立开发者的 AI 应用工程课程