Skip to content

feat: add adaptive compression candidates - #341

Open
drexb-ops wants to merge 4 commits into
ranxianglei:masterfrom
drexb-ops:2026-08-25_adaptive-compression-candidates
Open

feat: add adaptive compression candidates#341
drexb-ops wants to merge 4 commits into
ranxianglei:masterfrom
drexb-ops:2026-08-25_adaptive-compression-candidates

Conversation

@drexb-ops

Copy link
Copy Markdown

Summary

This PR adds executor-safe adaptive compression candidates while preserving ACPs existing range compression lifecycle.

  • Add bounded MICRO candidates for large plain messages and complete tool transactions.
  • Add EPISODE candidates for adjacent smaller historical units.
  • Share executor-equivalent boundary, protection, pair-closure, and minimum-size validation between planning and execution.
  • Show the same candidates in nudges and default acp_status output.
  • Keep candidates advisory: first nudges request one clearly stale candidate, repeated nudges escalate, and emergency nudges remain direct without requiring all candidates.
  • Split episodes at protected units and make production nudge gating authoritative to the executable candidate planner.
  • Add deterministic/property/unit/integration/E2E coverage and sync the stable local deployment path.

Motivation

Broad contiguous recommendations can force the model to compress useful reasoning together with a stale tool artifact. DCP-style message priority signaling is useful, but independent message compression can break tool call/result context. This approach improves target precision without introducing a new mode, tool, persisted state format, or automatic compression.

Compatibility

Existing compress range calls, arbitrary ranges, batch behavior, protection rules, quality gates, persisted block state, decompression, search, and T1/T2/T3 behavior remain available. No new runtime dependencies or config migrations are introduced.

Validation

  • npm test: 1045 passed
  • npm run typecheck: passed
  • npm run build: passed
  • SKIP_BUILD=1 ./scripts/e2e/run-e2e.sh: 13 passed
  • Changed-file formatting and git diff --check: passed

Author: Drex

@drexb-ops

Copy link
Copy Markdown
Author

Hey, I really like your idea, and ACP has been very helpful. It’s a really good project.

The only issue I’ve run into is that it can sometimes over-compress. When that happens, the agent may compress information it still needs, which can cause it to redo work or lose track of the current task.

I wanted to explore a way to compress only things that are genuinely no longer needed, while also allowing compression in different parts of the conversation instead of being limited to one broad range. The goal is to reduce context without making the agent lose its place.

I’ve been testing this approach, and it has been working really well so far. I think it could complement the core project nicely. Thanks for building such a useful tool.

@ranxianglei

Copy link
Copy Markdown
Owner

review

@ranxianglei

Copy link
Copy Markdown
Owner

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

Review: feat: add adaptive compression candidates (agent review 1/2 — AGENTS.md §5.3 requires a second independent review before merge)

Verdict: approve with minor findings — no blockers.

Verified locally (branch pr-341-review @ c08d81b):

  • npm run typecheck ✓ · npm test 1045/1045 ✓ · npm run build
  • CI: pr-validation ✓, test (22) ✓, test (24) ✓, build ✓, e2e
  • Prettier: all PR-changed files clean. (Repo-wide format:check reports 400 files, but master reports 411 — pre-existing baseline drift, not introduced by this PR.)
  • AGENTS.md compliance: devlog REQ/WORKLOG/DESIGN present and match the branch name; branch name matches the CI regex; package.json version untouched (correct for a feature PR).

Design — strong points:

  1. Executor parity is the right core idea. Candidates are planned through prepareExecutableRangePlans — the same code path compress runs at execution time — and selectionMatchesSource (exact rawIndex + messageIds equality) rejects any drift. A listed candidate is structurally submittable, and planner errors fail closed (nudge falls back to legacy range filtering; status degrades gracefully).
  2. The range.tsrange-utils.ts extraction is a faithful refactor: execution-path filter order (protected tools → last user message → recent protection → min-size) is unchanged, and the existing suite still passes.
  3. The advisory framing ("structurally safe to submit, not a command to compress") is consistent across the system prompt, all four nudge templates, and the compress tool prompt — directly addressing the over-compression motivation in floor 1.
  4. Test quality is high (AGENTS.md §5.6/§5.7): imports real source modules, complete config factory, a multi-turn baseline test asserting both shouldInjectThisTurn and lastPerMessageNudgeTokens/lastNudgeShownTokens across the full baseline→growth→nudge→compress→re-fire cycle, and a production preserveRecentMessages: 20 case. E2E scenario 13 satisfies §5.7.2: the fake LLM parses the advertised MICRO candidate from nudge text and compresses exactly that boundary; verify.ts asserts candidateSelected, candidateBoundaryMatches (block startId/endId persisted), and nudgeBaselineSet.

