第 3 章 上下文、记忆与状态

学习目标

  • 说明上下文退化与注意力预算,并确立选择上下文信息的标准。
  • 在压缩、工具结果清理、结构化笔记与子智能体之间,选择符合任务特点的方法。
  • 区分对话记录、任务状态、长期记忆和权威记录系统,在长时间运行中防止上下文污染。
  • 通过检查点与幂等性设计中断后的恢复,并在恢复时核实外部状态。
  • 为跨多个会话的任务建立交接结构,让下一位执行者能够接手。

引言

一个被安排执行为期两天任务的智能体,在第二天下午说出了一句奇怪的话:“工作差不多完成了。”检查后却发现,有一半工作根本还没有开始,而第一天上午与团队商定的一条规则,也已经消失得无影无踪。这并不是因为智能体懒惰。在这段时间里,上下文曾多次被填满,每次填满后都被替换为摘要。摘要虽然延续了工作脉络,却没能保存准确的任务状态。

这就好比一个人在做长期项目时不作笔记,全凭记忆推进。本章介绍如何给智能体配备笔记本和工作日志。我们先梳理哪些内容应放入上下文、哪些应保存在外部,如何选择压缩、外部记忆与子智能体,以及如何防止上下文污染,然后再讨论检查点、中断后的恢复和工作交接。

核心概念

上下文是有限的预算

Anthropic 将上下文工程定义为:在推理期间,筛选并维护输入模型的最优 token 集合的一整套策略。如果说提示词工程是打磨指令,上下文工程处理的就是模型看到的一切,包括系统提示词、工具定义、工具结果、消息记录和外部数据。

其依据是上下文退化(context rot):随着上下文中的 token 增加,准确回忆其中信息的能力会下降。同一篇文章解释,LLM 存在注意力预算(attention budget)。Transformer 会计算每个 token 与上下文中其他 token 的关系,因此关系数量随 token 数量的平方(n²)增长;token 越多,注意力被分摊得越薄。每个新增 token 都会消耗一点预算。

可以把上下文比作桌面空间。桌子再大,如果摊满所有文件,也难以找到此刻需要的那一张。窗口大意味着能放入更多内容,并不意味着模型能同样有效地利用所有内容。

放入什么:抽象层次、工具与示例

同一篇文章为上下文的各组成部分给出标准。

要素 应避免 应追求
系统提示词 写死复杂、易失效的 if-else 逻辑,或走向另一极端,使用缺少具体线索的模糊指引 既具体到能引导行为,又保有灵活性的“合适高度”
工具 功能过宽,或难以判断应使用哪个工具的臃肿工具集 连人工工程师也能立即说出应选哪个工具的配置
示例 罗列所有边界情况 少量多样且有代表性、能够清楚展示预期行为的示例

“合适高度”借用了飞机飞行高度的比喻。飞得太低,会被每一处地形牵制;飞得太高,又看不到目的地。把 180 个例外情况罗列为规则的提示词,每修改一条规则,就可能破坏另一个案例。改为原则与代表性示例,再用评估集(第 4 章)守住预期行为,更容易维护。

即时检索与渐进式披露

不预先放入全部数据,而是让智能体只持有文件路径、已保存查询、链接等轻量标识,需要时再通过工具加载,这称为即时检索(just-in-time retrieval)。Claude Code 不会整块读入大型数据,而是用 head、tail 等命令只查看所需部分。智能体边探索边逐步发现所需上下文,称为渐进式披露(progressive disclosure)。这就像人不会背下整本书,而是使用目录与书签。

即时检索并非总是更好,因为运行时探索比提前放入更慢。因此,同一篇文章介绍了混合策略:Claude Code 一开始就加载 CLAUDE.md,其余文件则通过 glob 和 grep 随时查找。几乎每次都需要的稳定资料提前放入,大而常变的资料按需检索;法律或金融等上下文动态性较低的领域,可以提高预加载的比例。

检索成功不代表找到了正确答案。检索结果还应携带来源可信度、文档适用时间,以及当前用户的访问权限。找不到依据时,让智能体说“未找到”,而不是断言“不存在”,也是检索设计的一部分。

信息的四个层次

长时间运行的系统中有四类性质不同的信息,混在一起就容易出事故。

层次 示例 权威性 存续时间
对话记录 消息与工具结果 低,会被摘要和截断 会话期间
任务状态 进度文件、检查点 恢复的依据 直到任务结束
长期记忆 用户偏好、经验教训 参考资料,需要验证 直到规定的有效期
权威记录系统 订单、转账、审批记录 最高,是事实依据 法律或业务规定的保留期

权威记录系统(system of record)是业务事实的最终权威系统。即使智能体记得“已经退款”,退款是否真的发生,仍由支付系统的账本决定。恢复时,决定下一步做什么的依据应是任务状态,而不是对话记录;任务状态与权威记录系统不一致时,以权威记录系统为准。

压缩与工具结果清理

压缩(compaction)是在窗口接近上限时,把对话概括为摘要,再以摘要开始新窗口的技术。Anthropic 解释,好的压缩应保留架构决定、未解决的缺陷和实现细节,丢弃重复工具输出。建议先调整压缩提示词,确保不遗漏内容(召回率),再减少冗余(精确率)。

最轻量、安全的压缩方式是工具结果清理,因为留在较早对话中的工具原始结果通常不需要再次阅读。平台也提供相关功能:Claude API 的上下文编辑(context editing)支持在服务器端清理旧工具结果,可配置何时开始(trigger)、保留最近多少项(keep)、每次至少清理多少(clear_at_least),以及不清理哪些工具(exclude_tools)。Claude API 还提供服务器端压缩(测试版);OpenAI Agents SDK 也有通过 Responses API 压缩会话历史的会话实现。名称与选项可能变化,请查阅文档。

注意

摘要可以延续脉络,却未必保存精确状态。“完成到了哪里”“作出了什么决定”等必须准确的信息,不要交给压缩,应保存在上下文之外的结构化记录中。

结构化笔记与外部记忆

结构化笔记让智能体在上下文窗口之外写下笔记,之后再读取。Anthropic 的示例包括 Claude Code 的待办列表、NOTES.md 文件,以及跨数千步记录进度和地图的宝可梦游戏实验。这种方法适合有明确里程碑的重复任务。

把这种方式做成产品功能,就是记忆工具。Claude 的记忆工具在客户端运行:模型请求对 /memories 下的文件操作,应用处理器在自行选择的存储中执行,因此安全措施也由应用负责。文档建议规范化所有路径,验证它们没有超出 /memories(防止 ../ 等路径遍历攻击),过滤敏感信息,限制文件大小,并删除长期未使用的文件。

Google ADK 区分两个层次:会话与状态是当前单个对话的短期记忆;记忆服务是可以从多个过去对话中检索信息的长期记忆存储(用 add_session_to_memory 添加,用 search_memory 检索)。开发用的内存实现重启后会丢失,因此生产环境应使用持久化实现。

用子智能体隔离上下文

子智能体结构让专业化子智能体在干净的窗口中处理专注的任务,只返回压缩后的结果。Anthropic 指出,子智能体即使探索了数万 token,通常也只返回 1,000~2,000 token 的摘要。探索噪声不进入主智能体窗口,主智能体就能专注于综合与判断。这适合能从并行探索中获益的复杂调查。

有些结果不能只用摘要。对于表格、代码等不应改动任何字符的产物,如果由主智能体重新抄写,不仅占满上下文,转写时还可能出错。Anthropic 的研究系统让子智能体将结果写入外部存储(例如文件),只返回轻量引用(文件路径、存储键等),以减少信息损失和 token 浪费。同一篇文章也介绍了主导智能体把计划存入记忆的方式,因为上下文超过 200,000 个 token 时会被截断。

