压缩图解

Budget Reduction + Microcompact + Snip 已实现 Context Collapse / Reactive 待实现
代码: context/engine.py、context/budget.py、context/microcompact.py、context/tool_aging/、context/snip.py
参考: Claude Code 压缩参考(5-tier 级联,基于逆向分析)

滚动摘要的当前约束

压缩使用与模型请求相同的 DAG 可见性、工具结果渲染和 aging 规则。顶层对话只定义可替换范围;每个范围的预算包含实际可见的子调用。最近完整用户轮次始终保留,默认保留最近两个用户轮次;token 预算不足时允许少于二十条顶层消息。短且已经低于保留预算的历史不调用摘要模型。

如果保留两个轮次后仍超过目标 token 预算,就改在下一个完整用户轮次边界切分,只保留最近一轮原文。这样已完成且工具输出很长的轮次不会使压缩失效。不得拆开正在执行的用户轮次;要压缩的工具证据必须进入摘要输入。完成事件同时报告压缩前后 token 数及是否真正应用摘要。

摘要输入从选定范围的第一条开始,包含原始用户要求、前次摘要和可见工具结果。摘要输入与 covers_ids 表达同一范围,不存在未提供给摘要模型却被移除的前缀。前次摘要只来自当前分支适用的记录,不使用其他分支的会话级缓存。

生成后先在内存图中执行相同的替换及渲染,只有 token 估算严格降低才写入。估算包含摘要标记和工具结果,压缩标记与上下文面板使用同一消息渲染。失败、取消、空摘要、无收益或生成期间内容变化均不提交摘要、不增加压缩次数。失败不会用丢弃内容的结构摘要替代。

存储仍使用一个滚动摘要与原有 covers_ids;不迁移或删除历史。重复压缩吸收当前适用摘要;分支隔离、HEAD 不变以及工具结果与调用配对继续由现有 DAG 渲染规则保证。历史错误摘要不自动重写。

验收与边界

参考来源继续使用本页已有对照资料;本次不引入新的外部实现或改变多级压缩的其他策略。

实现状态

上述修正已实现,回归测试覆盖完整摘要输入、最近两轮保留、无收益拒绝、取消与并发变更、再次压缩以及统计一致性。会话图的压缩范围限定于同一份消息快照,避免加载期间再次压缩导致摘要状态不一致。

节点类型 🔵 对话节点 🟢 函数节点 🟡 工具节点
1. 总览:常规操作 + 5 级级联

和 Claude Code 对齐,分两类:

常规操作(每轮 LLM 调用前都做,和上下文长度无关):Budget Reduction

级联压缩(按 Tier 1→2→3→4→5 顺序升级,成本低的先做,前一级不够才上后一级):

名称做什么触发条件调 LLM破缓存状态
常规Budget Reduction截断超大单个输出单个 tool_result > 4000 字符否否✅
Tier 1Microcompact旧工具输出清理工具调用 ≥ 50 次(之后每 25 次)/ 空闲 90 分钟否cache-aware 不破✅
Tier 2Snip删最旧几轮Tier 1 后仍超预算否是✅
Tier 3Context Collapse分段摘要Tier 2 后仍超,~90%是(每段一次)是⏳
Tier 4Auto-Compact全量摘要Tier 3 后仍超 / 用户 /compact是(一次)是✅
Tier 5Reactive紧急压缩API 返回 413是是⏳

级联逻辑:每轮 LLM 调用前,从 Tier 1 开始检查。满足条件就执行,执行完重新检查是否还超。还超就进入下一个 Tier。

注:Tier 3 和 Tier 4 在 Claude Code 逆向分析中被描述为"overlapping strategies",不一定严格级联。/compact 手动命令直接触发 Tier 4,跳过 Tier 1-3。

2. 我们独有:函数调用节点的生命周期

Claude Code 是线性对话没有 DAG,不需要这一层。我们有函数调用节点(🟢),需要额外处理。

2.1 函数运行中

触发
函数被调用,status="running"
做什么
内部子树完整展开在上下文中
可见范围
由 render_context 的 callers/subcalls/expose 控制
节点状态
内部视角:子节点 full。外部视角:默认 expose=io,调用方只看到输入+输出
和压缩的关系
函数运行中,内部的 🟡 工具输出照常走 Budget Reduction(§3)和 Microcompact(Tier 1)。不参与 Snip(不能删正在用的)

2.2 函数结束 → expose=io,走常规级联

触发
函数返回,status="completed"/"error"
做什么
expose=io 天然隐藏内部子树。不需要额外操作。子节点保留在 DAG 中(可查)
后续
函数节点(输入+输出)进入 §3-§4 的常规级联,和 🔵 对话节点 一视同仁。不需要单独的策略
3. 常规操作:Budget Reduction ✅ 已实现