Findings (all minor, non-blocking):

  1. CI build-artifact failure — the pr-artifact.yml "Publish to npm with PR tag" step fails because this PR comes from an external fork and GitHub does not pass repo secrets (NPM_TOKEN) to fork PRs by default. Infrastructure/permissions issue, not a code defect, and it is not a required merge check (branch protection gates on pr-validation). Side effect: no installable opencode-acp@pr-341 artifact or install-instructions comment was produced. Fix: enable fork-PR secret access in repo settings, or rebase the work onto a branch of the main repo.
  2. Episode flush missing adjacency check — in buildDrafts (lib/messages/inject/candidates.ts), the protected-unit flush merges accumulated residual units into one episode even when they are non-contiguous (e.g., a MICRO unit between two small units). The draft then spans across the micro unit and fails selectionMatchesSource, dropping the whole episode as executor-selection-drift. Safe direction (false negative — candidates lost, never wrongly included), but the trailing residual loop already does the right adjacency check (unit.startIndex !== previous.endIndex + 1); the flush should reuse that grouping.
  3. search_context system-prompt inconsistency — the PR correctly fixed the tool description (execute() never searched visible messages; deep was always a no-op), but lib/prompts/system.ts:28 still says "Search compressed block summaries (and optionally visible messages)". Please update that line to match.
  4. acp_status error path hides all guidancerenderCompressionCandidates's catch returns "(candidate planning unavailable)", replacing the old behavior of showing raw compressible ranges. Fail-closed is reasonable, but consider falling back to renderUncompressedRanges in the catch (the function is right there) so the model still gets some guidance.
  5. Behavior change worth a release note: nudge suppression now keys off executable candidates instead of legacy range filtering (allProtected/allInProtectedZone/allBelowMin). Intended and tested, but sessions where legacy filtering found eligible ranges and the planner omits everything (recent protection, active compression, drift) are now suppressed — the new no_executable_candidates log reason helps observability.

Performance note (observation, no action needed): candidate planning runs on every message transform (every LLM call). includeTokenAccounting: false correctly skips tokenizer counting on the planning path and the 12-candidate cap bounds cost, but it's a hot path worth keeping in mind for future tuning.

@ranxianglei

Copy link
Copy Markdown
Owner

简述这个修复思路 以以及为什么过度压缩

@ranxianglei

Copy link
Copy Markdown
Owner

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

为什么会出现过度压缩(根因)

  1. 粒度太粗:旧机制给模型列的是"按对话轮次分组的可压缩范围",一个 range 里混着过期的工具输出和仍有用的推理/决策。系统提示还写着 "All listed ranges should be compressed"——是命令式语气,模型在 nudge 压力(尤其 context limit 强提醒)下倾向于把整个 range 压掉,连还需要的内容一起压。
  2. 压了就拿不回来:摘要是损真的,压掉后细节丢失,agent 只能重读文件、重跑命令,或者丢失任务状态——这就是 floor 1 里抱怨的"压缩了还需要信息 → 重做工作"。
  3. 结构性限制:range 内部没有"结构安全"的切分单元。工具调用和结果是成对结构,模型想只压掉一个过期工具输出、保留相邻推理,旧机制下做不到(会破坏 tool call/result 配对),只能整段压。

这个 PR 的修复思路

核心一句话:把"命令你压这些范围"改成"给你一批结构上保证可提交、彼此独立的候选,你自己判断哪些真的不需要了再压"

  1. 候选规划器lib/messages/inject/candidates.ts):每次消息变换时扫描可见消息,产出有界、独立、不重叠的候选,最多 12 个:
    • MICRO:一条大消息,或一个完整工具事务(调用+结果+传递闭包),保证 tool-pair 结构完整;
    • EPISODE:由相邻小单元组成的连续历史片段,遇到受保护消息就断开。
  2. 执行器同构(executor parity):每个候选都用 compress 工具执行时的同一条代码路径prepareExecutableRangePlans:边界解析、工具对扩展、受保护工具/最后用户消息/近期保护过滤、最小尺寸校验)验证,再用 selectionMatchesSource 做精确比对,有漂移就丢弃。所以列表里的候选保证提交不会报错——没有幻影目标。规划器出错时 fail-closed,回退到旧的 range 过滤。
  3. 措辞从命令改为建议:系统提示、四个 nudge 模板、compress 工具提示统一改为"候选只是结构安全的建议,不是压缩命令;只压内容不再需要的候选,保留当前意图和活跃工作,没有合适候选就不要硬造目标"。首次 nudge 要求处理一个明显过期的候选,重复 nudge 才升级语气,紧急 nudge 保持直接。
  4. nudge 门控以候选规划器为准:只有存在可执行候选才发 nudge(无候选 → 不催促,避免幻影重试循环)。
  5. 候选同时展示在 nudge 和 acp_status 默认视图,列表过期时模型可以重新拉取最新视图。

为什么这样能抑制过度压缩:粒度上,模型可以只压掉那个过期的工具输出(MICRO),不动相邻还有用的推理;结构上,pair-safe 边界让"只压一半"第一次变得安全可行;语义上,"什么还需要"的判断权交还给模型,系统只负责保证"列出来的东西提交了一定有效"。

@ranxianglei

Copy link
Copy Markdown
Owner

这个问题也提交到内核一份 应该有一样的问题 拆分出两个 一个是候选算法 候选算法支持拔插 一个是压缩提示词 提交两个 issue 到 acp kernel

@ranxianglei

Copy link
Copy Markdown
Owner

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

内核仓库确认为 ranxianglei/context-compress-algorithms(v1.3.0,现有模块:quality-gate / prompts / trigger,无候选规划器)。当前沙箱没有创建 issue 的工具权限(本地 tracker API 不在我的网络边界内),所以按该仓库现有的英文 issue 规范备好了两个完整 issue,可直接创建:


Issue 1

Title: feat: candidate planner algorithm (compression candidates) — pluggable module

## Problem
Hosts that use imperative range-based compression guidance ("all listed ranges should
be compressed") experience over-compression: under nudge pressure (especially at the
context limit), the model compresses whole turn-grouped ranges, including reasoning
and decisions it still needs. Summaries are lossy, so the agent re-reads files,
re-runs commands, or loses task state.

Prior art: opencode-acp PR #341 (feat: add adaptive compression candidates) solves
this host-side by planning a bounded set of independent, non-overlapping candidates
and framing them as advisory. The algorithm is host-agnostic and belongs in the kernel.

## Proposal
Add a `candidates` module to the kernel:

- Input: visible message sequence + session/protection config.
  Output: `CandidatePlan { candidates, omitted, truncatedCount }` (bounded; default cap 12).
- Candidate kinds:
  - `micro` — one large plain message, or one complete tool transaction
    (call + result + transitive span); tool-pair safe.
  - `episode` — contiguous historical segment composed of smaller adjacent units;
    split at protected units.
- Structural safety (planner guarantees, host enforces): protected exclusion
  (protected tools, last user message, recent-protection window), tool-pair closure,
  minimum-size filter, deterministic and non-overlapping output.
- Pluggability: follow the existing quality-gate registry pattern —
  `registerCandidatePlanner()` / `getCandidatePlanner()` / `listCandidatePlanners()`,
  with a built-in default (`micro-episode-v1`). Hosts can register/replace planners;
  host code depends only on the interface.
- Executor-parity hook: expose a validation seam so hosts run each candidate through
  the SAME code path their compress tool executes (opencode-acp:
  `prepareExecutableRangePlans` + exact `selectionMatchesSource` check).
  Guarantee: everything listed is structurally submittable; planner errors fail closed.

## Reference implementation
opencode-acp PR #341: `lib/messages/inject/candidates.ts` (~567 lines) +
`tests/compression-candidates.test.ts` (12 unit tests),
`tests/compression-candidates-property.test.ts`, e2e scenario 13.

