fix(compaction): tail sizing reads cumulative step messages — compaction never fires in production (#176)#197
Conversation
…action never fired (#176) ai@6 exposes step.response.messages as the CUMULATIVE response list up to that step, not a per-step delta (verified against a live run: per-step lengths [2,4,6]). buildCompactionStep summed those arrays over the last keepLastSteps steps, so for any conversation with >=2 steps in the window tailCount >= messages.length, splitAt pinned to 0, older was empty, and summarize-and-continue compaction (#100's Wave-1 P0, wired in #102) passed through at EVERY step in production. - Compute the last-K-steps count as cumulative(last) - cumulative(last-keepLastSteps). - Guard steps.length === 0 (first prepareStep of a run or a #107 continuation): no boundary to cut and the continuation's live tail must not be summarized away. - Tests: the above-threshold fake now carries cumulative arrays [2,4,6,8] (SDK shape) — the pre-fix sum-based math pinned splitAt to 0 and this test is the regression proof; plus a continuation-edge test (steps=[] over threshold → pass-through, summarizer never runs). Part of #196. Shares its root cause with #175 (transcript duplication).
📝 WalkthroughWalkthroughCompaction now interprets step response messages as cumulative, calculates the retained tail using cumulative differences, and skips compaction when no completed steps exist. Tests update their fixtures and add coverage for continuation and summarizer failure behavior. ChangesCompaction boundary correction
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/aitm/src/compaction/compaction-step.ts (1)
70-74: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winGuard placement forces a wasted compactor lookup on every continuation step.
The
steps.length === 0check is correct, but it sits after theawait init.compactor.shouldCompact(...)call and theestimateTokens(messages)computation — both of whose results are discarded whenever this guard fires (every first-step /priorHandlecontinuation call). Moving the guard right after themessages.length === 0check avoids the unnecessary token estimation and compactor round-trip on a path that's guaranteed to pass through anyway.♻️ Proposed reorder
if (messages.length === 0) return undefined; + // No completed steps yet — the first prepareStep of a run, or of a `#107` `priorHandle` + // continuation. There is no step boundary to cut, and a continuation's live tail must not be + // summarized away (cumulative math below would treat the whole injected history as `older`). + // Pass through; compaction resumes once a step has run. + if (steps.length === 0) return undefined; + // Size off the LIVE `messages` ... const liveInputTokens = estimateTokens(messages); let decision: Awaited<ReturnType<Compactor['shouldCompact']>>; try { decision = await init.compactor.shouldCompact(init.modelId, liveInputTokens); } catch (err) { ... } if (decision.kind === 'skip') return undefined; - // No completed steps yet — the first prepareStep of a run, or of a `#107` `priorHandle` - // continuation. There is no step boundary to cut, and a continuation's live tail must not be - // summarized away (cumulative math below would treat the whole injected history as `older`). - // Pass through; compaction resumes once a step has run. - if (steps.length === 0) return undefined; - // Cut at a step boundary...🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/aitm/src/compaction/compaction-step.ts` around lines 70 - 74, Move the existing steps.length === 0 early return in the compaction step flow to immediately after the messages.length === 0 check, before estimateTokens(messages) and init.compactor.shouldCompact(...). Preserve the current undefined pass-through behavior for first-step and priorHandle continuation calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/aitm/src/compaction/compaction-step.ts`:
- Around line 70-74: Move the existing steps.length === 0 early return in the
compaction step flow to immediately after the messages.length === 0 check,
before estimateTokens(messages) and init.compactor.shouldCompact(...). Preserve
the current undefined pass-through behavior for first-step and priorHandle
continuation calls.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 36676f98-d3ed-4b80-b891-869ee6d30e81
📒 Files selected for processing (2)
packages/aitm/src/compaction/compaction-step.test.tspackages/aitm/src/compaction/compaction-step.ts
Closes #176. First of the two P0s from the post-#100 parity verification (#196).
Root cause (verified)
ai@6.0.182exposesstep.response.messagesas the cumulative response-message list up to that step, not a per-step delta. Confirmed against a live run's transcript (sum/working-2.jsonl): per-step lengths [2, 4, 6], record 0 a strict prefix of record 1.buildCompactionStepsized the kept tail by summing those arrays over the lastkeepLastStepssteps:For any conversation with ≥ 2 steps in the window,
tailCount >= messages.length→splitAt === 0→olderempty → pass-through at every step. So summarize-and-continue compaction (#100's Wave-1 P0, wired in #102) never fired in production; long worker/reviewer/CI-fix conversations still died at the context window.Fix
cumulative(last) − cumulative(last − keepLastSteps)(delta over cumulative arrays), so the step-boundary cut is preserved but the count is correct.steps.length === 0: the firstprepareStepof a run — or of a feat(compat): subagent continuation — resume a completed agent with retained messages #107priorHandlecontinuation — has no completed step to cut on, and a continuation's live tail must not be summarized away (the inverse over-compaction edge the issue calls out).Tests
[2,4,6,8](real SDK shape). The pre-fix sum-based math pinnedsplitAtto 0 and returnedundefined— this test is the regression proof that compaction fires.steps=[]over threshold → pass-through, summarizer never runs.test:node(aitm 903) + biome.Same root cause as #175 (transcript duplication) — fixed separately since it touches different files.
Part of #196.
Summary by CodeRabbit