三种方法的用途可整理如下。

方法 适用任务 可能丢失的内容
压缩 需要长时间来回交互的任务 精确状态、早期决定
结构化笔记 里程碑明确的重复任务 没写进笔记的内容
子智能体 能从并行探索中获益的调查 摘要遗漏的细节(原文通过引用保留)

防止上下文污染

上下文污染是指错误、过时或不可信的信息在上下文中不断积累,持续扭曲之后判断的现象。它有三种成因:失败尝试留下的痕迹,即一连串产生同样错误的尝试都保留在上下文中,使模型困在那条路径上;不可信的内容,即网页或文档中包含的指令;过时的记忆,即实际情况已经发生变化的事实。

预防规则如下。

  1. 删除已经完成用途的工具原始结果,只留下仍然需要的要点和引用。
  2. 如果同一种失败反复出现,就把失败记录压缩为一行摘要,明确写出新的处理方法,再重新开始。必要时,把任务交给上下文干净的子智能体。
  3. 对来自外部的内容标明出处,与指令区分开来,不把这些内容中的句子当作指令遵从(第 5 章)。
  4. 为每个记忆条目附上来源和观察时间;对于过时条目,重新确认或丢弃。
  5. 未经验证,不得把用户说的话提升到系统指令的层级。记忆只是数据,并不赋予权限。

提示词缓存与上下文组织

长时间运行的智能体会几十、上百次地重发相似的前部内容。提示词缓存复用对这段重复前缀(prefix)的处理,以降低成本和延迟。按照 Claude API 文档,缓存以前缀顺序 tools、system、messages 构建;任何一层变化,都会使该层及其后所有层失效。修改工具定义会使整个缓存失效。默认有效期为 5 分钟,每次使用都会刷新,也有 1 小时选项。不同模型有可缓存的最小长度;短于该长度的前缀,即使标记也不会缓存。可通过响应中的 cache_read_input_tokens 与 cache_creation_input_tokens 检查缓存命中。

破坏缓存的常见错误包括:把每次请求都变化的值(当前时间、请求编号)放在固定指令之前;每次重新生成工具定义,改变其内容或顺序;使用键顺序随机变化的 JSON 序列化。原则是稳定内容放前面,变化内容放后面。工具结果清理也会与缓存冲突:从删除位置起缓存会失效,因此文档建议一次清理足够多的内容,让收益值得承担失效代价(clear_at_least)。

检查点与幂等性

对于会在外部留下影响的长时间任务,发生失败时不能从头重新执行。Anthropic 关于研究系统的文章指出,智能体会保存状态,而且错误会不断累积。文章介绍,团队把能够从出错位置恢复的系统,与重试逻辑、定期检查点等确定性安全措施结合起来。文章还建议,应让智能体知道工具正在执行失败,因为即使是小小的失败,也可能造成致命后果。

检查点是持久保存各步骤结果、使执行能够恢复的节点;幂等键则是一种标识符,用来确保同一个请求即使重复到达,也只产生一次效果。设计中需要包含三个要素。

  1. 将每个步骤做了什么、何时执行、得到了什么结果,都记录在持久化存储中。
  2. 为外部调用添加由任务与对象确定性生成的幂等键(例如 작업ID:대상ID:단계)。
  3. 恢复执行时,根据已保存的记录和权威账本决定接下来要做的事,而不是依据模型的记忆。

中断后的恢复设计

从保存位置继续,并不自动意味着恢复安全,因为保存之后外部世界可能已经变化。恢复流程应加入以下检查。

检查 原因
核对外部状态 用账本或查询 API 确认最后记录之后,外部操作是否实际发生
检查过期情况 审批、凭据、预览标识与句柄是否仍有效
执行所有权 是否已有其他执行器接管同一任务(通过租约及其到期时间管理,租约是在限定时间内有效的占用权)
版本兼容 当前代码与智能体定义是否能读取已保存状态

