Skip to content

Auto-compact cannot fire mid-turn: the gate reads the last billed prompt, not live tokens — and Esc during a compact resumed the turn #6047

Description

@Hmbown

Filed after the fix, to record work that had no ticket. Code is on local main, not pushed: 11501c529 "fix(tui): auto-compact on live mid-turn tokens; Esc stops the turn".

Problem

A long turn could exhaust the context window without auto-compact ever firing, even well past the 80% threshold.

The check was already at the right seam — pre-next-request in crates/tui/src/core/engine/turn_loop.rs (~995), after tools have been appended, the same place GrokBuild calls check_auto_compact_needed and Codex runs CompactionPhase::MidTurn. The signal was wrong.

The gate used max(last billed prompt, /4 estimate of the whole list). After a prompt billed at ~70%, tool results land in the transcript but not in that bill. Whenever the crude estimator undercounted the full list below the last bill, max() returned the stale billed prompt and every new tool result was invisible to the gate. Context then ran out at the next sample.

GrokBuild's formula is exact prior count plus a byte-estimate of the items appended since the last response; Codex additionally compacts-and-retries on ContextWindowExceeded.

Note: this is not a mid-tool-call compaction. The watermark is taken at add_parent_usage, before this step's assistant/tool messages are appended; the pass still runs at the pre-request boundary after tools complete.

Fix (a) — live token signal

Change Why this seam
core/turn.rs: note_parent_prompt_len + live_input_tokens_for_compaction Live tokens = billed prompt + suffix growth
core/engine/turn_loop.rs, after add_parent_usage Watermark is the billed prompt's message count
core/engine/turn_loop.rs compact gate Passes live tokens, not the stale billed figure

Fix (b) — interrupt semantics

Esc during a compact used to call only try_cancel_compaction and continue, so the turn loop's compaction_cancel arm fired and the turn resumed. Codex and GrokBuild both treat an interrupt as stopping the turn, with the compact as collateral.

Change Why this seam
tui/ui/compaction_flow.rs: compact_interrupt_should_stop_turn Decision table: compact serving a turn → stop the turn
tui/ui.rs: escape_cancel_request The Esc path
tui/ui/event_loop.rs: Ctrl+C CancelTurn Same for Ctrl+C

Compact-only (a manual /compact with no in-flight turn) still cancels just the pass.

Evidence actually run

  • live_compaction_tokens_include_tool_results_after_the_billed_prompt — 1 passed
  • compact_interrupt_stops_the_turn_not_just_the_pass — 1 passed
  • cargo fmt --all -- --check — pass
  • cargo clippy -p codewhale-tui --all-targets --all-features --locked -D warnings — pass

Not verified

  • No run against a live long turn. The token formula is unit-tested; the engine path end-to-end is not.
  • The full codewhale-tui lib suite (~12k tests) has not been run.
  • Commit is local-only, not pushed.
  • Codex's compact-and-retry on ContextWindowExceeded was not adopted — the gate now sees live tokens, but there is still no recovery if a single request overshoots anyway. Worth a follow-up decision.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    • Status
      Backlog

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions