压缩使用与模型请求相同的 DAG 可见性、工具结果渲染和 aging 规则。顶层对话只定义可替换范围;每个范围的预算包含实际可见的子调用。最近完整用户轮次始终保留,默认保留最近两个用户轮次;token 预算不足时允许少于二十条顶层消息。短且已经低于保留预算的历史不调用摘要模型。
如果保留两个轮次后仍超过目标 token 预算,就改在下一个完整用户轮次边界切分,只保留最近一轮原文。这样已完成且工具输出很长的轮次不会使压缩失效。不得拆开正在执行的用户轮次;要压缩的工具证据必须进入摘要输入。完成事件同时报告压缩前后 token 数及是否真正应用摘要。
摘要输入从选定范围的第一条开始,包含原始用户要求、前次摘要和可见工具结果。摘要输入与 covers_ids 表达同一范围,不存在未提供给摘要模型却被移除的前缀。前次摘要只来自当前分支适用的记录,不使用其他分支的会话级缓存。
生成后先在内存图中执行相同的替换及渲染,只有 token 估算严格降低才写入。估算包含摘要标记和工具结果,压缩标记与上下文面板使用同一消息渲染。失败、取消、空摘要、无收益或生成期间内容变化均不提交摘要、不增加压缩次数。失败不会用丢弃内容的结构摘要替代。
存储仍使用一个滚动摘要与原有 covers_ids;不迁移或删除历史。重复压缩吸收当前适用摘要;分支隔离、HEAD 不变以及工具结果与调用配对继续由现有 DAG 渲染规则保证。历史错误摘要不自动重写。
参考来源继续使用本页已有对照资料;本次不引入新的外部实现或改变多级压缩的其他策略。
上述修正已实现,回归测试覆盖完整摘要输入、最近两轮保留、无收益拒绝、取消与并发变更、再次压缩以及统计一致性。会话图的压缩范围限定于同一份消息快照,避免加载期间再次压缩导致摘要状态不一致。
和 Claude Code 对齐,分两类:
常规操作(每轮 LLM 调用前都做,和上下文长度无关):Budget Reduction
级联压缩(按 Tier 1→2→3→4→5 顺序升级,成本低的先做,前一级不够才上后一级):
| 名称 | 做什么 | 触发条件 | 调 LLM | 破缓存 | 状态 | |
|---|---|---|---|---|---|---|
| 常规 | Budget Reduction | 截断超大单个输出 | 单个 tool_result > 4000 字符 | 否 | 否 | ✅ |
| Tier 1 | Microcompact | 旧工具输出清理 | 工具调用 ≥ 50 次(之后每 25 次)/ 空闲 90 分钟 | 否 | cache-aware 不破 | ✅ |
| Tier 2 | Snip | 删最旧几轮 | Tier 1 后仍超预算 | 否 | 是 | ✅ |
| Tier 3 | Context Collapse | 分段摘要 | Tier 2 后仍超,~90% | 是(每段一次) | 是 | ⏳ |
| Tier 4 | Auto-Compact | 全量摘要 | Tier 3 后仍超 / 用户 /compact | 是(一次) | 是 | ✅ |
| Tier 5 | Reactive | 紧急压缩 | API 返回 413 | 是 | 是 | ⏳ |
级联逻辑:每轮 LLM 调用前,从 Tier 1 开始检查。满足条件就执行,执行完重新检查是否还超。还超就进入下一个 Tier。
注:Tier 3 和 Tier 4 在 Claude Code 逆向分析中被描述为"overlapping strategies",不一定严格级联。/compact 手动命令直接触发 Tier 4,跳过 Tier 1-3。
Claude Code 是线性对话没有 DAG,不需要这一层。我们有函数调用节点(🟢),需要额外处理。
render_context 的 callers/subcalls/expose 控制和 Claude Code 一致。每轮 LLM 调用前都做,和上下文长度无关。
context/tool_aging/truncate.py: middle_truncate、context/tool_aging/policy.py: MAX_TOOL_RESULT_CHARS = 4000Budget Reduction 之后,按 Tier 顺序检查。前一级做完仍超阈值才上后一级。
和 Claude Code 一致。清理旧 🟡 工具输出。
cache_edits,beta header context-management-2025-06-27)服务端清除旧 tool_result。客户端消息不变,缓存不破。sentinel 做了字节级归一化(byte-stable),重复操作不改变已缓存内容context/microcompact.py、context/rules/microcompact.py和 Claude Code 一致。直接删最旧的几轮对话。不做摘要,不调 LLM。
context/snip.py和 Claude Code 一致。分段 LLM 摘要,原始消息保留(可回滚)。
和 Claude Code 一致。全量 LLM 摘要,替换所有旧消息。不可回滚。
/compact(直接跳到这里)/compact 或 /compact 保留数据库迁移的讨论。建议在 60% 时手动做context/engine.py: compact()API 返回 413(prompt too long)时的紧急压缩。每轮至多触发一次。都失败则终止。
每轮 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 → 紧急压缩 → 重试
→ 失败则终止
场景:200K context,持续工作几小时,中间调用了 research_agent。
🔵 user → 🔵 assistant,Budget Reduction 检查工具输出,没超的跳过。级联都不触发。
工具调用超过 50 次。Microcompact cache-aware 首次触发,清理旧工具输出,释放约 15K tokens。之后每 25 次再清一轮。Tier 2-5 不触发。
函数运行中:内部 🟡 工具调用展开。Budget Reduction 截断超大输出。函数结束后 expose=io 隐藏内部子树,节点走常规级联。
Microcompact 做了还是超。Snip 删最旧 5 轮(包括已完成的函数节点),降到阈值以下。Tier 3-5 不触发。
反复 Snip 后可删的不多了。Context Collapse 分段摘要。还不够则 Auto-Compact 全量摘要。很少发生。
压缩后正常对话。再次超阈值时重复级联。大模型想查旧的函数内部过程,可以通过工具读取 DAG 子节点。
| 我们 | Claude Code | 说明 |
|---|---|---|
| §2 函数节点生命周期 | 无 | 我们独有(DAG + expose=io) |
| §3 Budget Reduction | Budget Reduction | 一致(4000 字符阈值,60/40 截断) |
| Tier 1 Microcompact | Tier 1 Microcompact | 一致。Anthropic provider 用 cache-aware path |
| Tier 2 Snip | Tier 2 Snip | 一致(整轮删) |
| 第 3 层上下文折叠 | 第 3 层上下文折叠 | 一致(分段摘要,可回滚) |
| 第 4 层自动压缩 / /compact | 第 4 层自动压缩 / /compact | 一致(全量摘要) |
| Tier 5 Reactive | Tier 5 Reactive | 一致(413 紧急压缩) |
来源:Claude Code 压缩参考(基于逆向分析,非官方文档)
| 步骤 | 做什么 | 状态 | 代码 |
|---|---|---|---|
| 1 | Budget Reduction — 截断超大输出(§3) | ✅ | context/tool_aging/truncate.py |
| 2 | Tier 1 Microcompact — 旧工具输出清理 | ✅ | context/microcompact.py |
| 3 | Tier 2 Snip — 删最旧几轮 | ✅ | context/snip.py |
| 4 | Tier 3 Context Collapse — 分段 LLM 摘要 | ⏳ | |
| 5 | Tier 4 Auto-Compact — 全量 LLM 摘要 | ✅ | context/engine.py |
| 6 | Tier 5 Reactive — 413 紧急压缩 | ⏳ | |
| 7 | /compact 命令 | ⏳ | |
| 8 | /context 命令(token 分布) | ✅ | cli/repl/handlers.py |
| 9 | Anthropic Context Editing API 集成 | ⏳ |
聊天与函数内部共用 AgentLoop 的发送前处理:在转换消息、注入记忆、解析实际工具后计量请求。压缩仅替换当前请求视图中已完成工具组的结果文本,保留调用参数、结果 ID、错误状态、用户原文和多模态内容。原始执行记录不被改写。
摘要采用独立预算分块,调用次数与总时限有上限;摘要请求不再次进入 AgentLoop。缓存只属于本次 AgentContext,按原结果内容与模型配置匹配,不跨函数视图。优先处理较早结果,最新一次工具组保留;必要时也可处理已完成的最新组。每次发送都重新检查实际工具与系统内容。
未显式设置输出上限时,发送上限取模型能力、默认输出预留量与上下文四分之一的最小值;计量和 provider 请求使用同一个值。显式上限不被静默降低,保护内容与该上限合计超预算时仍拒绝发送。
本次范围:单轮工具累积的请求压缩;不替换既有跨轮 DAG 摘要机制,不承诺语义无损或本地估算等同 provider 计数。不可压缩的保护内容超预算时,不发送已知超预算请求。
实现状态:已接入共享 AgentLoop 请求入口,并通过独立规格与质量评审;单轮连续调用、函数内部入口、原文保留、摘要预算、取消及凭据传递均有回归测试。此处理不生成持久化 DAG 摘要节点。
本方案改进请求内压缩,保持 DAG 存储方式不变,供维护者审查请求预算和历史保留行为。
| 阶段 | 行为 | 边界 |
|---|---|---|
| 预算 | 每次请求计算系统提示、工具定义、响应 schema、输出预留和历史。 | 切换备用模型后重新计算。 |
| 工具证据 | 优先摘要已完成工具组的纯文本结果。 | 保留调用标识和错误标记。 |
| 请求检查点 | 仍超预算时,摘要较早的完整纯文本执行前缀,保留所有用户消息原文和最新助手执行片段。 | 不拆分工具组,不删除多模态、签名推理或未完成内容。 |
| 摘要请求 | 分段处理全部原文,以滚动摘要合并。服务端报告超限时缩小分段重试。 | 限制次数和时间;取消、错误、预算用尽均保留原始历史。 |
| 继续执行 | 仅在模型与原始前缀完全一致时复用检查点,仅发送验证过预算的请求。 | 原始执行消息和 DAG 节点不变。 |
参考 Codex 本地压缩保留用户消息并替换旧执行历史的行为。OpenProgram 将其适配到请求视图,不复制 Rust 源码、不截断用户指令、不接入远端加密压缩,也不替换 DAG。摘要始终作为助手数据,不作为系统指令。
审查已通过,完整验证未通过。定向测试、独立规格审查和质量审查均通过;四项仓库检查在修改前基线上同样失败。本功能尚未标记完成,主分支集成和安装版 App 验收仍待完成。