## Acceptance criteria
- Deterministic, non-overlapping, bounded output; fail-closed on planner errors.
- Unit + property coverage per the kernel's existing test conventions.
- No breaking changes to existing exports (`quality-gate`, `prompts`, `trigger`).

Issue 2

Title: feat: advisory compression prompt set (nudge + system + tool guidance)

## Problem
Current prompt rules (kernel `compression-rules` + host nudge templates) use
imperative phrasing: "All listed ranges should be compressed", "compress now",
"Prefer one large range over multiple small ones". Combined with range-based
targets this amplifies over-compression — the model complies by compressing
everything listed, including active work.

## Proposal
Add an advisory prompt set to the kernel `prompts` module (new exports; existing
exports unchanged):

- COMPRESSION CANDIDATES section (system prompt): candidate semantics —
  MICRO = one large plain message or complete tool transaction; EPISODE =
  contiguous historical segment; entries are independent, non-overlapping,
  batchable; advisory, not a command ("a candidate is structurally safe to
  submit, not a command to compress"); do not invent a target when none is
  listed; keep current intent and active work visible.
- Nudge templates (turn / context-limit / iteration): first nudge requests
  compressing ONE clearly stale candidate; repeated nudges escalate
  ("already been shown without a compression"); emergency/max-limit nudges stay
  direct but still scope to listed candidates; "Do not compress active work or
  every candidate."
- Compress tool prompt guidance: use the candidate's exact IDs; batch only
  independent stale candidates in one content[] call; verify IDs are still
  visible before submitting; use acp_status for a fresh view when the list is stale.
- Wording principle: the system guarantees structural safety; the model retains
  semantic judgment about what is still needed. Parameterize tool names/limits so
  hosts can reuse the templates.

## Reference implementation
opencode-acp PR #341: `lib/prompts/system.ts` (COMPRESSION CANDIDATES section),
`lib/prompts/turn-nudge.ts`, `lib/prompts/context-limit-nudge.ts`,
`lib/prompts/iteration-nudge.ts`, `lib/prompts/compress-range.ts` (CANDIDATE GUIDANCE).

## Acceptance criteria
- New exports only; no breaking changes (follows the v1.3.0 holistic TIER2/TIER3
  prompt pattern).
- No imperative "compress all" phrasing remains in the advisory set.

两点说明:① 两个 issue 都引用本 PR 作为参考实现(未合并前是 prior art,合并后可直接摘代码);② 候选算法的"拔插"沿用了内核里 quality-gate 已有的 registry 模式(register/get/list),保持内核 API 风格一致。

@ranxianglei

Copy link
Copy Markdown
Owner

@ranxianglei

Copy link
Copy Markdown
Owner

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

收到,更正:内核是 ranxianglei/acp-kernel(压缩 & 状态管理引擎,零运行时依赖),不是我上一条说的 context-compress-algorithms(那个只是提示词/算法小包)。之前那条按错仓库写的 issue 作废,以下按 acp-kernel 的实际情况和 issue 规范(英文、feat(scope): 风格)重写。已核对:内核现有 recommend 管线节点(产出按轮次分组的 CompressibleRange)、viable.tsVIABLE_RANGE_MIN_TOKENS = 200 最小尺寸过滤)、src/search/registry.ts + src/filter/registry.ts(现成插件化 registry 模式)、src/prompts.tsPrompts 接口 + defaultPrompts)+ nudge-text.tsNudgeVoice = "gentle" | "emergency");29 个 open issue 中无候选规划器相关重复项。

沙箱内仍无创建 issue 的工具权限,两个 issue 内容如下,可直接创建:


Issue 1

Title: feat(recommend): pluggable compression candidate planner (MICRO/EPISODE) — advisory, executor-parity

## Problem
The `recommend` pipeline node emits turn-grouped `CompressibleRange`s, and the
surfaces that display them (nudge-inject, report/acp_status, panel) pair that
list with imperative guidance ("compress now", "compress the listed ranges").
Hosts adopting this guidance see over-compression: under nudge pressure
(especially at the context limit) the model compresses whole turn-grouped
ranges, including reasoning and decisions it still needs. Summaries are lossy,
so the agent re-reads files, re-runs commands, or loses task state.

Prior art: opencode-acp PR #341 (feat: add adaptive compression candidates) —
host-side implementation, under review. The algorithm is host-agnostic and
belongs in the kernel.