和 Claude Code 一致。每轮 LLM 调用前都做,和上下文长度无关。

做什么
截断超大的单个 🟡 工具输出。不处理 🔵 user/assistant 消息
阈值
4000 字符(约 1000 tokens)
截断方式
保留前 2400 字符(60%)+ 后 1600 字符(40%),中间丢弃
代码
context/tool_aging/truncate.py: middle_truncate、context/tool_aging/policy.py: MAX_TOOL_RESULT_CHARS = 4000
4. 级联压缩(Tier 1→5)

Budget Reduction 之后,按 Tier 顺序检查。前一级做完仍超阈值才上后一级。

第 1 层:微压缩 ✅ 已实现

和 Claude Code 一致。清理旧 🟡 工具输出。

触发
Cache-aware: 50 次工具调用后首次,之后每 25 次。Time-based: 空闲 ~90 分钟
只处理
FileRead、Shell、Grep、Glob、WebSearch、WebFetch、FileEdit、FileWrite
两条路径
Cache-aware(Anthropic provider):通过 Context Editing API(cache_edits,beta header context-management-2025-06-27)服务端清除旧 tool_result。客户端消息不变,缓存不破。sentinel 做了字节级归一化(byte-stable),重复操作不改变已缓存内容
Time-based(其他 provider / 空闲 90 分钟):客户端替换为占位文本(~20 tokens)。会破缓存
压缩效果
每个工具输出:原始大小 → 0 tokens(cache-aware)或 ~20 tokens(time-based)
代码
context/microcompact.py、context/rules/microcompact.py

第 2 层:截减 ✅ 已实现

和 Claude Code 一致。直接删最旧的几轮对话。不做摘要,不调 LLM。

触发
Tier 1 做完后仍超预算
删除粒度
整轮(user + assistant + 该轮所有工具调用一起删)
删多少
从最旧的开始逐轮删,每删一轮重新算 token 数,删到阈值以下为止(不是固定 N 轮)
代价
信息完全丢失,缓存前缀被破坏。但免费(不调 LLM)
函数节点
已完成的 🟢 函数节点按时间参与 Snip。运行中的不删
代码
context/snip.py

第 3 层:上下文折叠 ⏳ 待实现

和 Claude Code 一致。分段 LLM 摘要,原始消息保留(可回滚)。

触发
Tier 2 Snip 后仍超(约 90%,95% 阻塞强制)
分段
5-10 轮一段,每段独立 LLM 摘要(每段约 100-300 tokens)
LLM 调用
次数 = 段数(例:30 轮分 3 段 = 3 次 LLM 调用)
可回滚
原始消息保留在 collapse store 中(读时投影 / read-time projection)

第 4 层:自动压缩 ✅ 已实现

和 Claude Code 一致。全量 LLM 摘要,替换所有旧消息。不可回滚。

触发
Tier 3 后仍超。或用户手动 /compact(直接跳到这里)
LLM 调用
1 次(全量摘要)
摘要大小
约 2000-5000 tokens
保留
最近 1-2 轮完整。system prompt 从配置文件重新加载(不依赖摘要)
/compact 命令
/compact 或 /compact 保留数据库迁移的讨论。建议在 60% 时手动做
链式压缩
压缩后继续聊,再次满了在上一次摘要基础上再压。信息逐层衰减
代码
context/engine.py: compact()

第 5 层:响应式压缩 ⏳ 待实现

API 返回 413(prompt too long)时的紧急压缩。每轮至多触发一次。都失败则终止。

5. 执行流程
每轮 LLM 调用前:

1. Budget Reduction → 截断超大工具输出(每轮都做)

2. 级联检查(按 Tier 顺序):
   Tier 1: Microcompact 条件满足?→ 清旧工具输出
   Tier 2: 仍超阈值?→ Snip 删旧轮
   Tier 3: 仍超阈值?→ Context Collapse 分段摘要
   Tier 4: 仍超阈值?→ Auto-Compact 全量摘要

3. 调 LLM

4. 如果 API 返回 413:
   Tier 5: Reactive → 紧急压缩 → 重试
   → 失败则终止
6. 完整运行示例

场景:200K context,持续工作几小时,中间调用了 research_agent。

阶段 1(0-30%):正常对话

🔵 user → 🔵 assistant,Budget Reduction 检查工具输出,没超的跳过。级联都不触发。

阶段 2(30-50%):Tier 1 Microcompact 开始工作

工具调用超过 50 次。Microcompact cache-aware 首次触发,清理旧工具输出,释放约 15K tokens。之后每 25 次再清一轮。Tier 2-5 不触发。

阶段 3:调用 🟢 research_agent

函数运行中:内部 🟡 工具调用展开。Budget Reduction 截断超大输出。函数结束后 expose=io 隐藏内部子树,节点走常规级联。