框架也能提供帮助。OpenAI Agents SDK 支持把等待人工审批的运行序列化为 RunState,保存到数据库或队列,之后恢复继续。文档指出,序列化状态包含审批决定、待执行工具调用及其参数,因此应保存在服务器控制的存储中,只恢复可信快照,并把智能体定义或 SDK 的版本标识与状态一起保存。在 Google ADK 中,状态必须以事件的 state_delta 记录才会持久化;直接修改从服务取得的会话的 session.state,可能因无法追踪而不会保存。状态值只能使用可序列化的基本类型。

长时任务的交接

Anthropic 的长期编码智能体实验观察到两种失败:一次想做太多事情,中途用尽上下文;后续会话看到部分进展,就宣告任务完成。解决方案是分离角色。

  • 初始化智能体(首次会话):创建用于启动开发服务器的 init.sh、记录智能体所做工作的 claude-progress.txt、展示初始新增文件的首次提交,以及一个将 200 多项功能全部标为“失败”的功能列表。
  • 编码智能体(后续会话):每次只推进一项功能,通过描述清楚的提交与进度记录,为下一会话留下线索,并在结束时让环境保持整洁状态。

功能列表使用 JSON,因为相比 Markdown,模型不当修改或覆盖 JSON 的可能性更低。编码智能体只允许修改 passes 字段,并被明确禁止删除或修改测试。新会话先确认工作目录,读取 Git 历史与进度文件,从功能列表选择优先级最高的未完成功能,启动开发服务器,测试基本行为,然后才开始工作。只有像人一样完成端到端测试并确认后,才能将功能标记为通过。

将这一实验推广,就得到交接包:让下一位执行者无需阅读整段长对话即可接手的文档。内容包括目标与完成标准、已完成项目及验证结果、剩余工作与优先级、已确认事实和仍属假设的内容、产物与外部操作标识,以及下一步行动的前提和需要额外审批的位置。关键是记录经过验证的产物,而不是“基本完成”这类自我评价。

对话状态放在哪里

还需要决定由谁管理对话记录。OpenAI Agents SDK 提供三种方式:把结果直接作为下一次输入列表的手动管理;运行前加载记录、运行后保存新条目的会话;以及服务器端对话管理(conversation_id 或 previous_response_id)。文档建议每个对话只选择一种持久化策略,并警告:同一次运行不能同时使用会话与服务器端续接选项;混用客户端管理的记录与服务器管理的状态,可能导致上下文重复。

Google ADK 通过键前缀定义状态范围。

前缀 范围
无 当前会话
user: 同一应用中,该用户的所有会话
app: 该应用的所有用户与会话
temp: 单次调用(invocation)期间,结束即丢弃

状态作用域设置错误,可能导致一个用户的信息泄露给另一个用户,或导致必要信息在下一会话中丢失。

深入阅读

检查点与外部副作用之间的间隙

保存检查点与修改外部系统并不处于同一个事务之中。外部调用刚刚成功、结果尚未写入记录时,进程就可能终止。如果恢复代码只查看记录,就会判断“还没有执行”,从而把同一件事再做一遍。

处理这一间隙的标准流程是记录意图 → 执行 → 记录结果。执行之前,先记录“打算使用这个幂等键做这件事”,然后执行,最后记录结果。恢复时,如果遇到有意图记录、却没有结果记录的条目,必须在重新执行之前,先使用幂等键向外部系统查询是否已经处理。如果已经处理,就只补记结果;如果尚未处理,才使用同一个键执行。如果外部系统不支持幂等键,那么重复执行所造成的损害越大的任务,就越需要先经过人工确认。对于无法撤销全部步骤的任务,还必须一并设计第 5 章介绍的补偿操作。

记忆的生命周期