## Proposal
New `src/candidates/` module (or extension of `recommend.ts`):

- `planCandidates({ messages, state, config }) → CandidatePlan { candidates, omitted, truncatedCount }`
- Kinds:
  - `micro` — one large plain message, or one complete tool transaction
    (reuse `tool-pairs.ts` pairing for call+result+transitive span closure).
  - `episode` — contiguous historical segment of smaller adjacent units;
    split at protected units (reuse `protected.ts`).
- Structural safety: protected exclusion (protected tools, last user message,
  recent window), tool-pair closure, minimum size (align with `viable.ts`
  `VIABLE_RANGE_MIN_TOKENS` — the kernel's token floor is the right primitive;
  opencode-acp's char-based `minCompressRange` maps onto it), deterministic,
  non-overlapping output, bounded (default cap 12).
- Pluggability: follow the existing registry pattern (`src/search/registry.ts`,
  `src/filter/registry.ts`) — `registerCandidatePlanner()` /
  `getCandidatePlanner()` / `listCandidatePlanners()`, built-in default
  `micro-episode-v1`. Hosts register/replace planners; depend only on the interface.
- Executor parity: validate each candidate through the same validation path
  `applyCompression` runs (the kernel validates the whole batch atomically —
  see the "Summary too short" atomic-rejection incident documented in
  `viable.ts`). Guarantee: everything listed is structurally submittable.
  Planner errors fail closed (surfaces fall back to the current range list).
- Surfaces: nudge-inject node, `report.ts`, panel — candidates replace/augment
  the raw range list (supersedes the grouping concern in #165).

## Reference implementation
opencode-acp PR #341: `lib/messages/inject/candidates.ts` (~567 lines) +
`tests/compression-candidates.test.ts` (12 unit tests),
`tests/compression-candidates-property.test.ts`, e2e scenario 13.

## Acceptance criteria
- Deterministic, non-overlapping, bounded; fail-closed on planner errors.
- Unit + property tests per kernel conventions.
- No breaking changes: new exports; `recommend` node unchanged as fallback.

Issue 2

Title: feat(prompts): advisory compression prompt set (candidate-aware nudge/system/tool guidance)

## Problem
Current prompt text is imperative. `nudge-text.ts` emergency header:
"⚠️ Context limit reached — compress now. Prioritize consumed tool outputs.";
efficiency note: "compress early and keep context lean"; host-side guidance:
"All listed ranges should be compressed". Combined with turn-grouped range
targets this amplifies over-compression — the model complies by compressing
everything listed, including active work.

## Proposal
Extend `src/prompts.ts` (`Prompts` interface / `defaultPrompts`) +
`nudge-text.ts` with an advisory, candidate-aware prompt set:

- COMPRESSION CANDIDATES section: candidate semantics (MICRO = one large plain
  message or complete tool transaction; EPISODE = contiguous historical
  segment); entries are independent, non-overlapping, batchable; advisory,
  not a command ("a candidate is structurally safe to submit, not a command
  to compress"); do not invent a target when none is listed; keep current
  intent and active work visible.
- Nudge voices (`gentle` / `emergency`): gentle = "compress one clearly stale
  candidate before continuing"; repeated-nudge escalation ("already been shown
  without a compression"); emergency stays direct but scoped to listed
  candidates; "Do not compress active work or every candidate."
- Compress tool / wire guidance: use the candidate's exact IDs; batch only
  independent stale candidates in one call; verify IDs are still visible
  before submitting; use status for a fresh view when the list is stale.
- Wording principle: the system guarantees structural safety; the model
  retains semantic judgment about what is still needed.

## Reference implementation
opencode-acp PR #341 prompt changes: `lib/prompts/system.ts` (COMPRESSION
CANDIDATES section), `lib/prompts/turn-nudge.ts`,
`lib/prompts/context-limit-nudge.ts`, `lib/prompts/iteration-nudge.ts`,
`lib/prompts/compress-range.ts` (CANDIDATE GUIDANCE).

## Acceptance criteria
- New/extended exports only; no breaking changes.
- No imperative "compress all" phrasing remains in the advisory set.
- `DESIGN-prompts.md` updated to document the new prompt surface.

与上一条的差异:① 目标仓库改为 acp-kernel;② 候选算法的拔插对齐内核已有的 search/registry.tsfilter/registry.ts registry 模式(比 quality-gate 更贴内核风格);③ 最小尺寸对齐 viable.ts 的 token 下限;④ 提示词 issue 落到内核的 Prompts 接口 + nudge-text.tsNudgeVoice 上,并要求更新 DESIGN-prompts.md;⑤ 标注了与 open issue #165(acp_status range 分组)的关系。

@ranxianglei

Copy link
Copy Markdown
Owner

再试试 你可以提交的

@ranxianglei

Copy link
Copy Markdown
Owner

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

已提交,两个 issue 都在 ranxianglei/acp-kernel

  1. feat: pluggable post-compression quality gate (v1.13.0, issue #20) #166 feat(recommend): pluggable compression candidate planner (MICRO/EPISODE) — advisory, executor-parity
    feat(recommend): pluggable compression candidate planner (MICRO/EPISODE) — advisory, executor-parity acp-kernel#166
    候选规划算法:planCandidates() 产出有界、独立、不重叠的 MICRO/EPISODE 候选;复用内核 tool-pairs.ts / protected.ts / viable.ts;拔插沿用 search/registry.tsfilter/registry.ts 的 registry 模式(registerCandidatePlanner/get/list,内置 micro-episode-v1);executor-parity 保证列出的候选一定能提交,规划失败 fail-closed 回退现有 range 列表。

  2. fix: compress notification no longer injects empty user message (closes #20) #167 feat(prompts): advisory compression prompt set (candidate-aware nudge/system/tool guidance)
    feat(prompts): advisory compression prompt set (candidate-aware nudge/system/tool guidance) acp-kernel#167
    建议式提示词集:落到 src/prompts.tsPrompts 接口 + nudge-text.tsNudgeVoice(gentle/emergency);核心措辞原则"系统保证结构安全,语义判断留给模型";要求更新 DESIGN-prompts.md

两个 issue 均引用本 PR #341 作为参考实现,验收标准含"不破坏现有导出"。

@drexb-ops

Copy link
Copy Markdown
Author

Performance follow-up pushed in a04876e.

After the first compression, OpenCode still sends ACP the full raw transcript every transform. I found that the adaptive planner was validating candidates on turns where a nudge could not fire, and block sync rebuilt per-message membership arrays on every transform.

This follow-up:

  • skips candidate planning when the nudge gate is closed;
  • uses a Map for prepared candidate-plan lookup instead of repeated linear searches;
  • repairs persisted message/block membership once, then avoids rebuilding it until the active block set changes;
  • preserves existing tool-cache ordering because its turn numbering depends on step-start parts;
  • adds regression coverage for idle planning, normal and malformed persisted membership state, and unchanged active-block membership.

Validation: independent full suite 1049 passed, production build passed, and all 13 E2E scenarios passed. Two independent reviews found and corrected the persisted-state/cache-order edge cases before this commit.

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 ework agent · qwen3.8-27b

增量审查:a04876e perf: skip idle candidate planning — 通过,无阻塞问题

本地独立验证(pr-341-review @ a04876e):typecheck ✓ · npm test 1049/1049 ✓(与声明一致,1045+4 新增)· build ✓ · 触碰文件 prettier-clean(lib/state/utils.ts 的 prettier 告警在 master 上同样存在,是基线漂移非本 PR 引入)。Docker E2E 未在本环境运行——写此评论时该 SHA 的 CI check-runs 尚未生成,以 CI e2e job 结果为准。

逐项核查:

  1. inject.ts 延迟规划nudgeAllowed(inject.ts:317)= emergencyOverride || (shouldNudge && growthSinceBaseline >= growthFloor),正是"本轮可能发出 nudge"的准确门控。我核对了 nothingToCompress 的全部消费点(:392/:399/:403/:604):全部被 nudgeAllowedemergencyOverride 正确门控(emergency 路径必然使 nudgeAllowed=true,故 emergencyNoTargets 语义不变);空闲轮次从"报告无可压缩"变为"不报告",但无未门控消费者,baseline 重置逻辑(fix: preserve growth baseline when nothingToCompress #207 区域)未触碰。acp_status 不受影响——status.ts 按需独立规划,本提交未改动。✓
  2. sync.ts 成员关系快路径 — 关键点 membershipsVerified 确实是瞬态的:不在 PersistedPruneMessagesState 序列化形状里,loadPruneMessagesState(lib/state/utils.ts:115)从 createPruneMessagesState()(false)起步 → 每次重启后必然执行一次修复同步。✓ 快路径健全性不变量成立:我 grep 了全部 entry.allBlockIds 写入点(lib/compress/state.ts:257/265/282、lib/gc/merge.ts:171、sync.ts 自身),每一处都伴随 active block 集合变化(新块创建即 active、去活移除 id、blockId 单调唯一),所以 sameBlockIds 相等时逐消息数组必然一致。孤儿持久化状态(entry 引用不存在的块)由无块分支一次性清理,与改动前数据语义一致,无回归。✓
  3. candidates.ts Map 查找 — O(n²)→O(n),new Map(undefined) → 空 Map,安全。✓
  4. tool-cache 顺序 — diff 未触碰 tool-cache 文件;DESIGN.md 已记录保留顺序的原因(turn numbering 依赖 prune 会移除的 step-start parts)。✓

测试质量(§5.6):"no-nudge turns skip candidate planning" 用 Proxy 计数属性访问断言规划确实没跑(candidateMessagesAccesses === 0),而不是从状态推断——设计到位,名称与断言一致。sync 三个新测试用引用同一性(assert.strictEqual(entry.activeBlockIds, capturedRef))证明数组未被重建,孤儿用例走真实 loadPruneMessagesState 加载路径。✓ Devlog REQ/WORKLOG/DESIGN 已同步更新,验收标准新增两条并勾选。✓

遗留提醒(均非本次引入):

  • 上轮 review 的 minor finding Add CI workflow for testing and building #2buildDrafts 保护单元 flush 缺少邻接检查 → 跨 MICRO 单元合并的 episode 被整体判为 executor-selection-drift 丢弃,安全方向的召回缺口)仍然开放,本提交未涉及。
  • §5.3 要求至少 2 个独立 agent 审查:目前为本 agent 的两轮审查(原始提交 + 本跟进),建议再安排一个独立 agent 过一遍完整 diff 后由人工 merge。

@drexb-ops

Copy link
Copy Markdown
Author

Final update: I merged current master (v1.14.27) into this branch, resolved the overlap in range execution by retaining the new provider/model-effective config while continuing to use the shared executor-parity range planner, and pushed merge commit 34473c3.

I also finished the post-compression performance follow-up:

  • candidate planning now skips turns where the nudge gate is closed;
  • block membership indexes repair once after loading and are not rebuilt unless active blocks change;
  • candidate prepared-plan lookup is O(D) rather than repeated linear search;
  • candidate selection is evaluated before later tool-output truncation, with the synthetic nudge suffix excluded from the real three-message truncation protection window.

The final regression proves an old large tool output appears as MICRO m00002–m00002 before truncation, is then truncated, and a large tool output in the third-last real message remains protected.

Final local validation: npm test 1098/1098 passed, npm run build passed, git diff --check passed, and all 13 E2E scenarios passed. The updated GitHub checks are now running; I will leave merging to the maintainer.

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 ework agent · qwen3.8-27b

Reviewed the merge commit 34473c3 (parents verified: a04876e + master 9b7adfd v1.14.27). LGTM — no blockers.

What I verified

  1. Conflict resolution (range execution). lib/compress/range.ts keeps both sides correctly: the feat: per-model growth-nudge floor (modelMinNudgeLimits) for mixed-context installs #344 provider/model cascade (getModelInfo(rawMessages)applyCompressOverrides) is applied after prepareSession (it needs the raw messages) and before prepareExecutableRangePlans, and injectCompressNudges applies the same swap at its top — so the planner and the executor read the same effective config. Executor parity survives the merge intact.
  2. Truncation reordering. Mechanically confirmed in lib/messages/truncate-tools.ts: PROTECT_RECENT_MESSAGES = 3, protectedIndex = messages.length - 3 computed on the passed array, parts mutated in place. Passing output.messages.filter(!isSyntheticMessage) therefore correctly removes the nudge suffix from both the truncation target set and the protection-window arithmetic (without it, the suffix would consume one slot and de-protect the third-last real message). This also fixes a latent parity bug: candidateMessages is a shallow slice sharing object references with output.messages, so under the old order the in-place truncation shrank tool outputs before planning → char accounting diverged from what compress later fetches fresh. Planning now sees pre-truncation sizes, matching the executor.
  3. sync.ts changed-flag + orphan repair. The no-blocks branch now clears stale activeBlockIds/activeByAnchorMessageId (persisted orphans), and the strengthened test asserts the cleared shape through serializePruneMessagesState. Consistent with the previously verified invariant that every allBlockIds writer coincides with an active-set change.
  4. "Final regression" is committed, not ad-hoc: tests/e2e-message-transform.test.ts:267 "candidate planning uses pre-truncation tool output" — asserts the nudge advertises MICRO m00002–m00002, the old output gets [truncated for context space, and the third-last real message's output does not. It pins both the ordering and the synthetic-suffix exclusion (with a 6-message fixture, removing the filter shifts protectedIndex and fails the assertion).
  5. Local validation on 34473c3: typecheck ✓ · 1098/1098 tests ✓ (matches your claim) · build ✓ (dist 446 KB) · git diff --check clean.
  6. GitHub checks: pr-validation ✓, test (22) ✓, test (24) ✓, build still running at time of writing; build-artifact ✗ remains the known fork-PR NPM_TOKEN infra gap flagged in round 1 (not a required check).

Minor notes (non-blocking)

  1. range.ts:108 — the summary-length validation reads ctx0.config.compress.maxSummaryLengthHard (global, pre-swap), but OVERRIDE_BLANKET_APPLY_EXCLUDE contains only maxContextLimit, so maxSummaryLengthHard is per-model overridable. With a per-model override set, early validation uses the global value while downstream enforcement uses the effective one — a summary between the two values either gets a confusing late rejection or the configured headroom silently goes unused. Suggest re-validating after the swap (or documenting the precedence).
  2. Round-1 finding Add CI workflow for testing and building #2 (buildDrafts protected-unit flush merges non-contiguous residuals into one episode → dropped as executor-selection-drift) is still open — unchanged by this merge. Safe direction, recall gap only.
  3. §5.3 dual-agent requirement: this agent has done three rounds; one more independent reviewer is still advisable before the human merge.

Nice work on the truncation-parity catch — that was the subtlest interaction in the merge.

@ranxianglei

Copy link
Copy Markdown
Owner

ework帮忙看看为啥ci失败,另外看看这个有对应的issue追踪吗?如果没有搞一个

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 ework agent · qwen3.8-27b

查完了,CI 失败原因明确:

根因:fork PR 拿不到 NPM_TOKEN secret

  • pr-artifact.yml 的 "Publish to npm with PR tag" 步骤用 NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 发 npm。GitHub 对 fork 仓库发起的 pull_request 事件不传递仓库 secrets,所以这个 PR(来自 drexb-ops/opencode-acp fork)里 token 是空的 → npm publishENEEDAUTH
  • 失败日志原文(job 100839297387):
    Publishing version 1.14.27-pr.341.98 with tag pr-341
    npm error code ENEEDAUTH
    npm error need auth This command requires you to be logged in to https://registry.npmjs.org/
    
  • 对照历史:pr-artifact.yml 所有 head repo 为本仓库的 run 全部 success,唯一失败的正是这个 fork PR——模式完全吻合。
  • 连带影响:publish 步骤失败导致后面的 tarball 上传和 "📦 Built Plugin Artifact" 安装说明评论也都没跑;红勾把 PR 的 mergeable_state 变成 unstable(只是噪音,required gate 是 pr-validation)。

没有现成的追踪 issue(按 fork / NPM_TOKEN / build-artifact 搜过 open+closed),已新建:

👉 #366 fix(ci): build-artifact fails on fork PRs — NPM_TOKEN secret unavailable to pull_request from forks

里面附了证据、影响面和修复建议:给 publish 步骤加 guard(if: github.event.pull_request.head.repo.full_name == github.repository),fork PR 跳过 npm tag 但仍产出 tarball artifact 和安装说明(Option B/C 不依赖 publish)。修好后本 PR 的 CI 就全绿了(merge 本身不受影响,随时可以合)。

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.

2 participants