第 4 课:结构化状态:让 Agent 记住任务进行到哪
学习目标:
- 解释为什么把任务进度埋在对话文字里不可靠,必须变成结构化状态
- 说出待办事项从创建到删除要经历的完整生命周期
- 区分"从头重来"和"从中断处恢复"分别付出的代价
- 判断一份任务状态该留在会话历史里,还是该额外写进检查点
前置要求:完成第 3 课,理解外部记忆的两种模式 | 上一课 第 3 课 << | 下一课 第 5 课 >>
进程重启之后,Agent 还知道自己做到哪一步了吗
一个 Agent 正在执行一个多步骤任务:重构一个模块,涉及"改类型定义""改调用点""跑测试""更新文档"四个步骤。它刚做完第二步,进程因为一次意外重启中断了。重新拉起来之后,它面对的是同一个任务描述——这时候它是该从第一步重新做一遍,还是知道自己已经做完了前两步,直接从第三步继续?
答案取决于一件事:任务进行到哪一步,有没有被记录成一份结构化状态,而不只是散落在一堆对话文字里。如果"已经改完类型定义了"这句话只是模型某一轮回复里的一句自然语言描述,淹没在几十条消息中间,宿主代码没办法可靠地从里面提取出"当前处于第几步"这个明确的事实。但如果这个进度被表达成一份有固定字段的任务清单——每一项任务都有明确的状态标记——宿主代码就能直接读出:第一步、第二步是 completed,第三步还没开始。
这也是这一课要解决的核心问题:第 1、2、3 课讲的都是"内容"该怎么管理——对话历史、记忆文件;这一课讲的是"进度"该怎么表达,才能让 Agent 或者宿主应用在中断之后,准确地知道任务做到哪了。
任务清单的生命周期:创建、激活、完成、删除
以 Claude Code 的任务追踪工具为例,官方文档给出了待办事项在执行过程中要经历的完整生命周期,一共四步:1
- 创建(Created):识别出一项任务时,把它作为待办事项加入清单,状态是 pending(待处理)。
- 激活(Activated):真正开始执行这项工作时,把状态改成 in_progress(进行中)。
- 完成(Completed):任务顺利完成时,把状态标记为 completed(已完成)。
- 删除(Removed):不再需要某项待办事项时,通过一次 TaskUpdate 调用把它的状态设成 deleted(已删除)来移除它。1
这四步不是自然语言里含糊的"我做完了""我在做了",而是明确的四个状态值:pending、in_progress、completed,以及表示删除的 deleted。每一次状态变化,都是通过一次明确的工具调用完成的,而不是靠模型在回复文字里随口一提。
官方文档还说明了这套机制具体是怎么体现在对话里的:在一个拥有任务追踪工具的会话里,Claude 会维护一份写下来的待办事项清单,随着工作推进更新每一项的状态,你能在消息流里看到每一次变化——它是以一次结构化的工具调用的形式出现的。1 这句话点出了关键区别:进度不是被动地"体现"在对话里,而是主动地被写成一次可以被单独识别、单独解析的工具调用,这正是"结构化状态"和"散落在文字里的进度描述"的本质差别。
检查点:让恢复不等于从头重来
有了结构化的任务清单,下一个问题是:这份清单本身存在哪里?如果它只存在于这一次会话的对话历史里,一旦这次会话真正结束(不是短暂中断,而是彻底关闭,比如第 3 课讲过的"会话一结束,窗口里的一切就没了"),这份进度记录也会跟着消失——归根到底,API 是无状态的,服务器不替你的会话在请求之间保留任何东西2。
这就是检查点要解决的问题:把任务在某个时间点的状态——已完成哪些步骤、当前在哪一步、还剩哪些步骤——写成一份数据,持久保存到会话生命周期之外的地方。检查点和第 3 课讲的外部记忆用的是同一套底层手段(写文件、之后再读回来),区别在于检查点里存的不是"值得记住的知识",而是"任务进行到哪一步"这类可以直接用来恢复执行的状态。
有了检查点,可恢复性才成立:进程重启之后,Agent 不需要凭空猜测"我做到哪了",而是直接读取最近一份检查点,看到"步骤一、步骤二状态是 completed,步骤三是 in_progress",从步骤三继续,而不是把步骤一、步骤二重新做一遍。
这里的代价对比是具体的:重构任务里"改类型定义"这一步,如果本身是幂等的(重复执行结果一样),从头重来只是浪费时间;但如果某一步是"往数据库里插入一条迁移记录"这种非幂等操作,从头重来可能会插入两条重复记录,甚至污染数据。检查点省下来的不只是时间,还包括避免这类非幂等操作被意外重复执行的风险。
结构化状态,是为了让中断变得可以承受
回到本课开头的场景:进程重启之后,Agent 该从头重来,还是从中断处恢复,答案现在可以说清楚了——取决于两件事有没有做到位:任务进度有没有被表达成结构化状态(而不是散落在对话文字里),这份状态有没有被写进检查点(而不是只活在这一次会话的历史里)。两者缺一不可:只有结构化状态、没有检查点,状态照样在会话结束后消失;只有检查点、没有结构化状态,写进检查点的内容本身就是含糊的自然语言,下次读出来照样没法可靠地知道进行到哪一步。
这一课讲的是"进度怎么表达、怎么留存",下一课要讲的是另一个同样关键的问题:这些被留存下来的状态和记忆,会不会反过来成为攻击者的目标——如果攻击者能往检查点或者记忆文件里写入内容,会发生什么。
小结
- 任务进度只有变成结构化状态,才能被宿主代码可靠地读取;散落在自然语言回复里的进度描述,无法稳定解析出"当前进行到哪一步"
- 待办事项的完整生命周期是创建(pending)、激活(in_progress)、完成(completed)、删除(deleted)四步,每一步都通过一次明确的结构化工具调用完成,能在消息流里被观察到
- 结构化和持久化是两件不同的事:结构化解决"能不能被程序读懂",检查点解决"进程重启或会话结束后状态还在不在"——两者缺一不可
- 检查点把任务在某个时间点的状态写到会话生命周期之外,让恢复变成"从中断处继续",而不是"从头重来",尤其能避免非幂等操作被意外重复执行
- 结构化状态和外部记忆一样,一旦被持久化下来,自身也会成为下一课要讨论的攻击目标——留存下来的东西越多,需要守住的边界也越多
>> 第 5 课:记忆的边界与安全