Skip to content

feat(rollover): 批量 rollover 压缩 — Prompt Cache 稳定性主杠杆 (closes #241) - #243

Open
ranxianglei wants to merge 1 commit into
masterfrom
feat/rollover-cache-stability
Open

feat(rollover): 批量 rollover 压缩 — Prompt Cache 稳定性主杠杆 (closes #241)#243
ranxianglei wants to merge 1 commit into
masterfrom
feat/rollover-cache-stability

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

背景

#80 提出、#240 确认:压缩真实成本的大头不是写摘要的 output tokens,而是 Prompt Cache 失效——selective compression 在历史中间就地改写,把压缩点之后的整个后缀踢出缓存前缀,后续每轮按全价重算这段 input。

本 PR 实现 #241主杠杆:把频繁就地压缩变成偶发批量 rollover,让 model-visible history 在阶段内尽量 append-only。

方案(默认开启,"rollover": false 恢复旧行为)

  1. 阶段内 append-onlycompress 调用立即校验范围(坏范围仍当场报错、计入重试上限),但只把范围记录为 pending:原文保持可见,历史不改写。第 3 个 pending 调用起,kernel 的 KEEP_LAST_ORPHANED=2 会把最早的 pending 调用从历史中间隐藏——adapter 侧做了 restore 修复(按原始位置重新插入合成 tag 的消息,字节级与 kernel 渲染一致),保证前缀稳定。
  2. pending drop(absorb 工具) — 新 absorb({ref, summary}):把大型 tool output 蒸馏成模型自写的紧凑摘要;原文标记 pending drop、保持可见直到批量应用(复用 kernel 原生 applyAbsorb,纯函数校验)。
  3. 批量 rollover — 用量越过 rollover.threshold(默认 70%,刻意低于 75% 强制 nudge 带,保证 rollover 总在 nudge 升级前触发)时,一次性 applyCompression 全部 pending + 合并 absorb 记录:一次缓存失效摊薄整段。该轮追加一次性 ▣ ACP rollover | N compression(s) + M absorb(s) applied: X → Y tokens 报告(瞬态尾部,下轮消失)。
  4. 检索结果追加到尾部 — decompress / search_context 结果是历史末尾的 tool result,前缀永不触碰。

关键设计决策

  • 默认开启:这是主杠杆,所有用户默认受益;"rollover": false(或 {enabled: false})是逃生舱,恢复逐次就地压缩。
  • 零 kernel 改动:全部 adapter 侧(pending 状态、延迟 apply、restore 修复、config、工具、命令、系统提示词段落)。acp-kernel 保持 pin 不变,无跨仓库发布顺序问题。
  • 持久化:pending 存 .acp.jsonrolloverPending 字段(跨重启生效);顺带修复 mergeInitialState 丢弃 absorbed 字段的 bug(rollover 应用后重启会丢 absorb 记录)。
  • 可观测acp_status 新增 Rollover: N pending ... (threshold 70%, current X%) 行;/acp-rollover 命令强制立即批量。

验收标准

  • 阶段内 model-visible history append-only(不就地改中间)
  • pending drop 机制:可删内容标记 + 延迟到 rollover 移除
  • 批量 rollover:压力高(≥70%)/ 手动(/acp-rollover)时一次性压缩 + 删除
  • decompress / search 结果追加到尾部
  • 单元测试:验证缓存前缀稳定性(tests/rollover.test.ts 5 个:阶段内字节稳定含 ≥3 pending、阈值触发恰好一次改写后重新锚定、absorb 原文可见直到批量、检索结果纯尾部追加、手动 /acp-rollover)
  • 文档更新(README + README.zh-CN + CONFIGURATION + CONFIGURATION.zh-CN + CHANGELOG)

验证

  • npm run typecheck
  • npm test → 433/433 ✅(428 基线 + 5 新增;既有 60 处 createAcpExtension 调用显式 rollover: false 保留旧行为覆盖,prefix-stab 回归测试同处理)
  • npm run build ✅(dist/index.js 544 KB,acp-kernel 内联)

@github-actions

Copy link
Copy Markdown

📦 Built Extension Artifact

Branch: feat/rollover-cache-stability (9832e70)

Option A — Install from npm PR tag (recommended)

pi install npm:billion-context-pi@pr-243

Each push to this PR publishes a new version under the pr-243 npm tag.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pi-pr243.tgz
pi install ./package

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] 收到,开始审查 feat/rollover-cache-stability(closes #241)。计划:

  1. 审阅完整 diff,核对 issue 声称的设计(pending 延迟 apply、restore 修复、absorb 工具、70% 阈值、持久化、/acp-rollover
  2. 本地跑 npm run typecheck / npm test / npm run build 验证 433/433 等声明
  3. 重点验证缓存前缀稳定性测试与「零 kernel 改动」声明(acp-kernel pin 是否未变)

有结论后在此回帖。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] 审查完成,结论:通过,验收标准全部独立复验

验证结果(本地 checkout feat/rollover-cache-stability @ 9832e70):

  • npm run typecheck
  • npm test433/433 ✅(与声明一致)
  • npm run build → dist/index.js 544.36 KB ✅(acp-kernel 内联)
  • 零 kernel 改动 ✅:acp-kernel 保持 pin 0.0.46,package.json / package-lock.json 零 diff,无跨仓库发布顺序问题

关键设计点核实

  1. 机制成立:kernel 的 hideConsumedCompressCallsKEEP_LAST_ORPHANED=2,acp-kernel dist:658-710)确认第 3 个 pending compress 调用会把第 1 个从历史中间隐藏——正是破坏缓存前缀的就地改写点;restore 修复(findHiddenPendingCompressCalls + mergeRestoredMessages,src/rollover.ts:196-248)按原始位置重插。
  2. 变异验证:临时禁用 mergeRestoredMessages 重跑 tests/rollover.test.ts → 字节稳定测试如期失败(4/5 过),恢复后 5/5。测试真实覆盖修复,非恒真。
  3. 既有测试 66 处 createAcpExtension 调用全部显式声明 rollover(61 处 rollover: false 保留旧行为 + 5 处新测试 rollover: true/{threshold: 0.6})。issue 写「60 处」实为 61,计数小出入,不影响结论。
  4. absorb 复用 kernel applyAbsorb 作纯函数校验:只取 AbsorbRecord,丢弃其 state 变更(save 的是 turn.state),原文保持可见直到批量——与描述一致;同 ref 重复 absorb 有去重。
  5. 持久化:.acp.jsonrolloverPending 字段带 parse 校验;mergeInitialState 丢弃 absorbed 的 bug 已修(parseAbsorbedRecords,src/state.ts:203-215)。
  6. 配置语义:rollover: false 布尔简写 / {enabled, threshold} 对象;threshold 接受 0.7"70%";默认 0.70 < 0.75 强制 nudge 带 ✅。absorb 工具在 reloadConfig 之后按最终配置注册,user-config 的 rollover: false 能正确赢过工厂默认值。
  7. 文档:README / README.zh-CN / CONFIGURATION / CONFIGURATION.zh-CN / CHANGELOG 均已更新,含 trade-off 说明。

小问题(非阻塞)

  1. AcpRuntime.setRolloverPending 接口声明返回 void、注释称 "Synchronous on purpose"(src/runtime.ts:96-98),但实现是 async function 返回 Promise<void>(src/runtime.ts:408-411)。因 store.setRolloverPending 本身同步且调用方均不 await,实际原子性成立,但 async 包装有误导性,建议改成普通函数。
  2. runRollover 中批量 apply 若部分 range 报错,整个 pending(含成功项)被清空、失败 range 不会重新 pending,错误只出现在一次性尾部报告。可接受(模型可见错误、可重新 compress),但属于值得知晓的设计选择。

按仓库约定 PR merge 由人工执行,我不代操作。

@ranxianglei ranxianglei mentioned this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant