perf: avoid transform work that scales with compression history (#384) - #385
Merged
Conversation
ranxianglei
force-pushed
the
2026-09-11_perf-transform-history
branch
from
September 11, 2026 02:06
68ac2ea to
4977cde
Compare
added 3 commits
September 11, 2026 10:11
- RC1: request-scoped boundary lookup memo in SearchContext (built once per compress call instead of once per candidate draft) - RC1b: resolveSelection token estimates now use chars/4 fast estimator instead of the Anthropic BPE tokenizer per message - RC2: nudge analysis (context composition / protected refs / compressible ranges) gated behind needsNudgeAnalysis so quiet turns skip it entirely - RC3: structureVersion-based invalidation for syncCompressionBlocks incremental path and hideConsumedCompressCalls derived-index cache - RC4: ordered, coalescing per-session save queue in persistence (latest-snapshot-wins bursts, FIFO across batches, failure isolation) + scripts/bench-candidate-planning.ts benchmark harness + tests: sync (+4), hide-consumed (+3), compress-search (+4), token-counting (+3), persistence (+4), inject (+2, multi-turn #5.7 cycle) + devlog 2026-09-11_perf-transform-history (REQ/WORKLOG/DESIGN) Candidate planning @1000 msgs: 13618ms -> 1.16ms (~11700x); target <=20ms met. Full suite: 1151 tests, 0 failures.
- commit devlog DESIGN.md (required by AGENTS.md §5.1.2, was untracked) - persistence.ts: correct save-queue comment — snapshot payloads are captured by reference at enqueue and serialized at write time; late serialization sees only strictly-newer values (inter-save mutations are additive/monotonic), which is safe - inject.ts: document that emergencyOverride is intentionally kept in the needsNudgeAnalysis gate despite being subsumed by nudgeAllowed - REQ.md: drop lib/state/rebuild.ts from affected modules (zero diff)
- persistence.ts: correct save-queue comment — snapshot payloads captured by reference at enqueue, serialized at write time; late serialization sees only strictly-newer values (inter-save mutations are additive/monotonic) - inject.ts: document that emergencyOverride is intentionally kept in the needsNudgeAnalysis gate despite being subsumed by nudgeAllowed - REQ.md: drop lib/state/rebuild.ts from affected modules (zero diff)
ranxianglei
force-pushed
the
2026-09-11_perf-transform-history
branch
from
September 11, 2026 02:13
4977cde to
428b256
Compare
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-385 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-09-11_perf-transform-history" --globalOption C — Download artifact
tar xzf opencode-acp-pr385.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #384 — per-transform work scaled with total compression history instead of staying bounded by visible context + active blocks. Candidate planning at 1,000 messages measured ~13.6 s on master; after this change it is ~1.2 ms.
Root causes & fixes (all verified in code before changing anything)
resolveBoundaryIds()rebuilt the global boundary lookup (O(entire ref history)) per candidate draftSearchContext.boundaryLookupbuilt once per compress call (buildSearchContext), lazy??=fallback keeps hand-built contexts workingresolveSelection()ran the real Anthropic BPE tokenizer per message (~27 ms/KB measured)estimateAllMessageTokensFast()= chars/4 (existing estimation convention); exact BPE kept where correctness requires itnudgeAllowed || emergencyOverride || tierTriggerPossible; all downstream consumers null-guardedsyncCompressionBlocks()replayed ALL blocks (active + inactive) every transform;hideConsumedCompressCalls()rebuilt immutable consumed-call indexes every transformstructureVersionbumped at exactly the 3 block-mutation sites (applyCompressionState,mergeMarkedBlocks,deactivateCompressionTarget— full mutation-site audit done); sync skips full replay when unchanged, hide-consumed caches its derived index keyed by versionsetImmediatedrain, batch writes only the latest snapshot, FIFO across batches, failure isolationBenchmark evidence
Same machine/harness (
scripts/bench-candidate-planning.ts, new — workload: N visible messages with realistic tool outputs, H≈N/2 historical blocks, newest 20 active, full un-reclaimed ref history), median of 7 reps. Baseline = pristine master run in a throwaway worktree same day.A = candidate planning (
buildSearchContext+resolveRanges× 10 drafts); B = steady-state sync; C = hide-consumed. Acceptance target ≤ 20 ms @ 1000 met with ~17× headroom. Absolute values differ from the issue's isolated probe (3.2/34/105 ms) because this harness models a denser workload; before/after are identical-harness comparisons.Compatibility
structureVersion,lastSyncedStructureVersion,hideConsumedIndex,boundaryLookup) are transient and excluded from serialization; first transform after load does one full replay (version defaults to 0) — identical behavior to pre-change code.Tests
npm run typecheckclean,npm run buildclean.shouldInjectThisTurnAND baseline bookkeeping per turn + emergency-path preservation).Review
Dual-agent review completed (AGENTS.md §5.3/§5.6): both reviewers APPROVE-WITH-NITS, zero blocking findings; nits addressed in follow-up commit (DESIGN.md committed, comment accuracy, gate-redundancy documented).
中文摘要:修复了长会话中随压缩历史增长的每次 transform 开销(候选规划 1000 条消息从 13.6 s 降到 1.16 ms,达标 ≤20 ms),通过请求级索引复用、惰性 nudge 分析、结构版本号失效机制和有序合并的状态保存队列实现;持久化格式不变,1151 个测试全部通过,双代理审查无阻断问题,可以合并。