上下文压缩机制对比:PI / OpenClaw / Codex / WorkBuddy
最后修改时间:2026-10-10 21:43
一句话定义:上下文压缩是 harness /ˈhɑːnɪs/ (运行外壳)的"内存管理"——进上下文之前瘦身、组装时裁剪、超窗时摘要续命;四家实现细节不同,骨架高度一致。
关联阅读:压缩方法论与预算分配见 ../AI知识库/09-RAG与上下文/02-上下文工程.html;harness 是什么见 ../AI编程范式/AI辅助编程范式/09-harness-engineering.html;执行隔离层见 sandbox沙箱.html;Agent 侧的"选 / 压 / 层 / 淘"见 ../Agent开发知识/13-进阶与工程化/02-上下文工程.html。
1. 为什么压缩是 harness 的第一等问题
LLM /ˌel el ˈem/ ( Large Language Model /lɑːdʒ ˈlæŋɡwɪdʒ ˈmɒdl/ ,大语言模型)能力固定、窗口有限,而 Agent 的输入只增不减:每一轮工具调用都会往上下文里灌进命令日志、文件内容、 API /ˌeɪ piː ˈaɪ/ ( Application Programming Interface /ˌæplɪˈkeɪʃn ˈprəʊɡræmɪŋ ˈɪntəfeɪs/ ,应用编程接口)返回、搜索结果。 膨胀的来源不是对话,是工具结果。
所以压缩不是"聊天记录太长时删一点",它是 harness 四件事里的一件:
| harness 的职责 | 对应问题 | 压缩所处位置 |
|---|---|---|
| 工具层 | 模型能做什么 | 工具结果 在进上下文之前 就瘦身 |
| 执行环境 | 在哪做、权限多大 | 被压掉的内容落盘到工作区,仍可回查 |
| 上下文托管 | 看到什么 | 预算裁剪 + 按需加载 + 超窗摘要 |
| 循环编排 | 怎么迭代 | 每轮装配与回收上下文 |
一句话铁律,四家都遵守: 窗口是 RAM /ræm/ ( Random Access Memory ,随机存取存储器), 磁盘才是真相。 压缩只改变"模型下一轮看到什么",完整历史一律落盘——可恢复、可回放、可审计。
落地示例 :本文写作过程中抓取某开源仓库 README(113 KB)。工具层没有把 113 KB 塞进上下文,而是返回 截断视图 并提示 truncated output,原文留在临时文件;模型拿到的是可读片段 + 一个指针。这就是最廉价、收益最大的一层压缩。
2. 三层压缩框架
把四家做法抽象出来,压缩固定在三个层次上, 顺序就是从便宜到昂贵 :
flowchart LR
subgraph L1["① 工具层:进上下文之前"]
A1["截断 / 外部化<br/>只留摘要 + 指针"] --> A2["完整内容落盘"]
end
subgraph L2["② 组装层:每次调用前"]
B1["token 预算裁剪"] --> B2["按需加载 / 渐进披露"]
end
subgraph L3["③ 会话层:超窗时"]
C1["结构化摘要"] --> C2["保留近端原文"]
C2 --> C3["写入 compaction entry"]
end
L1 --> L2 --> L3
| 层 | 触发时机 | 典型手段 | 是否落盘 | 收益 |
|---|---|---|---|---|
| ① 工具层 | 工具返回的瞬间 | 截断、 offload /ˌɒfˈləʊd/ (外部化)、只留指针 | ✅ 落盘到工作区 | 最大 |
| ② 组装层 | 每次调用前 | 预算裁剪、技能按需加载、渐进披露 | ❌ 仅本轮视图 | 大 |
| ③ 会话层 | 超出窗口时 | compaction /kəmˈpækʃn/ (摘要压缩)+ 保留近端 | ✅ 写进 transcript | 中(续命) |
为什么顺序不能颠倒 :摘要是有损的(会丢路径、丢精确标识符、丢错误堆栈),而工具层压缩可以做到无损或可控损失。 能用①解决的,不要用③解决。
落地示例 :同一个"读完 200 个文件再回答"的任务:
| 做法 | 下一轮上下文 | 备注 |
|---|---|---|
| 全塞 | 200 × 3 000 token ≈ 60 万 token | 直接超窗,无法入框 |
| 只用③摘要 | 约 800 token | 结论留下,但文件路径 / 行号大概率丢 |
| ①+②(推荐) | 摘要 800 + 按需读的 5 个文件 4 000 ≈ 4 800 token | 需要细节再读,路径与行号都精确 |
3. 四家总览
| 维度 | PI | OpenClaw | Codex | WorkBuddy |
|---|---|---|---|---|
| 开源可验证 | ✅ 白盒 | ✅ 白盒 + 文档最细 | ❌ 行为可观测 | ❌ 闭源 |
| 摘要落盘 | ✅ 会话树 entry | ✅ transcript /ˈtrænskrɪpt/ entry | ✅ 原生线程 | ✅ JSONL /ˌdʒeɪsən ˈel/ ( JavaScript Object Notation Lines ,JSON 行格式) |
| 工具结果单独治理 | ✅ 截断 / 摘要 | ✅ pruning /ˈpruːnɪŋ/ 独立一层 | ✅ 截断 + 落盘指针 | ✅ tool-result swap |
| 切点与工具配对 | ✅ 强制配对 | ✅ 强制配对 | 未公开 | 未公开 |
| 保留近端策略 | keepRecentTokens |
默认 20 000 token | 未公开 | 未公开 |
| 溢出自恢复 | ✅ 压缩后重试 | ✅ 保留原请求、不重放 | ✅ 原生处理 | 未见公开 |
| 分支 / 会话树摘要 | ✅ branch_summary |
支持回退 / 分支 | — | — |
| 压缩器可插拔 | 模块化 | ✅ provider + context engine | ❌ | ❌ |
| 服务端压缩 | — | ✅ Anthropic 侧清除 | ✅ 原生(app-server) | — |
表中"未公开"表示没有任何一手来源能证实,本文不猜测。
4. PI:压缩是会话树里的一个 entry
PI(earendil-works/pi,官网 pi.dev)是开源编码 Agent harness;社区教学仓库 cellinlab/how-pi-agent-works 把它的压缩链路完整拆了出来。
4.1 机制
PI 把会话建模为 会话树(Session Tree) ,消息以只追加的 JSONL 落盘。压缩不是外部旁路,而是 往树里追加一个 compaction entry :
- 估算当前路径上的 token 用量,超阈值(或用户敲
/compact)触发; - 选 cut point /kʌt pɔɪnt/ (切点),把"旧范围"与"保留近端"分开;
- 用模型把旧范围压成 结构化摘要 ;
- 追加
CompactionEntry,记录summary+firstKeptEntryId+tokensBefore; - 后续组装上下文 = 摘要 +
firstKeptEntryId之后的原文 。
flowchart TB
A["完整历史消息"] --> B["估算 token"]
B --> C{"超过阈值?"}
C -->|"否"| D["正常请求"]
C -->|"是"| E["选切点 cut point"]
E --> F["结构化摘要旧消息"]
F --> G["追加 CompactionEntry"]
G --> H["上下文 = summary + 近端原文"]
4.2 四条关键不变式
| 不变式 | 内容 | 为什么必须 |
|---|---|---|
| 工具配对 | 切点若落在 assistant 工具调用与它的 toolResult 之间,边界要挪开 |
拆散配对会破坏协议,模型看到"孤儿工具结果" |
| split turn | 单个 turn 本身就超 keepRecentTokens 时,turn 前缀单独摘要、后缀保留原文 |
保住连续性,又不把巨大工具输出整块塞进下一轮 |
| 续算边界 | 下次压缩从上次 firstKeptEntryId 附近算起,而非从上一个 compaction entry 之后算起 |
否则"上次被保留的"永远进不了摘要,近端越滚越大 |
| branch summary /brɑːntʃ ˈsʌməri/ | 切分支时,把"离开的那条分支"的发现摘要带走 | 探索式编程里,走过的弯路本身就是资产 |
摘要内容同样是结构化的:官方实现包含 <read-files>、<modified-files> 这类 文件操作追踪 ,以及 details 字段—— details 给程序读,自然语言 summary 给模型读。
落地示例 :仓库里有 README.md 与 src/api.ts。
| 事件 | 记录 |
|---|---|
第 8 轮读了 README.md、改了 src/api.ts |
进 details.readFiles / details.modifiedFiles |
| 第 40 轮压缩 | 摘要写"已确认鉴权逻辑在 src/api.ts:118,待补测试" |
| 第 41 轮 | 模型直接读 src/api.ts:118,不再全仓搜索—— 省掉一次 20 次工具调用的探索 |
4.3 落地示例:最小 compaction 模块
主示例用 TS /ˌtiː ˈes/ ( TypeScript /ˈtaɪpskrɪpt/ )。下面这段把四条不变式都实现出来,summarize 换成真实模型调用即可上线:
/** 会话条目:与 PI / OpenClaw 的 transcript entry 同构 */
export type EntryType = "user" | "assistant" | "toolResult" | "compaction";
export interface Entry {
id: string;
type: EntryType;
text: string;
/** assistant 发起工具调用时的 callId;toolResult 用同一 callId 回指 */
callId?: string;
toolName?: string;
}
export interface CompactionDetails {
readFiles: string[];
modifiedFiles: string[];
}
export interface CompactionEntry extends Entry {
type: "compaction";
summary: string;
/** 保留原文的起始 entry id;下次压缩从这个边界附近续算 */
firstKeptEntryId: string | null;
tokensBefore: number;
details: CompactionDetails;
}
/**
* 粗略 token 估算:CJK 字符约 1 字 1 token,其余约 4 字符 1 token。
* OpenClaw 同样按 CJK 的近似权重计数;生产环境应改为读 provider 的 usage。
*/
export function estimateTokens(text: string): number {
let cjk = 0;
for (const ch of text) {
if (/[\u3000-\u9fff\uff00-\uffef]/.test(ch)) cjk += 1;
}
return Math.ceil(cjk + (text.length - cjk) / 4);
}
export const tokensOf = (entries: Entry[]): number =>
entries.reduce((sum, e) => sum + estimateTokens(e.text), 0);
/**
* 选切点(不变式 ①②):从尾部向前累加直到超过 keepRecentTokens;
* 若切点落在 toolResult 上,把它配对的 assistant 调用一起保留。
*/
export function pickCutPoint(entries: Entry[], keepRecentTokens: number): number {
let kept = 0;
let cut = entries.length;
for (let i = entries.length - 1; i >= 0; i--) {
kept += estimateTokens(entries[i].text);
if (kept > keepRecentTokens) break;
cut = i;
}
while (cut > 0 && entries[cut]?.type === "toolResult" && entries[cut - 1]?.type === "assistant") {
cut -= 1;
}
return cut;
}
/** 续算边界(不变式 ③):找不到 firstKeptEntryId 时,退回上一个 compaction 之后 */
export function resumePoint(entries: Entry[]): number {
const last = [...entries].reverse().find((e): e is CompactionEntry => e.type === "compaction");
if (!last) return 0;
const idx = entries.findIndex((e) => e.id === last.firstKeptEntryId);
return idx >= 0 ? idx : entries.indexOf(last) + 1;
}
export interface CompactionOptions {
/** 模型上下文窗口 */
contextWindow: number;
/** 预留:给输出与摘要本身留空间(OpenClaw 内嵌默认 20 000) */
reserveTokens: number;
/** 保留近端原文(OpenClaw 默认 20 000) */
keepRecentTokens: number;
summarize: (older: Entry[]) => Promise<{ summary: string; details: CompactionDetails }>;
}
/** 超窗才压缩;返回「摘要 entry + 保留的近端原文」 */
export async function compactIfNeeded(
entries: Entry[],
opts: CompactionOptions,
): Promise<Entry[]> {
const used = tokensOf(entries);
if (used + opts.reserveTokens <= opts.contextWindow) return entries;
const cut = pickCutPoint(entries, opts.keepRecentTokens);
const older = entries.slice(0, cut);
const tail = entries.slice(cut);
// 单个 turn 就超窗:真实实现走 split turn(前缀摘要 + 后缀保留)
if (older.length === 0) return entries;
const { summary, details } = await opts.summarize(older);
const entry: CompactionEntry = {
id: `cmp-${older[older.length - 1].id}`,
type: "compaction",
text: summary,
summary,
firstKeptEntryId: tail[0]?.id ?? null,
tokensBefore: used,
details,
};
return [entry, ...tail];
}
代入数字 (contextWindow = 128 000、reserveTokens = 20 000):
| 轮次 | 压缩前 token | 动作 | 压缩后 token |
|---|---|---|---|
| 第 40 轮 | 112 000 | 触发(112 000 + 20 000 > 128 000) | 摘要 900 + 近端 19 500 ≈ 20 400 |
| 第 90 轮 | 118 000 | 再次触发,从上次 firstKeptEntryId 续算 |
摘要 1 100 + 近端 19 800 ≈ 20 900 |
5. OpenClaw:compaction 与 pruning 分工最清楚
OpenClaw(github.com/openclaw/openclaw,文档 docs.openclaw.ai)是本文 公开资料最细 的一家,设计最值得直接照抄。
5.1 两套机制,职责不重叠
| Compaction(摘要) | Pruning(剪枝) | |
|---|---|---|
| 做什么 | 摘要旧对话 | 只剪 旧工具结果 |
| 是否落盘 | ✅ 写进 transcript(持久) | ❌ 仅请求期投影(不重写原文) |
| 范围 | 整个会话 | 仅 toolResult |
心法: 摘要治"对话",剪枝治"工具输出"。 两者互补——剪枝让工具输出在两个压缩周期之间保持精简。
5.2 compaction 的分层预算采样
内置压缩 一次模型请求 处理完旧历史,但单次请求输入被限制在 160 000 字符(约 40 000 token) ;超了按固定分层填充:
| 份额 | 内容 | 规则 |
|---|---|---|
| 1/2 | 最新消息 原文 | 保证近期细节不失真 |
| 1/4 | 较旧的 用户消息 | 承载诉求 / 决策 / 纠正;每条 ≤ 2 000 字符,均匀铺开 |
| 1/10 | 最旧消息 | 保留起始语境 |
| 其余 | 剩余消息 均匀取样 | 每段 ≤ 6 000 字符 |
每个空档都标注"漏了多少条",并明确指示摘要器 "不要猜测被省略的内容,且要继承上一版摘要里的事实" 。中 / 日 / 韩( CJK /ˌsiː dʒeɪ ˈkeɪ/ , Chinese Japanese Korean /ˌtʃaɪˈniːz ˌdʒæpəˈniːz kəˈriːən/ ,中日韩字符)按近似 token 权重计数。
为什么这样采样而不是简单截断 :用户消息是 纠正与决策 的载体("改成只看华东区""按同比口径"),所以单独给 1/4 份额;简单截断会把中途改过的口径丢掉。
5.3 safeguard 质量门:摘要不合格就不写
现在默认开启 safeguard /ˈseɪfɡɑːd/ (安全护栏)模式,摘要必须过质量审计:
- 生成的正文里必须有 规定的标题 ;
- 即将落盘的确切文本里必须保留 待办诉求 与 精确标识符 (文件名、路径、ID、数字);
- 不合格只允许有限次纠正重试; 全部不通过就不写 entry、保留原历史 ——而不是写入一段坏摘要。
5.4 pruning:软剪、硬清除、服务端清除
| 档位 | 触发条件 | 动作 |
|---|---|---|
| 软剪 | 单条工具结果 > 4 000 字符 | 保留首尾各 1 500 字符,中间替换为 ... |
| 硬清除 | 上下文用量 ≥ 约 50%,且可剪工具内容 ≥ 50 000 字符 | 整条替换为 [Old tool result content cleared] |
| 服务端清除 | Anthropic 直连(API Key) | 交给服务端 tool-result clearing,不再本地剪 |
服务端路径的参数是 推导出来 的,不需要额外配置:
| 参数 | 取值 |
|---|---|
trigger |
max(50 000, floor(窗口 × 0.3)) |
keep |
最近 3 次工具调用及其结果 |
clear_at_least |
max(12 500, floor(窗口 × 0.05)) |
clear_tool_inputs |
false(保留工具入参) |
两条容易忽略的细节:
- TTL /ˌtiː tiː ˈel/ ( Time To Live /taɪm tuː lɪv/ ,存活时间)门控:客户端剪枝要等缓存 TTL 过期才动手(默认 5 分钟,Anthropic 插件播种为 1 小时), 避免打碎 prompt cache /prɒmpt kæʃ/ (提示缓存)前缀——否则省下的输入 token 还不够付缓存重建的钱。
- 图片清理 :至少保留最近 3 个完整 turn 逐字节不变,之后每 8 个 turn 推进一次边界,旧图替换为已处理标记,防止图片负载反复打爆 prompt cache。
5.5 保留量与触发路径
| 配置 | 默认值 | 含义 |
|---|---|---|
keepRecentTokens |
20 000 | 保留近端原文的预算(手动 /compact 也用它) |
reserveTokens |
20 000(窗口 ≥ 80 000 时) | 给输出与压缩摘要预留;上限为窗口的 1/4 |
触发三条路:
- 阈值 :接近上限时自动压缩;
- 溢出恢复 :provider 报上下文超限 → 压缩后重试 ,保留原请求、不重放已完成的动作;
- 手动 :
/compact,可聚焦话题(如/compact Focus on <topic>)。
另外,压缩前还会做一次 memory flush /ˈmeməri flʌʃ/ (记忆刷新):用静默 turn 把要点写进长期记忆,避免摘要把关键约束丢掉。
落地示例 :数据分析类会话的调参取舍。
| 会话特点 | 调参方向 |
|---|---|
| 工具输出巨大(图表 JSON / 明细表) | 优先开 pruning 让它先剪,别指望摘要兜底 |
| 多轮反复改口径("只看华东区") | keepRecentTokens 调高,并在摘要指令里强制保留硬约束 |
| Anthropic 直连 | 走服务端清除,本地别再叠一层 |
| 本地小模型(窗口 32 k) | reserveTokens 被压到窗口 1/4,此时必须靠 pruning 而不是 compaction |
6. Codex:原生压缩归 app-server
6.1 机制
⚠️ 来源说明:本文写作时本机网络对
developers.openai.com、platform.openai.com一律返回403,官方文档抓取脚本同样被拒。因此本节以 产品实际行为 为准,并引用 OpenClaw 对其集成路径的观察作为交叉验证。
- 压缩归 app-server 所有 :OpenClaw 的 Codex harness 运行时文档明确写"token 压力压缩与手动线程压缩由 Codex app-server 原生负责",压缩完成会发出
contextCompaction完成项。 - 两条入口 :自动的 token 压力压缩,以及手动
/compact(集成方通过thread/compact/start请求,并等待对应完成项)。 - 原生线程是唯一真相 :集成方只能维护 只读镜像 ,不能改写 Codex 的原生线程记录。
- 集成方兜底 :当集成方自身镜像字节数超阈值时,会先做一次它的语义压缩;若不成功,则退化为"最近整 turn 的有界投影",并 明确提示用户重发关键细节 (被省略的历史无法再回到上下文)。
6.2 可观测的三层(本会话实证)
| 层 | 我在本次会话中观察到的现象 |
|---|---|
| ① 工具层 | 抓取 113 KB README 时,输出被标注 truncated output,我只拿到截断样本,原文在磁盘 |
| ② 组装层 | 注入的 skill 我只看到 名称与描述 (元数据),要用时才读 SKILL.md;AGENTS.md 是常驻指令 |
| ③ 会话层 | 上下文吃紧时把更早历史压成检查点,线程继续往下走, 且压缩内部细节不对外暴露 |
6.3 明确的不确定项
以下问题 没有一手来源 ,本文不下结论:切点如何与工具调用配对、摘要模板长什么样、keepRecent 具体阈值、是否支持分支摘要。
这条"不透明"本身与 OpenClaw 文档相互印证:它记录原生压缩的起止状态,但 不暴露人类可读的压缩摘要,也不暴露 Codex 保留了哪些条目 ——说明这些细节没有公开接口。
7. WorkBuddy:闭源,但"记忆外置"是主线
7.1 先说证据边界
WorkBuddy 属腾讯 CodeBuddy 家族, 闭源、无公开压缩规范 。因此本节只写两类可验证材料:
- 本仓库可观测配置 :
.workbuddy/memory/MEMORY.md+ 按日期笔记 +.workbuddy/rules/*.mdc(与.cursor/rules/、.codebuddy/rules/三端同步); - 社区 clean-room 教学仓库
adongwanai/learn-workbuddy(明确声明是教学复刻、非源码提取)。
不做机制臆测:凡是它内部怎么切分、摘要模板如何,本文一律留白。
7.2 公开可验证的三条主线
| 主线 | 内容 | 对应本文框架 |
|---|---|---|
| tool-result swap | 大输出外部化,history 只留摘要 + 指针 | ① 工具层 |
| append-only JSONL transcript | 会话事件只追加,可恢复可回放 | ③ 会话层的落盘形态 |
| 记忆分层 | workspace memory / user memory / remote profile | ② 组装层的长期输入 |
教学仓库把心法写成一句话: 上下文窗口是 RAM,JSONL、SQLite、记忆文件和 tool-results 是磁盘。 它还专门为"压缩不变量"和"压缩后能否 fresh-process 恢复"设计了评测用例——这一点比多数 harness 更工程化。
7.3 落地示例:文件式记忆注入
WorkBuddy 的规则 / 记忆是 磁盘文件 ,所以注入时同样是"索引 + 预算",而不是全文搬运:
export interface MemorySource {
path: string;
/** 命中相关性 0~1,由检索或规则匹配给出 */
score: number;
/** 单文件注入上限,防单文件吃光预算 */
maxChars: number;
}
export interface MemorySlot {
path: string;
text: string;
truncated: boolean;
}
/**
* 记忆注入:按分数排序 + 逐条限长 + 总预算封顶。
* 全文始终留在磁盘,模型只看到"命中片段 + 路径指针"。
*/
export function buildMemoryContext(
sources: MemorySource[],
read: (path: string) => string,
budgetChars = 12_000,
): { slots: MemorySlot[]; usedChars: number; omitted: string[] } {
const slots: MemorySlot[] = [];
const omitted: string[] = [];
let used = 0;
for (const src of [...sources].sort((a, b) => b.score - a.score)) {
const full = read(src.path);
const cap = Math.min(src.maxChars, budgetChars - used);
if (cap <= 0) {
omitted.push(src.path);
continue;
}
const text = full.length > cap ? full.slice(0, cap) : full;
slots.push({ path: src.path, text, truncated: text.length < full.length });
used += text.length;
}
return { slots, usedChars: used, omitted };
}
代入数字 :3 条规则文件 + 1 份 MEMORY.md,单文件上限 4 000 字符、总预算 12 000 字符。命中 4 条时实际注入 ≈ 11 400 字符(约 3 000 token), 超预算的第 5 条只留路径 由模型按需读——和 OpenClaw 的"分层预算"是同一个思路。
8. 三条可复用结论
- 先做工具层,再谈摘要。 四家都在"进上下文之前"动手(截断 / 外部化 / 只留指针),摘要只是兜底。你的大数据(图表 JSON、明细表)就应该走这条。
- 摘要必须结构化 + 保标识符 + 可校验。 OpenClaw 的 safeguard 模式(必留标题 / 待办 / 精确标识符 + 质量审计 + 不合格不写)是现成的模板。
- 压缩要能"续算"。 PI 的
firstKeptEntryId规则解决一个隐藏坑:被保留的原文下一轮该有机会重新进摘要,否则近端越滚越大。
9. 自建 harness 落地清单
| 周 | 目标 | 关键动作 | 验收指标 |
|---|---|---|---|
| W1 | 工具层 | 工具输出截断 + 完整内容落盘 + 返回指针;补 token 埋点 | 单条工具结果入上下文 ≤ 2 000 token |
| W2 | 组装层 | 预算分配 + 技能只注入索引 + 剪枝(TTL 门控) | prompt cache 命中率 > 60% |
| W3 | 会话层 | compaction entry + 工具配对 + 结构化摘要(含 details) |
压缩后上下文 < 窗口 25%,路径/ID 不丢 |
| W4 | 回归 | 黄金集回归 + 溢出恢复演练 + 续算验证 | 结论准确率 ≥ 90%,溢出 0 失败 |
工具栈速查:
| 环节 | 选型 |
|---|---|
| 运行时 | Node.js 20 + TypeScript 5 |
| 落盘 | JSONL(transcript)+ SQLite(会话索引) |
| token 计数 | gpt-tokenizer 估算 + provider usage 校准 |
| 观测 | OpenTelemetry + Grafana(按层打 token 与延迟标签) |
| 回归 | 50 ~ 100 条黄金集 + 压缩前后结论对比 |
10. 常见坑
| 坑 | 后果 | 对策 |
|---|---|---|
| 只做摘要,不做工具层压缩 | 摘要刚压完,下一轮工具输出又把窗口塞满 | pruning 与 compaction 同时上,见 5.1 |
| 切点切在工具调用与结果之间 | 出现"孤儿工具结果",协议错乱 | 强制配对(4.2 不变式①) |
| 压缩后从"上一个 compaction 之后"续算 | 近端越滚越大,压缩越来越无效 | 从 firstKeptEntryId 续算 |
| 摘要里丢掉精确标识符 | 模型凭空编造路径 / 数字 | 审计 + 强制保留清单(5.3) |
| 每轮都摘要 | 成本与延迟随轮次上涨 | 只在超阈值 / 溢出 / 手动时触发 |
| 忽略 prompt cache 前缀 | 剪枝省下的 token 不够付缓存重建 | TTL 门控 + 保持前缀字节稳定 |
| 压缩后不回归 | 悄悄变笨,用户先发现 | 黄金集 + 灰度 |
11. 学习要点
- 压缩分三层: 工具层(最赚)→ 组装层 → 会话层(兜底) ,顺序不能颠倒。
- 铁律: 窗口是 RAM,磁盘是真相 ;压缩只改"下一轮看到什么"。
- 两条必须写进代码的不变式: 工具调用与结果配对 、 摘要续算边界 。
- 摘要要结构化并保留精确标识符;不合格宁可不写。
- 三家开源实现给出的默认值(
keepRecentTokens20 000、reserveTokens20 000、单次摘要输入 ≤ 40 000 token)可以直接当起点。
12. 资料来源与可信度
| 对象 | 来源 | 可信度 |
|---|---|---|
| PI | 官方仓库 earendil-works/pi + 教学仓库 cellinlab/how-pi-agent-works(docs/concepts/context.md、docs/source/advanced-compaction.md、docs/source/session-compaction.md) |
高(白盒 + 二手教学) |
| OpenClaw | 官方文档 docs.openclaw.ai:concepts/compaction、concepts/session-pruning、reference/session-management-compaction/compaction、plugins/codex-harness-runtime/compaction;仓库 github.com/openclaw/openclaw |
高(一手官方文档) |
| Codex | 产品实际行为 + OpenClaw 集成文档的观察 | 中(官方文档返回 403,未取到一手页面) |
| WorkBuddy | 本仓库 .workbuddy/ 可观测配置 + 社区 clean-room 教学仓库 adongwanai/learn-workbuddy |
低-中(闭源,无官方规范) |
| 通用方法论 | ../AI知识库/09-RAG与上下文/02-上下文工程.html、../Agent开发知识/13-进阶与工程化/02-上下文工程.html | 高(本仓库已整理) |
13. 本文缩写
| 缩写 | 音标 | 全拼 | 中文 |
|---|---|---|---|
| LLM | /ˌel el ˈem/ | Large Language Model | 大语言模型 |
| API | /ˌeɪ piː ˈaɪ/ | Application Programming Interface | 应用编程接口 |
| RAM | /ræm/ | Random Access Memory | 随机存取存储器 |
| TTL | /ˌtiː tiː ˈel/ | Time To Live | 存活时间 |
| CJK | /ˌsiː dʒeɪ ˈkeɪ/ | Chinese Japanese Korean | 中日韩字符 |
| JSONL | /ˌdʒeɪsən ˈel/ | JavaScript Object Notation Lines | JSON 行格式 |
| TS | /ˌtiː ˈes/ | TypeScript | 类型化 JavaScript 超集 |
评论
评论加载中…