Skip to content

fix: protected-zone rejection carries acp_status guidance; leanPack ref-stability wording (#385) - #387

Open
ranxianglei wants to merge 2 commits into
masterfrom
2026-09-24_compress-zone-error-guidance
Open

ranxianglei wants to merge 2 commits into
masterfrom
2026-09-24_compress-zone-error-guidance

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #385

Changes

1. Protected-zone whole-range rejection now carries convergent guidance (src/compress.ts, single-range apply path)

Old rejection tail:

Adjust startId/endId to older messages.

New tail:

Adjust startId/endId to older messages — run acp_status and target one of the CURRENT compressible ranges it reports.

The four sibling stale-state rejections in the same module already carry this kind of convergent guidance ("Run acp_status, then call the compress tool again using only the refs it reports" / "CURRENT compressible ranges it reports"); the protected-zone path was the lone inconsistency. In production (ranxianglei/billion-context#1262) the bare message produced 3× identical-retry loops (observed 11× across 10 sessions in one day): the model proposes the last N tags it can see, gets "adjust to older messages", retries the same range, burns turns. Convergence previously depended on the model independently discovering acp_status.

2. leanPack (pi adapter) ref wording matches actual assignRefs semantics (src/packs.ts) — the related minor observation in #385, same root cause (model-facing strings lagging the ref-semantics redesign).

Old: "Refs may be renumbered after compression."
New: "Refs are stable per message content and never reissued within a session; numbering restarts from m00001 only after native compaction or an upstream/model switch."

Verified against src/refs.ts: assignRefs keeps existing byRaw refs and allocates monotonically from nextIndex — refs are never reissued within a session. Numbering only restarts across generation switches / native-compaction resets, which already have dedicated error text.

Behavior changes (disclosure)

  • Whole-range-in-zone rejection string: tail appended (old → new above). Model-facing error text only; same throw site, same error shape, no programmatic-semantics change.
  • leanPack acpTags prompt line (pi adapter): "may be renumbered after compression" → stability + restart-from-m00001 semantics. Model-facing prompt text only. Why: the old line taught models to expect per-compress renumbering that no longer happens, contradicting both the kernel's ref-immutability invariant and the dedicated reset-path error texts.

Tests

  • tests/protect-recent.test.ts: the existing whole-range-rejection test now additionally asserts the error contains the acp_status/current-ranges pointer.
  • tests/packs.test.ts: the acpTags test asserts the stability wording is present and the "may be renumbered" claim is gone.

Both assertions fail against pre-fix code (string-pinned regression), pass post-fix.

Verification (rebased onto current origin/master d3808d6, v0.0.86)

  • npm run typecheck: clean
  • npm test: 1032/1032 pass
  • npm run build: success
  • prettier: every line in this diff is conformant (format:check reports 119 pre-existing non-conformant files on master itself; this diff adds zero new violations)

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-09-24_compress-zone-error-guidance (a033a98)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-387

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

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf acp-kernel-pr387.tgz
npm install ./package

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

收到,正在按 PR Review 规范核对:① 原 issue #385 的方向、② 回归清点、③ 代码与测试有效性。另外注意到 master 已前进到 3616730(#386 agents-nudge-flat-design 合并),本 PR 目前落后一个 commit(mergeable_state=behind),会一并确认是否有冲突并处理。稍后给完整结论。

@ranxianglei
ranxianglei force-pushed the 2026-09-24_compress-zone-error-guidance branch from 29f409d to a033a98 Compare September 24, 2026 06:57
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

Full review done — verdict below.

① Original issue (#385) — direction check
Verified the premise end-to-end. The four sibling stale-state rejections (src/compress.ts:383 unknown refs, :386 unanchored, :387 already-compressed, :1123 livelock guard) all carry convergent "run acp_status / CURRENT compressible ranges" guidance; the protected-zone whole-range rejection was genuinely the lone inconsistency. The cited production failure mode (identical-retry loops when the model can't independently discover acp_status) is consistent with the code path, and the fix targets the root cause (model-facing string lagging the redesign), not a symptom. The leanPack wording change is also verified against src/refs.ts: MIN_INDEX = 1 → restart is from m00001; assignRefs keeps existing byRaw refs and allocates monotonically from nextIndex, so "never reissued within a session" is accurate, and numbering restarts only on generation switch / native compaction — exactly what the new line says.

② Regression review — existing behavior inventory & conclusions

  • compress.ts:1044 throw site: unchanged throw, unchanged error shape; only the tail string extended. Grep confirms Adjust startId/endId appears exactly once in src — no other code or test depends on the old exact string. Preserved.
  • Sibling rejection paths (:383/:386/:387/:1123): untouched by this diff. Preserved.
  • leanPack prompt: one line changed; all other pack prompts and all programmatic behavior untouched. No other "renumbered" model-facing strings remain anywhere in src after the diff. Preserved.
  • Both behavior changes are model-facing text only, and both are disclosed old→new with rationale in the PR body per policy. No unintended regression found.

③ Code review
Trivial, surgical string edits. New phrasing matches the :387 sibling verbatim for the key clause ("run acp_status and target one of the CURRENT compressible ranges"), em-dash style consistent with surrounding text. Nothing else touched.

④ Test validity
Both new assertions have teeth: tests/protect-recent.test.ts pins the pointer phrase (case-insensitive match on the exact convergent guidance); tests/packs.test.ts asserts presence of the stability wording AND absence of "may be renumbered" — so either a revert or a half-edit fails. These would catch regressions of exactly these two strings.

⑤ Diff cleanliness
4 files, +15/−2, every line relevant to #385. No unrelated churn, no lockfile drift.

Maintenance performed (disclosure): master moved while this PR sat open (d3808d6 → 3616730 via #386, docs-only AGENTS.md +13, zero file overlap). I rebased the branch onto current master (new commits 5b7373e, a033a98; identical 4-file diff), re-ran the full pre-flight locally (typecheck clean, 1032/1032 tests pass, build success), and force-pushed the feature branch with an explicit lease. CI on the new head is fully green (pr-validation, test 22, test 24, build-artifact) and the PR is now mergeable_state: clean. One stale detail in the PR body: it says "rebased onto d3808d6" — the branch is now actually based on 3616730.

Ready for human merge: #387


中文摘要:审核了 #385 的两个文案修复(protected-zone 拒绝信息补上 acp_status 收敛指引、leanPack ref 措辞对齐 assignRefs 实际语义),逐项验证了根因、既有行为无回归、测试有牙、diff 干净;顺手把分支 rebase 到了最新 master(3616730) 并确认 CI 全绿,可以合并。

@ranxianglei

Copy link
Copy Markdown
Owner Author

暂时搁置(维护者决定)。

理由:run acp_status 是迫不得已的兜底,不是修复。真正要回答的问题是:为什么会报这个错——模型按 nudge 列出的 compressible ranges 提交折叠,正常路径根本不应该撞上 protected-zone 拒绝。需要先调查拒绝的产生机制,例如:

  • 列出的 range 内部是否可能嵌着保护消息(模型完全照做也会被整范围拒绝)?
  • range 构建到模型提交之间,新到达的工具结果是否使 span 失效?
  • 单范围 apply 路径是整段拒绝而不是围绕保护对切分折叠——拒绝本身是否就是缺陷(sanctioned fold 被 reject)?

查清后按根因修(比如服务端切分/收敛而不是让模型重试),文案指引只在真正的 last-resort 场景保留。

@ranxianglei

Copy link
Copy Markdown
Owner Author

跟进搁置评论里的调查问题,结论:观察到的那批错误循环,其生产者在当前 master 上都已死,acp_status 指引确实是在给一条基本死掉的路打补丁。

生产复现(#1262, bili 0.1.55, 8/25-26)的完整因果链:

  1. 陈旧 range 的生产者 A——ref 重编号:旧内核每次成功 compress 后重编号剩余 refs,nudge 里的 range 立即过期 → 模型按旧 range 提交 → 连环拒绝。已被 refs 稳定性重设计消灭(本 PR 第二处措辞修改正是记录这个语义)。
  2. 陈旧 range 的生产者 B——opener-hash 会话继承:0.1.55 用首条 user 消息哈希做持久 state 键,同 opener 新任务继承旧会话的压缩块和保护带。已被 prune/compress: a fold can split one tool call from its result (parallel burst) — unrecoverable 400 on strict OpenAI backends #286/mergeRangesToThreshold 输出低于 gate 阈值的尾部批次,破坏「列出即可压缩」不变量 #309 消灭:会话 id = 客户端提供的会话值原样(零哈希);无身份的请求走 prefix-affinity(全历史最长前缀匹配 + fork 语义)或显式 400,src/server.ts:1560-1606 已验证——同 opener 分叉的新任务按 fork 语义切新会话,不再继承。
  3. 内核侧本就是“疏”式(src/compress.ts:1013-1050):范围擦到保护消息 = 剔除后继续折叠,只有整段都在保护带才拒绝。生产里确认过 partial 路径正常降级。

残留:当前 master 上这个拒绝只剩一种触发方式——模型完全无视 nudge 列出的 range、自己点名最新 ~5 条消息。若未来还高频出现,根治方向是把当前可压缩边界(最老可压 ref)直接嵌进拒绝文本,让错误自含收敛信息,而不是让模型多跑一趟 acp_status。维持搁置。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

同意搁置,你的因果链我独立复核了一遍(当前 master 254e6e4,纯内核内存复现,无 adapter 参与),三点全部成立,另附一个对你搁置评论里假设 B 的反证和一条对未来根治方案的精度修正。

复核结果

  1. 内核侧「疏」式语义 — 确认。src/compress.ts:1013-1050:range 擦到保护消息 = 从 filteredIds 剔除后继续折叠(仅 warning),只有整段解析出的消息全部落在保护带且无已消费块时才 throw。与你描述一致。

  2. 时序偏斜生产者(你搁置评论里的假设 B)——不成立,有证明。我构造了最坏情形:先经 buildCompressibleRanges 列出 ranges,再在 apply 前向消息数组追加新消息(含一条大尺寸 acp_status 工具结果——保护带扩张的最大压力源),然后提交列出的 range。结果:

    • 单调性:保护带在 listing→apply 之间从不向后扩展覆盖旧内容。token 回溯预算被更新尾部优先消耗,last-5 窗口只向前滑,旧 user 消息只会被新 user 消息替换掉出带外。实证:apply 时新增受保护的旧 ref 数 = 0,zone 对旧内容的覆盖严格缩小、只新增了新 ref。
    • 因此按列表提交的合规 range 不可能因时序变成「整段入带」——实测 blocksCreated=1、零错误。adapter 传哪个时刻的消息数组都不影响这个结论(只要 state 一致)。
  3. 残留触发器 — 复现成功。唯一能触发该拒绝的路径就是你说的那种:模型无视列表、自点最新 ~5 条可见 ref。实测精确复现生产报错串(Range is entirely within the protected zone ... Adjust startId/endId to older messages.)。一个细化:极小会话里自点的尾部若总量 < minCompressRange(5000 chars),会先被 src/compress.ts:343-388 的尺寸预检拦下报另一个错——所以生产循环里模型点的是较大的近期消息,才轮到 zone 拒绝出手。与观察到的模式吻合。

对未来根治方向的精度修正:若把边界嵌进拒绝文本,有用的 ref 是带外的最新可压 ref(= 安全的最大 endId),不是最老可压 ref。措辞建议:target endId at or before mNNNNN。由上面的单调性保证:listing 时在带外的 ref 在 apply 时仍在带外,所以 endId ≤ 该边界 ⇒ range 必含至少一条不受保护消息 ⇒ 整段拒绝数学上不可能;残余的部分重叠仍走剔除降级路径。需处理一个边界情况:全新短会话中全部消息都在带内,没有可嵌入的安全 ref,需要兜底文案。这样错误完全自含收敛,连 acp_status 那趟都省了。

处置:PR 保持搁置不动(open,未合并)。注意 master 已越过它的 base(3616730 → 254e6e4,中间是 #375/#388),将来若复活需 rebase,但 diff 全是字符串改动,冲突风险极低。边界 ref 方案我先不单独立项——当前触发频率≈0,按你的条件「未来还高频出现」再开 issue 更合适;出现信号时说一声即可。


中文摘要:独立复现证实维护者的搁置结论(refs 重编号与 opener-hash 两个陈旧 range 生产者在当前 master 均已死),并额外证明了时序偏斜也不可能让合规提交撞进保护带(保护带单调性),该拒绝现仅剩「模型自点最新几条」一种触发方式;建议未来根治时把带外最新可压 ref(最大安全 endId)直接嵌入拒绝文本,PR 维持搁置不合并。

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.

compress: protected-zone whole-range rejection lacks acp_status/current-ranges guidance (retry loops)

1 participant