PiAgent 09|上下文压缩:对话超长后如何继续工作#
从触发判断、合法切点到摘要回接,理解 compaction 如何重建上下文而不是简单删除历史
压缩不是把聊天记录删掉一半。它是一种上下文重建操作:选择一个合法切点,把较早历史交给摘要过程,再将结构化摘要和保留区重新接回当前会话。压缩质量决定 Agent 在长任务中还能否记住目标、已经改过的文件和未完成的风险。
1. 为什么必须压缩#
模型上下文窗口是有限的,而 Agent 的历史会同时增长:用户消息、assistant 输出、工具调用、工具结果、重试和扩展消息都可能进入 transcript。每次把完整历史原样发送,会遇到:
- 上下文超限,请求直接失败。
- 历史占用过大,留给当前任务的输出空间不足。
- 旧工具结果和重复说明挤掉当前约束。
- 成本与延迟随轮次线性甚至超线性增长。
压缩的目标不是最短,而是让下一轮模型拥有足以继续工作的“工作记忆”。
2. 什么时候触发#
触发通常有两类:
- 自动压缩:接近模型上下文上限时,由 Session 根据窗口、输出预算和安全余量判断。
- 显式压缩:用户或宿主调用
session.compact(),或者扩展触发手动整理。
一个保守的预算模型是:
可用窗口 = 模型窗口 - 预留输出 - 预留工具结果 - 安全余量
触发条件 = 当前上下文估算值 > 可用窗口估算不必精确到最后一个 token,但必须稳定、可观测且倾向保守。压缩成功后仍要留出下一轮模型输出和工具结果的空间。
3. 切点不能破坏消息配对#
最危险的压缩方式是按字符串长度随意切历史。如果把 assistant tool call 与 tool result 拆开,重建后的上下文可能包含没有对应调用的结果,或者让模型误以为某个工具已经完成。
切点应尊重:
- user / assistant / tool result 的语义配对。
- turn 的边界。
- 当前分支的父子关系。
- 正在执行的工具和未收束的运行状态。
保留区开始
├─ 完整的 user message
├─ 完整的 assistant tool call
└─ 对应的 tool result当单个 turn 本身就很大时,不能假装它完整保留。摘要过程需要知道这个 turn 被截断了哪些部分,以及保留区从哪里开始。
4. 摘要应保存什么#
对编码 Agent,合格摘要至少包含:
- 当前目标、约束和验收标准。
- 已经做出的关键决策及原因。
- 创建、修改、删除过的文件和重要符号。
- 执行过的命令、测试结果和失败原因。
- 当前模型、工具或环境限制。
- 尚未完成的工作、待确认事项和潜在风险。
摘要不是面向人的会议纪要,而是给下一轮模型使用的状态压缩。像“已经处理了一些文件”这种句子没有恢复价值;文件路径、函数名和未解决问题才是可执行记忆。
5. 压缩后的上下文长什么样#
system prompt
+ compaction summary
- 任务目标
- 已完成修改
- 测试结果
- 未完成事项
- 风险与约束
+ 保留区中的最近消息
+ 当前用户输入摘要通常带有 tokensBefore、切点 ID 和可选的 usage/details。Session Manager 还要把压缩摘要作为会话历史中的一种 entry 保存,保证恢复和分支时知道这次重建发生过。
6. session_before_compact 是可控扩展点#
官方扩展 API 提供压缩前事件,扩展可以取消压缩或提供自定义摘要:
pi.on("session_before_compact", async (event, ctx) => {
if (shouldNotCompactYet(event)) {
return { cancel: true };
}
return {
compaction: {
summary: await buildDomainSummary(event),
firstKeptEntryId: chooseSafeCut(event),
tokensBefore: event.tokensBefore,
},
};
});自定义摘要适合补充业务状态,例如订单 ID、审批状态或外部任务编号,但不能用它绕过安全规则,也不能把未验证的外部数据当成事实写进记忆。
7. 失败、重试和可观测性#
压缩本身也可能失败:摘要请求超时、切点非法、模型窗口估算不一致或保存摘要失败。应该区分:
- 摘要生成失败:保留原历史或进入有限重试。
- 保存失败:不要宣称压缩成功,避免运行状态和持久化状态分叉。
- 空间不足:报告无法继续,而不是无限递归压缩。
建议记录 compaction_start/end、触发原因、压缩前后 token 估算、切点 ID、重试次数和最终状态。没有这些数据,就很难解释长任务为什么突然忘记了文件或变慢。
常见误区
- 只按字符数截断,不考虑 tool call/result 配对。
- 把摘要写成泛泛的“目前进展”,没有路径和决策。
- 压缩成功后不保存切点,恢复时无法重建同一上下文。
- 把所有旧日志都放进摘要,压缩后仍然占满窗口。
- 压缩失败时继续运行,导致模型看到不完整或不一致的状态。
小结
Compaction 的本质是一次有约束的上下文重建:先判断预算,再选择合法切点,摘要关键状态,保留最近有效历史,最后把结果写回会话。它连接了上下文工程、消息模型和会话树,不能被当成一个独立的“删文本工具”。
源码定位
- Session 压缩入口:
packages/coding-agent/src/agent-session.ts - 压缩事件与扩展 API:
packages/coding-agent/src/extensions/types.ts - 会话摘要 entry:
packages/coding-agent/src/core/session-manager.ts - Agent 上下文构建:
packages/agent/src/agent.ts