Found during PR #125's review (issue #124 fix: the compaction summarizer inherits the session's current effort level via `Session.Effort()` — see `runCompactionSummary`, `engine/compact.go`).
`compactionMaxTokens` (1024) documents itself as a deliberate cap on the summarization call's response. But at a non-off effort level:
- `provider/anthropic` raises `max_tokens` above the thinking budget (up to ~20480 at `EffortHigh`), so the effective cap is far above 1024.
- `provider/openai` (Responses) raises `MaxOutputTokens` to `reasoningOutputFloor` (up to 25000 at `EffortHigh`).
- `provider/openaicompat` applies reasoning_effort with no `max_tokens` floor at all — the request still caps output at 1024, so a reasoning-heavy model can spend most or all of that budget on reasoning tokens before emitting any summary text.
`runCompactionSummary` has no `StopReason` guard (only an empty-response check), so a response truncated at the token cap mid-summary is indistinguishable from a complete one and gets folded into history as-is via `spliceCompact` — silent data loss at the layer that exists specifically to prevent context overflow.
Suggested fix: either clamp the inherited effort level for this call so it can never push the response cap past `compactionMaxTokens`'s intent, or add a `StopReason` == max-tokens guard to `runCompactionSummary` that fails the compaction (same path as `TestCompactFailureNoJournalNoMutation`) instead of folding a truncated summary.
See the round-1 review on PR #125 for the full analysis (including the openai/anthropic transcode.go line references).
Found during PR #125's review (issue #124 fix: the compaction summarizer inherits the session's current effort level via `Session.Effort()` — see `runCompactionSummary`, `engine/compact.go`).
`compactionMaxTokens` (1024) documents itself as a deliberate cap on the summarization call's response. But at a non-off effort level:
`runCompactionSummary` has no `StopReason` guard (only an empty-response check), so a response truncated at the token cap mid-summary is indistinguishable from a complete one and gets folded into history as-is via `spliceCompact` — silent data loss at the layer that exists specifically to prevent context overflow.
Suggested fix: either clamp the inherited effort level for this call so it can never push the response cap past `compactionMaxTokens`'s intent, or add a `StopReason` == max-tokens guard to `runCompactionSummary` that fails the compaction (same path as `TestCompactFailureNoJournalNoMutation`) instead of folding a truncated summary.
See the round-1 review on PR #125 for the full analysis (including the openai/anthropic transcode.go line references).