长期记忆一旦存错,就会反复影响多个任务,因此记忆也需要生命周期。

  1. 记忆保留标准:确定什么可以成为记忆。只保留反复出现或经用户确认的信息,而不是一闪而过的话。
  2. 来源与时间:同时记录谁在何时说过,以及依据什么确认。
  3. 冲突解决:新信息与旧记忆不同时,根据新旧程度和来源权威性决定,并保留变更历史。
  4. 过期与重新确认:设置有效期,到期后重新确认或丢弃。
  5. 更正与删除的传播:用户要求删除时,不仅删除原始记录,也删除检索索引及由该记忆生成的摘要。

第五步涉及个人信息保护义务,因此应与隐私负责人一起,根据适用法律与条款确定保留和删除标准。技术上,应按用户隔离记忆存储,并设置测试,确认删除确实发生。

记忆与压缩策略也要评估

修改压缩提示词或记忆策略之后,成本可能下降,准确率却也随之降低。请把策略本身作为评估对象。将它与不使用记忆的基线比较,加入用来检验压缩刚结束时是否仍能准确遵循先前决定的任务,并故意放入过时记忆,观察能否将其过滤掉。强制中断后再测量恢复成功率,也是一个有用的指标。

工作场景

银河图书:1,800 本书转换任务遗忘的决定

电子书发行商“银河图书”让智能体把 1,800 本旧电子书转换为新格式,并补充无障碍信息。第一次尝试采用开启自动压缩的单个长会话。第三天,智能体忘记了第一天与团队约定的规则——“表格图片的替代文本取自原始图注”,以其他方式处理了 140 本书。还有 60 本没有运行验证器,就被标成“完成”。

团队否决了两个方案。更换上下文窗口更大的模型,只会推迟决定在摘要中消失的问题;在系统提示词中写“不要忘记决定”,则是在重复把记忆交给模型的错误。

新结构中,首次会话只做初始化:创建 books.json,包含 1,800 本书的状态(pending、converted、validated、failed)和验证结果路径;创建记录约定转换规则的 decisions.md,以及运行转换器和验证器的 init.sh,再提交。后续会话开始时读取这三个文件和 Git 历史,每次处理 20 本。只有验证器通过后,状态才能改为 validated;验证器原始日志只保留要点,其余从上下文清理。出现新规则时,先写入 decisions.md 并提交,再继续工作。

接下来的 1,000 本没有返工,也没有未经验证就标为完成的书。每个会话的上下文用量反而下降了。

田垄政策研究所:接手为期三天的调查

“田垄政策研究所”用三天调查韩国全国基层地方自治团体的青年住房支持政策,并制作比较表。第一天会话留下摘要:“41 处中已完成 23 处调查。”第二天上午,委托方在比较标准中增加了收入条件;同时,已调查的两个地方政府发布公告,修改了补助金额。第二天的会话直接相信第一天的摘要,只调查剩余 18 处。最终表格留下两项旧金额,第一天调查的 23 处收入条件栏也都是空白。

团队比较了方案。把第一天全部对话交给下一会话,既占上下文,又无法说明发生了什么变化;每天从头调查,成本则变为三倍。团队选择了交接包与恢复检查。

交接包记录比较标准文档版本、各地方政府的状态与已调查字段、依据 URL 与核实时间、仍属假设的条目,以及下一会话首先要做的事。新会话在开工前检查标准文档版本;版本变化时,把新增字段仍为空的地方政府重新列入调查。核实时间超过 24 小时的依据,先检查公告页面是否更新。调查结果按地方政府分别保存为文件,会话中只携带文件路径。

下一次调查中,标准变更了一次,五处公告发生变化,但最终表格中没有任何过时数据。

