Repository navigation
fix: Claude 5.x compaction lifecycle (replay gate, per-iteration cost) with end-to-end tests - #4688
Merged
Merged
Conversation
The replay check only compared the provider name, which the Claude API and Vertex clients share. After a native compaction on the Claude API, a client without native_compaction (Vertex, which rejects the option, or a Claude API model without it) sent the signed block and the compaction beta. Only a client configured with native_compaction now replays the block; others send the summary text every summary message carries. Signed-off-by: Arnaud Héritier <aheritier@apache.org>
Native compaction summed every iteration's usage and priced the total, so a context tier was chosen on the sum: two 60k iterations of Haiku 5.5 were priced as one 120k request at the higher rate. Keep each iteration and price it on its own; an unpriced iteration leaves the compaction unpriced. Signed-off-by: Arnaud Héritier <aheritier@apache.org>
Signed-off-by: Arnaud Héritier <aheritier@apache.org>
A thinking turn kept by threshold compaction is replayed with its signature, after the summary and under the drop_block binding, once the session is reloaded from SQLite. Signed-off-by: Arnaud Héritier <aheritier@apache.org>
The signed compaction block and its per-iteration usage are stored with the summary; after a reload the next request replays the block verbatim with the compaction beta. Signed-off-by: Arnaud Héritier <aheritier@apache.org>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
No blocking correctness issues remain; validation results were reported but not independently rerun.
1 open issue
What changed in this PR
Fixes native compaction replay and billing for Claude clients, with lifecycle coverage for compaction, SQLite reload, and continuation.
Changes:
- Gate signed-block replay on
native_compaction. - Preserve and price compaction usage per iteration.
- Add provider regression tests and runtime lifecycle tests.
| File | Description |
|---|---|
| pkg/runtime/native_compaction.go | Prices iterations separately. |
| pkg/runtime/native_compaction_test.go | Tests tiered and unpriced costs. |
| pkg/runtime/claude5_compaction_lifecycle_test.go | Tests compaction, reload, and continuation. |
| pkg/model/provider/anthropic/vertex/vertex_test.go | Tests summary fallback on Vertex. |
| pkg/model/provider/anthropic/features_integration_test.go | Enables compaction in replay coverage. |
| pkg/model/provider/anthropic/compaction.go | Gates replay and captures iterations. |
| pkg/model/provider/anthropic/compaction_test.go | Tests replay gating and iteration usage. |
| pkg/model/provider/anthropic/client.go | Uses client-specific replay eligibility. |
| pkg/model/provider/anthropic/beta_converter.go | Applies the replay gate during conversion. |
| pkg/model/provider/anthropic/beta_client.go | Gates the compaction beta header. |
| pkg/model/provider/anthropic/anthropictest/server.go | Adds scripted responses and request capture. |
| pkg/model/provider/anthropic/anthropictest/server_test.go | Tests the scripted server. |
| pkg/chat/compaction.go | Stores and clones iteration usage. |
| docs/providers/anthropic/index.md | Clarifies replay eligibility. |
Review effort: Balanced
With replay gated on native_compaction, the edited-summary and foreign-provider tests passed on a client without the option whatever ReplayableCompaction did. Enable the option so they check it again. Signed-off-by: Arnaud Héritier <aheritier@apache.org>
aheritier
enabled auto-merge
October 21, 2026 12:11
trungutt
approved these changes
Oct 21, 2026
This was referenced Oct 21, 2026
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.

Part of #4654 (PR 2 of 3). Part of #4583 and epic #4580.
What
Two compaction fixes, then two end-to-end tests that pin the whole compaction lifecycle on Claude 5.x: compaction, a SQLite reload, and the next request.
Fixes
fix(anthropic): replay native compaction only where it is enabledanthropic). After a native compaction on the Claude API, a client withoutnative_compaction(Vertex, or a Claude API model without the option) still sent the signed block and thecompact-2026-09-04beta.provider_opts.native_compactionis set. Otherwise it sends the summary text that every summary message already carries.fix(runtime): price native compaction iterations separatelychat.CompactionResult.Iterationskeeps each reported iteration. Each one is priced on its own and the costs are summed. If any iteration can't be priced, the cost is nil, as for any other unpriced call.Tests
TestCompaction_NotReplayedWithoutNativeCompaction,TestVertexDoesNotReplayCompactionBlockTestNativeCompactionPricesEachIteration,TestCompactionCostUnpricedIterationTestClaude5LocalCompactionLifecycle(pkg/runtime)drop_blockbindingFirstKeptEntryas 0; dropping bothProviderStateandThinkingSignatureon loadTestClaude5NativeCompactionLifecycle(pkg/runtime)native_compactiongate offBoth end-to-end tests use a real Sonnet 5.5 client built by the provider registry, a small scripted SSE server (
anthropictest, ~150 lines), and the SQLite store. Each takes ~0.3s under-race.Not added, and why
Notes
ProviderStatealone doesn't break the local lifecycle test: the Anthropic converter rebuilds the signed thinking block fromReasoningContent+ThinkingSignature. Both have to be lost for the signature to be lost.native_compactionon Vertex; lifting that is out of scope here.References
compact-2026-09-04beta header ("Send thecompact-2026-09-04beta header … on every later request that carries the signed block"); without it, a 400 sayscompactionis not one of the expected content block typessignature, and send it first on the next requestusage.iterationsreports it … sum acrossusage.iterations, not the top-level fields"signature;thinking.block_binding.prefix_mismatch_behavior: "drop_block"drops it, rather than failing, when the prefix changedValidation
task buildpasses.task testpasses.golangci-lintv2.14.0 reports 0 issues, andgo run ./lint .reports no offenses.go test -race -shuffle=on ./pkg/runtime/ ./pkg/model/provider/anthropic/... ./pkg/chat/passes.Deferred minor findings:
native_compactionoption name is written as a literal in the Anthropic and Vertex packages and as a constant in the runtime.newScriptedServer,textReply,compactionReply) could move ontoanthropictest.