在使用 Pi、Codex 或其他 AI Agent 时,我们经常会产生一种错觉:只要会话没有关闭,模型就能记住从开始到现在的全部内容。
事实并非如此。
模型拥有的是一个大小有限的上下文窗口,而 Agent 保存的 Session 可以持续增长。为了让长期任务继续运行,Agent 必须不断把庞大的历史记录整理成模型当前能够处理的“工作上下文”。这个过程就是上下文压缩(Context Compaction)。
一、Session 不等于上下文
理解上下文压缩,首先需要区分两个概念:
1Session = 完整档案库 2Context = 本次模型调用能够看到的工作材料
Session 可以包含:
- 用户和助手的完整消息;
- 工具调用及其结果;
- 模型切换记录;
- 对话分支;
- 压缩记录;
- 文件读取和修改信息。
上下文则受到模型窗口限制。即使 Session 已经积累了几十万甚至上百万 Token,一次模型请求也只能携带窗口允许的部分内容。
因此,模型并不是直接“读取整个 Session”。真正的过程是:
1完整 Session 2 ↓ 3选择当前对话分支 4 ↓ 5排除不需要发送的内容 6 ↓ 7用摘要替代较早历史 8 ↓ 9保留最近的原始消息 10 ↓ 11构造本次模型请求
二、上下文压缩是如何工作的
一次典型的压缩可以分为五步。
1. 判断是否接近模型窗口
Agent 通常不会等到模型明确返回“上下文超限”才处理,而是预留一部分安全空间。
以 Pi 为例,自动压缩的判断可以概括为:
1contextTokens > contextWindow - reserveTokens
Pi 默认预留 16,384 Token,并默认保留最近约 20,000 Token 的原始内容。这些值可以配置。
预留空间有两个作用:
- 为模型下一次回复和工具调用留下空间;
- 容纳 Token 估算与模型实际计费之间的误差。
2. 确定历史切割点
Agent 从最新消息向前计算,尽量保留最近的完整对话轮次:
1用户消息 2→ 助手回复 3→ 工具调用 4→ 工具结果
正常情况下,压缩不会把工具调用和工具结果从中间拆开。如果单个任务本身已经特别长,也可能在同一轮任务中切割,再分别总结历史部分和当前任务的前半部分。
3. 为较早内容生成摘要
需要压缩的旧消息会被序列化并交给模型总结。Pi 默认采用结构化格式,内容通常包括:
1## Goal 2用户正在实现什么目标 3 4## Constraints & Preferences 5用户提出的约束和偏好 6 7## Progress 8已经完成、正在进行和被阻塞的工作 9 10## Key Decisions 11关键决策及其原因 12 13## Next Steps 14下一步工作 15 16## Critical Context 17继续任务必须知道的信息 18 19<read-files> 20读取过的文件 21</read-files> 22 23<modified-files> 24修改过的文件 25</modified-files>
这种结构并不是为了给人阅读得更漂亮,而是为了让模型在失去原始历史后,仍然能够恢复任务状态。
4. 追加压缩检查点
压缩通常不会直接覆盖或删除原始 Session,而是追加一条压缩记录。
以 Pi 的 CompactionEntry 为例:
1{ 2 "type": "compaction", 3 "summary": "较早历史的结构化摘要", 4 "firstKeptEntryId": "最近原始消息的起点", 5 "tokensBefore": 100000 6}
较新的实现还可以把保留的近期消息写入 retainedTail,使压缩记录成为一个可以独立恢复的上下文检查点。
5. 重建下一轮模型上下文
压缩完成后,模型下一轮看到的内容变为:
1System Prompt 2+ 历史摘要 3+ 最近保留的原始消息 4+ 压缩后产生的新消息
旧消息仍然存在于 Session 文件中,只是不再发送给模型。
所以更准确的说法是:
物理上保留完整历史,逻辑上用摘要替代较早上下文。
三、模型不会主动读取 Session
模型本身无法访问 Agent 的 Session 文件,也不知道磁盘上是否保存了完整历史。
它只能看到 Agent 在本次请求中发送的内容。所谓“模型读取压缩摘要”,实际是 Agent 主动完成了以下工作:
1Agent 读取 Session 2→ 找到最新压缩检查点 3→ 取出摘要和最近消息 4→ 拼接 System Prompt 5→ 发送给模型
因此,上下文管理是 Agent 框架的能力,而不是模型自己拥有了无限记忆。
四、压缩时机为什么不可能绝对精确
Agent 记录的上下文与模型实际收到的请求并不一定逐字一致。
中间可能存在:
- System Prompt 的动态构建;
- 当前分支筛选;
- 不进入模型上下文的命令记录;
- Extension 对消息的临时过滤或注入;
- Provider 对消息、工具和图片的格式转换;
- 最终请求 Payload 的重写;
- 不同模型 Tokenizer 的计算差异。
Pi 会优先参考上一次模型响应返回的实际 Usage,再估算随后新增的消息。由于仍存在误差,默认的 reserveTokens 会提供安全余量。
这会产生两种情况。
提前压缩
1Pi 估算已经达到阈值 2模型实际仍有空间
压缩依然可以成功,因为生成摘要并不要求原请求已经真正溢出。提前压缩的代价主要是额外费用、延迟,以及旧内容更早被有损摘要替代。
延迟压缩
1Pi 估算仍未达到阈值 2模型实际请求已经超限
此时 Agent 可以在 Provider 返回上下文溢出后触发压缩,并重试刚才的请求。
因此,成熟的上下文管理通常包含两层保护:
1主动保护:接近阈值时提前压缩 2被动保护:实际溢出后压缩并重试
五、压缩是有损的,不等于长期记忆
压缩摘要会保留主要目标和任务状态,但不保证保留每个细节。
多次压缩后可能出现:
- 用户原始措辞丢失;
- 次要约束被忽略;
- 工具输出被截断;
- 早期事实逐渐失真;
- 摘要中的错误被后续摘要继承。
因此,以下内容不应该只依赖上下文摘要:
- 用户长期偏好;
- 权限和组织信息;
- 已确认的业务配置;
- 订单、任务和资源 ID;
- 必须精确保留的历史事实;
- 系统知识与产品说明。
一个更合理的分工是:
1业务数据库 2├── 完整聊天记录 3├── 用户长期记忆 4├── 权限与业务状态 5└── 可审计的关键事实 6 7Agent Session 8├── 当前任务历史 9├── 工具调用轨迹 10└── 压缩检查点 11 12模型上下文 13├── System Prompt 14├── 历史摘要 15├── 最近消息 16└── 当前工具结果
六、Pi 与 Codex 的机制是否相同
它们解决的是同一个问题,但实现方式并不完全相同。
Pi 的压缩结果通常是可读的结构化摘要,并作为 CompactionEntry 保存在 JSONL Session 中。开发者可以查看摘要、定制格式,甚至通过 Extension 替换压缩逻辑。
OpenAI Responses API 则提供了专门的 /responses/compact 能力。它可以返回不可读的加密 compaction item,应用将压缩后的 output 继续传给下一次请求即可。官方建议把这种压缩项视为不透明状态,不要解析或依赖其内部结构。
两者可以概括为:
| 方面 | Pi | OpenAI Responses/Codex 类机制 |
|---|---|---|
| 压缩结果 | 可读的结构化摘要 | 可以是不透明的加密状态 |
| 存储方式 | Session 中追加压缩记录 | 返回压缩后的 output items |
| 可定制性 | 可通过 Extension 深度定制 | 主要通过 API 管理压缩流程 |
| 共同目标 | 减少上下文并延续任务 | 减少上下文并延续任务 |
需要注意的是,OpenAI 并未公开 Codex 产品内部所有压缩阈值、Session 格式和上下文重建细节,因此不能断言 Codex 与 Pi 使用完全相同的实现。
七、构建 Agent 时的实践建议
1. 让动态内容进入标准上下文链路
知识库结果应该通过工具结果或标准消息交给 Agent,不要在最终 Provider Payload 阶段偷偷追加大量文本,否则 Agent 很难正确估算上下文大小。
2. 限制工具结果长度
文件读取、日志和知识检索结果往往是上下文增长的主要来源。应该限制单次返回长度,只提供当前任务真正需要的部分。
3. 在任务里程碑后压缩
不要每轮都压缩。完成一个较大的开发阶段、研究阶段或工具密集阶段后再压缩,通常更容易生成稳定摘要。
4. 记录压缩与真实 Usage
生产环境可以记录:
1session_id 2turn_id 3provider / model 4tokens_before 5compaction_entry_id 6Provider 返回的实际 input tokens
这样可以判断系统是否经常提前压缩,或者总是在溢出后才被动补救。
5. 不要把上下文压缩当作用户记忆
上下文压缩服务于“继续当前任务”;长期记忆服务于“跨会话理解用户”。两者的数据来源、权限、生命周期和准确性要求完全不同。
八、结语
上下文压缩并没有让模型获得无限记忆。它只是让 Agent 在有限窗口内持续整理工作材料:把旧历史归档为摘要,把近期细节留在桌面上,再将精简后的内容交给模型。
理解这一点后,很多现象都会变得清晰:
- Session 可以很长,但模型每轮看到的内容有限;
- 原始历史仍在,但模型主要依赖摘要;
- 压缩可以提前发生,也可以在溢出后补救;
- 压缩能够延续任务,却不能替代可靠的业务数据和长期记忆。
对于长期运行的 AI Agent,真正重要的并不是“保存了多少历史”,而是能否持续构造一份准确、紧凑且足以完成当前任务的工作上下文。
参考资料
cd ..