易错点

  1. “窗口大,就不需要上下文管理。” 数字变大,似乎什么都能装下,但窗口越满,回忆准确率越低。大窗口只是推迟了管理的需要。
  2. “压缩摘要也会保存进度状态。” 读摘要时脉络连贯,容易放心,但精确状态和早期决定可能遗漏。必须准确的信息应使用结构化记录保存。
  3. “恢复时问模型做到哪里即可。” 模型能给出看似合理的回答,确实方便,但新进程中的模型不了解上一次运行。应以记录与账本判断状态。
  4. “从检查点继续就是安全的。” 已保存令人安心,但保存后外部状态可能变化,审批也可能过期。恢复前应核对。
  5. “子智能体返回完整表格就能保证准确。” 直接拿到原件看似准确,但主导智能体重新转写时,会占满上下文并写错数值。应把结果留在存储中,传递引用。
  6. “把用户发言直接存入长期记忆,就实现了个性化。” 这虽是最快的方法,但将未经验证的用户发言直接存为长期记忆,会为过时信息与指令注入打开入口。
  7. “工具结果清理得越频繁越好。” 清理能减轻窗口负担,但每次清理都可能使提示词缓存失效。应一次清理足够多的内容。

要点速览

项目 应记住的答案
上下文有限的原因 上下文退化、注意力预算、token 之间的 n² 级关系
系统提示词标准 合适的抽象层次、少量代表性示例
即时检索 轻量标识加按需加载,稳定核心资料预先放入
最轻量的压缩 清理旧工具结果
方法选择 交互多用压缩,有里程碑用笔记,并行调查用子智能体
精确状态的位置 上下文之外的结构化记录,与账本不一致时以账本为准
保护缓存的布局 稳定内容在前,变化内容在后
恢复流程 读取记录 → 核对外部状态 → 检查过期、所有权与版本 → 用幂等键执行
交接 经过验证的产物、区分事实与假设、下一步行动的前提

查阅官方文档

自测

3-1. 请使用“注意力预算”一词,说明为什么即使上下文窗口变大,也仍然需要上下文管理。

3-2. 为什么清理旧工具结果被视为最轻量的压缩?与提示词缓存一起使用时,应注意什么?

3-3. 在对话记录、任务状态、长期记忆与权威记录系统中,恢复时应依据什么决定下一步?该依据与权威记录系统不一致时怎么办?

3-4. 长期编码运行框架把功能列表保存为 JSON,并只允许修改是否通过的字段,这样设计是为了防止什么失败?

3-5. 以进程在记录结果前崩溃的情况为例,说明为什么检查点应按“记录意图 → 执行 → 记录结果”的顺序保存。

3-6. 列出每条长期记忆都要保存来源与观察时间的两个原因。

3-7. 为把三天调查任务交给其他执行者的交接文档,设计五行目录,并用一句话说明每行应包含什么。

实操练习

练习

进度文件与强制中断后的恢复(60 分钟)

  1. 选择一个按顺序处理至少 50 项内容的任务,例如文档摘要或数据转换。
  2. 设计 JSON 进度文件,包含各项状态(pending、in_progress、done、failed)、幂等键与结果路径。
  3. 对外部写入操作采用“记录意图 → 执行 → 记录结果”的顺序。
  4. 在运行期间随机选择时点,强制终止进程三次并重新运行,其中一次应在外部写入后立即终止。

完成标准: 重新运行会按进度文件继续,任何项目都未被处理两次,且最终结果与无中断运行相同。

练习

检查压缩前后的信息损失(40 分钟)

  1. 选择一段至少持续 30 轮的真实智能体对话,删除敏感信息。
  2. 先由人工从对话中提取后续工作必需的 10 条信息(决定、未解决问题、标识、数值),列成清单。
  3. 用当前压缩提示词生成摘要,统计 10 条中准确保留了多少条。
  4. 根据遗漏内容修改压缩提示词,再次统计,然后检查缩短摘要后是否仍能保持分数。

完成标准: 修改后的提示词至少保留 10 条中的 9 条,并用表格记录修改前后的保留数量与摘要长度。