阶段 4(超阈值):Tier 2 Snip 触发

Microcompact 做了还是超。Snip 删最旧 5 轮(包括已完成的函数节点),降到阈值以下。Tier 3-5 不触发。

阶段 5(极端):Tier 3/4 触发

反复 Snip 后可删的不多了。Context Collapse 分段摘要。还不够则 Auto-Compact 全量摘要。很少发生。

阶段 6:继续使用

压缩后正常对话。再次超阈值时重复级联。大模型想查旧的函数内部过程,可以通过工具读取 DAG 子节点。

7. 和 Claude Code 的对应
我们Claude Code说明
§2 函数节点生命周期无我们独有(DAG + expose=io)
§3 Budget ReductionBudget Reduction一致(4000 字符阈值,60/40 截断)
Tier 1 MicrocompactTier 1 Microcompact一致。Anthropic provider 用 cache-aware path
Tier 2 SnipTier 2 Snip一致(整轮删)
第 3 层上下文折叠第 3 层上下文折叠一致(分段摘要,可回滚)
第 4 层自动压缩 / /compact第 4 层自动压缩 / /compact一致(全量摘要)
Tier 5 ReactiveTier 5 Reactive一致(413 紧急压缩)

来源:Claude Code 压缩参考(基于逆向分析,非官方文档)

8. 实施计划
步骤做什么状态代码
1Budget Reduction — 截断超大输出(§3)✅context/tool_aging/truncate.py
2Tier 1 Microcompact — 旧工具输出清理✅context/microcompact.py
3Tier 2 Snip — 删最旧几轮✅context/snip.py
4Tier 3 Context Collapse — 分段 LLM 摘要⏳
5Tier 4 Auto-Compact — 全量 LLM 摘要✅context/engine.py
6Tier 5 Reactive — 413 紧急压缩⏳
7/compact 命令⏳
8/context 命令(token 分布)✅cli/repl/handlers.py
9Anthropic Context Editing API 集成⏳

同一消息内的请求压缩

聊天与函数内部共用 AgentLoop 的发送前处理:在转换消息、注入记忆、解析实际工具后计量请求。压缩仅替换当前请求视图中已完成工具组的结果文本,保留调用参数、结果 ID、错误状态、用户原文和多模态内容。原始执行记录不被改写。

摘要采用独立预算分块,调用次数与总时限有上限;摘要请求不再次进入 AgentLoop。缓存只属于本次 AgentContext,按原结果内容与模型配置匹配,不跨函数视图。优先处理较早结果,最新一次工具组保留;必要时也可处理已完成的最新组。每次发送都重新检查实际工具与系统内容。

未显式设置输出上限时,发送上限取模型能力、默认输出预留量与上下文四分之一的最小值;计量和 provider 请求使用同一个值。显式上限不被静默降低,保护内容与该上限合计超预算时仍拒绝发送。

本次范围:单轮工具累积的请求压缩;不替换既有跨轮 DAG 摘要机制,不承诺语义无损或本地估算等同 provider 计数。不可压缩的保护内容超预算时,不发送已知超预算请求。

实现状态:已接入共享 AgentLoop 请求入口,并通过独立规格与质量评审;单轮连续调用、函数内部入口、原文保留、摘要预算、取消及凭据传递均有回归测试。此处理不生成持久化 DAG 摘要节点。

请求检查点与有预算限制的摘要

本方案改进请求内压缩,保持 DAG 存储方式不变,供维护者审查请求预算和历史保留行为。

阶段行为边界
预算每次请求计算系统提示、工具定义、响应 schema、输出预留和历史。切换备用模型后重新计算。
工具证据优先摘要已完成工具组的纯文本结果。保留调用标识和错误标记。
请求检查点仍超预算时,摘要较早的完整纯文本执行前缀,保留所有用户消息原文和最新助手执行片段。不拆分工具组,不删除多模态、签名推理或未完成内容。
摘要请求分段处理全部原文,以滚动摘要合并。服务端报告超限时缩小分段重试。限制次数和时间;取消、错误、预算用尽均保留原始历史。
继续执行仅在模型与原始前缀完全一致时复用检查点,仅发送验证过预算的请求。原始执行消息和 DAG 节点不变。

参考 Codex 本地压缩保留用户消息并替换旧执行历史的行为。OpenProgram 将其适配到请求视图,不复制 Rust 源码、不截断用户指令、不接入远端加密压缩,也不替换 DAG。摘要始终作为助手数据,不作为系统指令。

验收与实现状态

审查已通过,完整验证未通过。定向测试、独立规格审查和质量审查均通过;四项仓库检查在修改前基线上同样失败。本功能尚未标记完成,主分支集成和安装版 App 验收仍待完成。