Skip to content

feat(recommend): structure-aware range selection (#344) - #345

Open
ranxianglei wants to merge 2 commits into
masterfrom
2026-09-22_structure-aware-ranges
Open

ranxianglei wants to merge 2 commits into
masterfrom
2026-09-22_structure-aware-ranges

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #344

What

Pressure compression now picks candidate ranges that respect content structure, per #344 (from billion-context#1122: keep usage-band triggers, improve which ranges fold under pressure). Two deterministic heuristics in buildCompressibleRanges, no side-model, no config change:

  1. In-progress turn exclusion — new exported computeOpenTurnMemberIds(messages): a tool-call with no tool-result anywhere in the array marks its whole turn group (reasoning run + burst + paired results) as a gap. A live tail turn or a partially-complete parallel burst is no longer folded mid-sequence; range endpoints stop before it. The triggering user message stays compressible (no wire-level pairing constraint on user messages; recent-zone defaults already protect the tail).
  2. Per-segment foldability — after the existing whole-set integrity screen, each contiguous segment of the compressible set is re-screened with computeIntegrityWithdrawals until stable (hard cap 8 passes; every productive pass removes ≥1 message so real sessions converge in a few). Withdrawn members become gaps. This closes the case the whole-set screen cannot see: a call/result pair or a reasoning/call turn split by an interior gap (protected tool, zone edge), where today's code advertises ranges the apply-side fold gate always empties.

What is deliberately unchanged

  • Triggers: decideNudge usage-band/pressure branches untouched (issue item 3).
  • Config schema: no new knob — both refinements are conditional no-ops (early exit when no open calls / no interior gaps), so behavior only changes where it was structurally wrong.
  • Base grouping rules (user-boundary split at count ≥ 3): byte-identical, so self-contained histories produce the exact same ranges as before.
  • nudge / preemptive / host-preflight consumers benefit automatically through recommendedRanges; status surfaces stay consistent because refinement happens inside buildCompressibleRanges.

Acceptance mapping (#344)

  • In-progress tool-call history → no recommended range covers any member of the open turn: tests/recommend-structure-aware.test.ts (open tail, partial burst, sequential call→result→live-call variant).
  • All-self-contained history → regression pins deep-equal legacy output (two fixtures) + full existing suite green + every advertised range folds cleanly via applyCompression.
  • Deterministic: double-run deep-equal test on a mixed fixture.

Verification (rebased head, node 22)

  • npm run typecheck ✅
  • npm test ✅ 879/879 (incl. 10 new)
  • npm run build ✅
  • format:check not part of CI (ci.yml runs typecheck/test/build); note: prettier 3.9.6 (resolves from the unpinned ^3.3.0) flags 115 files on pristine master — pre-existing drift, left untouched here to keep the diff clean.

Interaction with PR #343

#343 fixes mergeRangesToThreshold (batching never bridges block gaps). This work sits upstream of merging, at the message-list level, so the two compose: this PR guarantees each pre-merge segment is individually foldable, #343 guarantees batches don't span block gaps. If #343 merges first I'll rebase this onto it.


中文摘要:实现了结构感知的压缩范围选择——进行中(未闭合)的工具调用轮次不再被纳入可压缩候选,段内切分点保证每个推荐范围单独可折叠;触发条件与配置 schema 未动,自洽历史输出与现状逐字节一致,879 项测试全绿,可以合并。

Keep candidate ranges out of in-progress turns and out of segments that cannot fold alone:
- computeOpenTurnMemberIds excludes turns holding a tool-call with no tool-result anywhere (live or partially-complete burst) from the compressible set;
- per-contiguous-segment foldability refinement re-runs the integrity gate per segment until stable, so no advertised range splits a call/result pair or a reasoning/call turn across an interior gap.
Triggers (decideNudge) untouched; config schema unchanged; self-contained histories converge in one no-op pass (byte-identical output); deterministic.
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-09-22_structure-aware-ranges (f5e7905)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-345

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

Option B — Download artifact

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

[bot] 🏷 Reviewed PR #345 (branch 2026-09-22_structure-aware-ranges) — verified, did not re-do the work.

Diff & base. Single commit on top of current master (b30723f, v0.0.82): src/recommend.ts (+151/−11) + new tests/recommend-structure-aware.test.ts (386 lines). No unrelated files, no whitespace churn, no lock/generated changes. decideNudge, config schema, and the user-boundary grouping rules are untouched, as claimed.

Technical verification (traced, not just read):

  • Open-turn exclusion — computeOpenTurnMemberIds excludes whole turn groups via computeTurnGroups; because exclusion takes the group wholesale, it cannot leave a partial turn behind that would create a new integrity violation. Results anywhere in the array count as "closed" — correct layering, since a closed-but-protected result is an INV2 pair-split case the integrity gate already handles. Early exit on zero open calls = zero overhead.
  • Per-segment refinement — confirmed the gap the whole-set screen truly has: with a call/result pair split by an interior protected gap, both members sit in the whole-set fold set, so INV2 never fires (remaining.has(call) === remaining.has(result)); folding either segment alone then empties at the apply gate. Walking the sandwich fixture (u0, c1[bash], c2[skill], t2, t1 with protectedTools:["skill"]): legacy advertises [m00001..m00002] + [m00005], both of which the fold gate strips to nothing useful; after refinement only [m00001..m00001] remains and folds cleanly. Convergence is sound: every productive pass removes ≥1 message, cap 8 bounds adversarial chains, and hitting the cap leaves state the apply-side gate already guards.
  • Self-contained histories — fully-contiguous ones skip refinement entirely (gapCount === 0), byte-identical output; two regression pins + applyCompression round-trip checks cover it.

Tests. All 10 new tests map to the #344 acceptance list (open tail, partial burst, sequential live call, 2 legacy pins, interior-gap sandwich, merged-batch boundary, double-run determinism). Ran locally on node 22.23.2: typecheck ✅, npm test 879/879 ✅, build ✅. CI green on the original head (test 22/24, build-artifact, pr-validation).

One fix pushed during review — f5e7905 style(recommend): format structure-aware additions to house prettier style: the new code itself wasn't prettier-clean (the multi-line computeOpenTurnMemberIds signature in src/recommend.ts + ~10 over-width lines in the new test file). Formatted only the PR-owned lines; the pre-existing master drift (~115 files) was left untouched as intended. Re-ran typecheck/test/build locally (all green) and CI is green on f5e7905.

Interaction with #343 — agrees with the description: this sits upstream of merging (message-list level), #343 fixes mergeRangesToThreshold batching; they compose. If #343 lands first, a rebase will be needed as noted.

Ready to merge — per repo rules I can't merge myself. Please merge when you're happy: #345

中文摘要:核对了 diff(单 commit、仅 recommend.ts + 新测试,基于最新 master)并本地复跑 typecheck/879 测试/build 全绿;逐条验证了开放轮次排除与段内可折叠性两个启发式的正确性与收敛性;review 中直接补了一个 commit 把新增代码按仓库 prettier 风格格式化(未动 master 既有漂移),新 head CI 已绿——可以合并。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant