a89f6aba44
Idle compaction (`compression.idle_compact_after_seconds`) skipped work only when the context was at or below `threshold_tokens × summary_target_ratio`. That is a *theoretical* post-compaction target: a real pass lands well above it because the system prompt, the tool schemas and the protected head/tail are an incompressible floor. So a session that compacted to well above the target stayed above it forever, and every later idle resume re-ran a full summarisation over a transcript that had not grown. In the reported session the 17:01 pass reduced 64,105 -> 44,579 tokens; the 17:36 resume re-fired because 44,579 was still above the 25,502 theoretical floor, blocking the prompt for another 256 s on a ~55 tok/s local route and reclaiming nothing. Neither pass was followed by a single API call. `_should_idle_compact` now also honours `last_compression_rough_tokens` — what the previous pass on this session actually produced, recorded by `compress_context` with the same `estimate_request_tokens_rough` shape the idle estimate uses. When it is known, the transcript must accumulate at least one `floor_tokens` worth of new content on top of it before another pass is worth its wall clock. A raised floor is a deferral, not an off switch. `0` — nothing compacted yet, or the counter cleared by a rebind/recalibration — keeps the original semantics exactly, so the first idle compaction of any session is unaffected. The call site type-pins the read so compressor doubles that expose a Mock there fall back to the original floor. Fixes a root cause behind #97239. Deliberately does not touch the TUI status surface (#97253), the digest chain / total ceiling (#93241), or turn-settle scheduling (#96891). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HV3k1v5nx9ag5d5wXSFZ7o