第 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 时会被截断。
三种方法的用途可整理如下。
| 方法 | 适用任务 | 可能丢失的内容 |
|---|---|---|
| 压缩 | 需要长时间来回交互的任务 | 精确状态、早期决定 |
| 结构化笔记 | 里程碑明确的重复任务 | 没写进笔记的内容 |
| 子智能体 | 能从并行探索中获益的调查 | 摘要遗漏的细节(原文通过引用保留) |
防止上下文污染
上下文污染是指错误、过时或不可信的信息在上下文中不断积累,持续扭曲之后判断的现象。它有三种成因:失败尝试留下的痕迹,即一连串产生同样错误的尝试都保留在上下文中,使模型困在那条路径上;不可信的内容,即网页或文档中包含的指令;过时的记忆,即实际情况已经发生变化的事实。
预防规则如下。
- 删除已经完成用途的工具原始结果,只留下仍然需要的要点和引用。
- 如果同一种失败反复出现,就把失败记录压缩为一行摘要,明确写出新的处理方法,再重新开始。必要时,把任务交给上下文干净的子智能体。
- 对来自外部的内容标明出处,与指令区分开来,不把这些内容中的句子当作指令遵从(第 5 章)。
- 为每个记忆条目附上来源和观察时间;对于过时条目,重新确认或丢弃。
- 未经验证,不得把用户说的话提升到系统指令的层级。记忆只是数据,并不赋予权限。
提示词缓存与上下文组织
长时间运行的智能体会几十、上百次地重发相似的前部内容。提示词缓存复用对这段重复前缀(prefix)的处理,以降低成本和延迟。按照 Claude API 文档,缓存以前缀顺序 tools、system、messages 构建;任何一层变化,都会使该层及其后所有层失效。修改工具定义会使整个缓存失效。默认有效期为 5 分钟,每次使用都会刷新,也有 1 小时选项。不同模型有可缓存的最小长度;短于该长度的前缀,即使标记也不会缓存。可通过响应中的 cache_read_input_tokens 与 cache_creation_input_tokens 检查缓存命中。
破坏缓存的常见错误包括:把每次请求都变化的值(当前时间、请求编号)放在固定指令之前;每次重新生成工具定义,改变其内容或顺序;使用键顺序随机变化的 JSON 序列化。原则是稳定内容放前面,变化内容放后面。工具结果清理也会与缓存冲突:从删除位置起缓存会失效,因此文档建议一次清理足够多的内容,让收益值得承担失效代价(clear_at_least)。
检查点与幂等性
对于会在外部留下影响的长时间任务,发生失败时不能从头重新执行。Anthropic 关于研究系统的文章指出,智能体会保存状态,而且错误会不断累积。文章介绍,团队把能够从出错位置恢复的系统,与重试逻辑、定期检查点等确定性安全措施结合起来。文章还建议,应让智能体知道工具正在执行失败,因为即使是小小的失败,也可能造成致命后果。
检查点是持久保存各步骤结果、使执行能够恢复的节点;幂等键则是一种标识符,用来确保同一个请求即使重复到达,也只产生一次效果。设计中需要包含三个要素。
- 将每个步骤做了什么、何时执行、得到了什么结果,都记录在持久化存储中。
- 为外部调用添加由任务与对象确定性生成的幂等键(例如
작업ID:대상ID:단계)。 - 恢复执行时,根据已保存的记录和权威账本决定接下来要做的事,而不是依据模型的记忆。
中断后的恢复设计
从保存位置继续,并不自动意味着恢复安全,因为保存之后外部世界可能已经变化。恢复流程应加入以下检查。
| 检查 | 原因 |
|---|---|
| 核对外部状态 | 用账本或查询 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,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 小时的依据,先检查公告页面是否更新。调查结果按地方政府分别保存为文件,会话中只携带文件路径。
下一次调查中,标准变更了一次,五处公告发生变化,但最终表格中没有任何过时数据。
易错点
- “窗口大,就不需要上下文管理。” 数字变大,似乎什么都能装下,但窗口越满,回忆准确率越低。大窗口只是推迟了管理的需要。
- “压缩摘要也会保存进度状态。” 读摘要时脉络连贯,容易放心,但精确状态和早期决定可能遗漏。必须准确的信息应使用结构化记录保存。
- “恢复时问模型做到哪里即可。” 模型能给出看似合理的回答,确实方便,但新进程中的模型不了解上一次运行。应以记录与账本判断状态。
- “从检查点继续就是安全的。” 已保存令人安心,但保存后外部状态可能变化,审批也可能过期。恢复前应核对。
- “子智能体返回完整表格就能保证准确。” 直接拿到原件看似准确,但主导智能体重新转写时,会占满上下文并写错数值。应把结果留在存储中,传递引用。
- “把用户发言直接存入长期记忆,就实现了个性化。” 这虽是最快的方法,但将未经验证的用户发言直接存为长期记忆,会为过时信息与指令注入打开入口。
- “工具结果清理得越频繁越好。” 清理能减轻窗口负担,但每次清理都可能使提示词缓存失效。应一次清理足够多的内容。
要点速览
| 项目 | 应记住的答案 |
|---|---|
| 上下文有限的原因 | 上下文退化、注意力预算、token 之间的 n² 级关系 |
| 系统提示词标准 | 合适的抽象层次、少量代表性示例 |
| 即时检索 | 轻量标识加按需加载,稳定核心资料预先放入 |
| 最轻量的压缩 | 清理旧工具结果 |
| 方法选择 | 交互多用压缩,有里程碑用笔记,并行调查用子智能体 |
| 精确状态的位置 | 上下文之外的结构化记录,与账本不一致时以账本为准 |
| 保护缓存的布局 | 稳定内容在前,变化内容在后 |
| 恢复流程 | 读取记录 → 核对外部状态 → 检查过期、所有权与版本 → 用幂等键执行 |
| 交接 | 经过验证的产物、区分事实与假设、下一步行动的前提 |
查阅官方文档
- Anthropic: Effective context engineering for AI agents — 上下文退化、合适的抽象层次、即时检索、长时任务的三种方法
- Anthropic: Effective harnesses for long-running agents — 初始化与编码智能体、JSON 功能列表、会话启动流程
- Claude API: Context editing — 工具结果清理选项及其与缓存的关系
- Claude API: Memory tool — 客户端记忆、防止路径遍历、大小与过期
- Claude API: Prompt caching — 缓存顺序、失效规则、有效期
- OpenAI Agents SDK: Sessions — 会话实现及其与服务器端续接的关系
- OpenAI Agents SDK: Human-in-the-loop —
RunState序列化与保存注意事项 - Google ADK: State — 状态前缀、持久化与状态更新方法
自测
3-1. 请使用“注意力预算”一词,说明为什么即使上下文窗口变大,也仍然需要上下文管理。
3-2. 为什么清理旧工具结果被视为最轻量的压缩?与提示词缓存一起使用时,应注意什么?
3-3. 在对话记录、任务状态、长期记忆与权威记录系统中,恢复时应依据什么决定下一步?该依据与权威记录系统不一致时怎么办?
3-4. 长期编码运行框架把功能列表保存为 JSON,并只允许修改是否通过的字段,这样设计是为了防止什么失败?
3-5. 以进程在记录结果前崩溃的情况为例,说明为什么检查点应按“记录意图 → 执行 → 记录结果”的顺序保存。
3-6. 列出每条长期记忆都要保存来源与观察时间的两个原因。
3-7. 为把三天调查任务交给其他执行者的交接文档,设计五行目录,并用一句话说明每行应包含什么。
实操练习
练习
进度文件与强制中断后的恢复(60 分钟)
- 选择一个按顺序处理至少 50 项内容的任务,例如文档摘要或数据转换。
- 设计 JSON 进度文件,包含各项状态(
pending、in_progress、done、failed)、幂等键与结果路径。 - 对外部写入操作采用“记录意图 → 执行 → 记录结果”的顺序。
- 在运行期间随机选择时点,强制终止进程三次并重新运行,其中一次应在外部写入后立即终止。
完成标准: 重新运行会按进度文件继续,任何项目都未被处理两次,且最终结果与无中断运行相同。
练习
检查压缩前后的信息损失(40 分钟)
- 选择一段至少持续 30 轮的真实智能体对话,删除敏感信息。
- 先由人工从对话中提取后续工作必需的 10 条信息(决定、未解决问题、标识、数值),列成清单。
- 用当前压缩提示词生成摘要,统计 10 条中准确保留了多少条。
- 根据遗漏内容修改压缩提示词,再次统计,然后检查缩短摘要后是否仍能保持分数。
完成标准: 修改后的提示词至少保留 10 条中的 9 条,并用表格记录修改前后的保留数量与摘要长度。