Skip to content

feat: mechanically preserve subagent session ids in compression blocks (#481) - #482

Open
ranxianglei wants to merge 3 commits into
masterfrom
2026-09-29_mechanical-fidelity
Open

ranxianglei wants to merge 3 commits into
masterfrom
2026-09-29_mechanical-fidelity

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What

Root-cause fix for #481 (parent: ranxianglei/billion-context#1563): when a fold range contains opencode subagent dispatch tool messages whose content carries ses_... session ids, the kernel now mechanically extracts those ids from the folded originals and force-appends them to the compression block summary as a machine appendix line ([acp-preserved:subagent-session] <id>, <id>). The model never transcribes the id, so fidelity no longer depends on summary-writing quality.

How

  • New module src/preserved-ids.ts: extractor registry (DEFAULT_FIDELITY_EXTRACTORS = word-boundary-anchored ses_[A-Za-z0-9]+, so ses_ab / ses_abcdef stay distinct), source-text selection (tool-call / tool-result messages only — the directed dispatch-pair scope), dedup-preserving extraction, and an appendix builder that skips ids already present verbatim in the model summary.
  • Single insertion point at block creation in applySingleRange (covers T1 folds, lean-pack folds, and T2/T3 distillation uniformly) plus applyRefolds (refold-in-place must not drop ids either). Source texts = live folded tool messages + consumed blocks' summaries (tier distillation carries ids forward through summaries on pruned hosts).
  • When an appendix is appended, a transparency note is surfaced via result.notes (same channel as resolveBoundaries 静默交换反向引用,参数错误被伪装成内容错误 #310 reversal notes).

Behavior change (flagged per owner policy)

  • Old: stored block summary == exactly the model-written text.
  • New: stored block summary == model text + optional machine line [acp-preserved:subagent-session] ... — only when folded tool content contains ses_ ids missing from the model summary. No prompt / summary-writing rules changed; when nothing is extracted the output is byte-identical (pinned by tests). Cost: tens of bytes per affected fold. Side benefit: blocks become searchable by session id.

Scope notes

  • Extractors are deliberately narrow per #481 (subagent session ids only); commit-hash / PR-number extractors can join the same registry later (泛化与否另议).
  • Absorb records and emergency truncation are not compression-block channels and are untouched.

Tests

tests/preserved-ids.test.ts (15 new) implements the issue's mechanical assertions: whatever the model summary outputs, the full ses_ string exists verbatim in the stored block summary across fresh T1 folds, T2 distillation propagation, and refold-in-place; plus byte-identity pins (no ids / ids already present), prose-narrowing pin, dedup/order, and prefix-collision safety. On rebased head: typecheck clean, 1217/1217 tests pass, build OK, prettier clean.

中文摘要:修复折叠摘要抄丢 opencode 子代理 ses_ session id 的根因——折叠提交时由代码从被折叠工具消息中机械提取 id 并强制写入压缩块附录(模型不经手),T1/T2/refold 全通道覆盖,无论模型摘要写什么,id 逐字存活。

Fixes #481

ework-agent added 3 commits September 29, 2026 22:49
When a fold range contains opencode subagent dispatch tool messages whose
content carries ses_... session ids, extract those ids from the folded
originals and force-append them to the compression block summary as a
machine appendix line ([acp-preserved:subagent-session] ...). The model
never transcribes the id, so fidelity no longer depends on summary-writing
quality. Inserted at the single block-creation site in applySingleRange
(covers T1 folds and T2/T3 distillation uniformly) plus applyRefolds so
refold-in-place cannot drop ids either. Source texts are live folded tool
messages plus consumed blocks' summaries (tier distillation carries ids
forward through summaries on pruned hosts). A transparency note is surfaced
via result.notes when an appendix is appended.
Mechanical assertions per #481 acceptance criteria: whatever the model
summary outputs, the full ses_ string exists verbatim in the stored block
summary across fresh T1 folds, T2 distillation propagation, and
refold-in-place; plus byte-identity pins (no ids / ids already present),
prose-narrowing pin, dedup/order, and prefix-collision safety.
@github-actions

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-09-29_mechanical-fidelity (8480b22)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-482

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

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf acp-kernel-pr482.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

Reviewed PR #482 (head 2026-09-29_mechanical-fidelity @ 8480b22, base master). This carries the ework-agent-pr marker, so it's completed agent work — I verified it rather than redid it.

CI: all green on head — pr-validation, test (22), test (24), build-artifact all success. PR reports mergeable: clean.

Direction (root cause): correct. Chain billion-context#1563 → bug issue (now living at billion-context#1702): the model transcribes opencode subagent ses_… dispatch ids into fold summaries and drops/mangles them. The fix moves fidelity from model-dependent to mechanical — code extracts the verbatim ids from folded tool-call/tool-result content and force-appends them to the block summary, so survival no longer depends on summary-writing quality. Fixes the root cause, not the symptom. ✓

Code review (src/preserved-ids.ts new + two insertion points in src/compress.ts):

  • Extractor \bses_[A-Za-z0-9]+\b is word-boundary anchored → prefix-collision safe (ses_ab vs ses_abcdef stay distinct; no spurious prefix match). Confirmed by reasoning + pinned tests.
  • extractFidelityIds clones the regex per call (no shared-lastIndex mutation), dedups via Set, preserves first-occurrence order, guards zero-length matches. Correct.
  • buildFidelityAppendix emits one labeled line per extractor, skips ids already present verbatim in the model summary, returns "" otherwise → byte-identity guarantee when nothing is extracted. Idempotent across repeated refolds (one line per label, dedup within label).
  • Source scoping is deliberately narrow: tool-call/tool-result only (the dispatch-pair shape); prose excluded (pinned test). Matches #481 direction (subagent ids only; commit-hash/PR-number can join the registry later).
  • Insertion points: a single point in applySingleRange (covers fresh T1, lean-pack folds, and T2/T3 distillation uniformly) + applyRefolds (refold-in-place must not drop ids). Source texts = live folded tool messages + consumed blocks' summaries (tier carry-forward on pruned hosts). Both call sites pass messages. Correct.
  • Transparency note surfaced via the existing result.notes channel (same as resolveBoundaries 静默交换反向引用,参数错误被伪装成内容错误 #310 reversal notes) — typed at src/types.ts:555, wired into the return at src/compress.ts:725. The "same channel as resolveBoundaries 静默交换反向引用,参数错误被伪装成内容错误 #310" claim is accurate.
  • Zero runtime deps preserved (imports only types.js); pure functions, platform-agnostic. No as any / @ts-ignore.

Regression review — existing behaviors on the touched paths:

  • applySingleRange stored summary: OLD = exactly the model text → NEW = model text + optional machine line, ONLY when folded tool content contains ses_ ids missing from the model summary. The common case (no ids) is byte-identical (pinned). Additive only in the narrow case. No regression.
  • applyRefolds: same additive logic; refold must not drop ids. No regression.
  • notes: additive, omitted when empty; existing note semantics unchanged.
  • Untouched: node ordering / pipeline shape, ref allocation & immutability, tool-pair atomicity, protected-tool filtering, the four load-bearing prompts, and the wire tag structure. Lossless round-trip holds (the machine line restores verbatim on decompress). Single-owner content intact — assignRefsNode is still the sole writer of message content; this writes the block summary.
  • maxSummaryLength is enforced pre-append in validateSummaryLength; no downstream path slices/truncates block.summary, so the machine line can never be cut off. Stored summary may exceed the cap by ~tens of bytes — intentional (fidelity > cap), harmless.

Behavior change (flagged per owner policy): the stored block-summary format gains an opt-in machine line [acp-preserved:subagent-session] <id>, …. Old value = model text only; new value = model text + optional line, added only when needed. Cost = tens of bytes per affected fold. Side benefit = blocks become searchable by session id. Already disclosed in the PR body — good.

Tests (15 new, tests/preserved-ids.test.ts): all real assertions with teeth against the stored block.summary — mechanical includes-full-string across fresh T1 / T2 propagation / refold-in-place; byte-identity pins (no ids / ids already present); prose-narrowing pin; dedup + order; prefix-collision safety; note-surfacing. A regression that loses/duplicates/mis-scopes an id would fail these. Not tautological. Strong. ✓

Diff cleanliness: 5 files, +684/−7, every line on-purpose (new module, barrel export, the two insertion points + notes wiring, one module-map doc line, tests). No unrelated churn, no mass whitespace, no lock/generated files. Clean. ✓

Two non-blocking caveats for the owner:

  1. Shared hotspot — src/compress.ts is also touched by open PRs fix: reconcile-live-ids keeps positional replays of folded originals #466, fix: re-anchor nudge cadence when count drops below the shown reference #479, fix: protected-zone rejection carries acp_status guidance; leanPack ref-stability wording (#385) #387, feat: conditionally protect acp_rule pairs by stream-derived rule liveness #378 (and index.ts/types.ts by fix: reconcile-live-ids keeps positional replays of folded originals #466/feat: conditionally protect acp_rule pairs by stream-derived rule liveness #378). feat: mechanically preserve subagent session ids in compression blocks (#481) #482's change is orthogonal (summary-text appendix vs their id-reconciliation / nudge / rule-protection work), so semantic conflict is unlikely, but whoever merges first forces rebases on the rest (§7.2 standard churn).
  2. Issue link — the PR body's Fixes #481 points at an issue that now redirects to ranxianglei/billion-context#1702 (the live open bug issue, "折叠时按消息类型机械保真…"). As written, Fixes #481 inside an acp-kernel PR will not auto-close #1702 (cross-repo). Suggest changing the body to Fixes ranxianglei/billion-context#1702 before merge, or close #1702 manually post-merge. Not a code blocker.

Verdict: code correct, well-tested, clean diff, CI green, direction sound, behavior change properly disclosed. This is a feat: that changes stored block-summary shape → correctly human-gated (not auto-merged). Ready for human review & merge. I can't merge PRs — AGENTS.md forbids it. Please merge yourself: #482

中文摘要:审核了 PR #482(机械保真:折叠时由代码从工具消息中提取 opencode 子代理 ses_ session id 并强制写入压缩块附录,模型不经手)。方向正确(修根因——保真不再依赖模型摘要质量)、代码与测试扎实(前缀碰撞安全、无 id 时字节级恒等 pin、散文不纳入、T1/T2/refold 全通道覆盖)、CI 全绿、diff 干净,且已按 owner 政策披露「存储摘要格式新增可选机器行」这一行为变更。两处非阻塞提醒:src/compress.ts 是与 #466/#479/#387/#378 的共享热点(合并顺序需 rebase);正文 Fixes #481 因跨仓迁移不会自动关闭真实 bug issue(billion-context#1702),建议改用完整限定符或合并后手动关闭。结论:可以合并(需人工执行)。

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