Skip to content

fix(compaction): add mid-turn in-flight compression safety valve - #42898

Closed
JimStenstrom wants to merge 1 commit into
NousResearch:mainfrom
JimStenstrom:fix/inflight-compaction
Closed

fix(compaction): add mid-turn in-flight compression safety valve#42898
JimStenstrom wants to merge 1 commit into
NousResearch:mainfrom
JimStenstrom:fix/inflight-compaction

Conversation

@JimStenstrom

Copy link
Copy Markdown
Contributor

What does this PR do?

Proactive context compression only runs once per turn, at preflight before the tool-calling loop (agent/turn_context.py). A single long autonomous turn that emits many/large tool calls can therefore grow context unbounded and overflow the model's window mid-turn, before the next turn's preflight ever runs — today only the reactive post-error path and the Ollama hard-abort catch it. Observed on a local model: a 6-tool-call turn reached 2.6× the preflight threshold with zero compaction.

This adds _maybe_inflight_compress(), called at the top of the tool-calling loop, which re-checks compression but only once the request nears the model's real context window — an emergency fraction (HERMES_INFLIGHT_COMPRESS_FRACTION, default 0.85, well above the 50% preflight threshold). Normal turns never cross it and keep their prompt-cache prefix intact; the one-time mid-turn cache bust is paid only when a turn would otherwise overflow. It mirrors preflight's post-compression resets and clears the caller's history ref so the session-DB flush writes the compacted messages. It complements (does not replace) the preflight and reactive-on-error paths.

The 0.85 default lines up with the maintainer's own choice in #40957 (raised the compaction trigger to 85% for gpt-5.5 on Codex OAuth).

Related Issue

Refs #36624 — this owns one sub-symptom of that P1 ("auto compression can exhaust context in tool-heavy sessions"): the proactive trigger cadence (compaction is never re-evaluated between preflight and overflow). It is complementary, not a duplicate, to the other open PRs on #36624, which address a different facet — compaction effectiveness and graceful exhaustion:

Even with both merged, nothing triggers compaction in time mid-turn; this PR adds that missing proactive valve. Using Refs (not Closes) intentionally — the broader #36624 scope (tail demotion, explicit exhaustion) stays open.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/conversation_loop.py — add _maybe_inflight_compress() and call it at the top of the tool-calling loop. Guarded: only after the first iteration, only when compression is enabled and a compressor exists, only once usage crosses the emergency fraction, honoring the compressor's anti-thrash gate and protect_first_n / protect_last_n boundaries. Prefers the real provider prompt-token count from the prior response, falling back to a rough estimate.
  • tests/test_inflight_compression.py — 6 behavioral unit tests.

How to Test

  1. scripts/run_tests.sh tests/test_inflight_compression.py → 6 passed.
  2. Behavioral coverage: fires when usage ≥ the emergency fraction; skips on the first iteration, when below the emergency line (preflight's job), when compression is disabled, and when the anti-thrash gate blocks it; plus the HERMES_INFLIGHT_COMPRESS_FRACTION override path.
  3. Regression: ran the neighbouring compression suites (413/reactive, boundary, trigger, feasibility, persistence, concurrent-fork, compressor core) — 162 passed, 0 failed.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(compaction):)
  • I searched for existing PRs to make sure this isn't a duplicate (it's a non-overlapping sub-symptom of Auto compression can exhaust context in tool-heavy sessions #36624 — see above)
  • My PR contains only changes related to this fix (no unrelated commits)
  • I've run the tests and all pass — via scripts/run_tests.sh (the CI-parity hermetic wrapper)
  • I've added tests for my changes (required for bug fixes)
  • I've tested on my platform: macOS 15 (Darwin 25.5)

Documentation & Housekeeping

  • Documentation — N/A (internal safety valve; the HERMES_INFLIGHT_COMPRESS_FRACTION knob is documented in the commit body)
  • cli-config.yaml.example — N/A (env-var controlled, not a config key)
  • CONTRIBUTING.md / AGENTS.md — N/A (no architecture/workflow change)
  • Cross-platform impact — considered; pure-Python, no platform-specific calls
  • Tool descriptions/schemas — N/A

Screenshots / Logs

N/A — logs/perf-safe change. Diff is additive: +246 / −0 across 2 files (1 prod, 1 test).

Proactive context compression runs only once per turn, in the preflight
before the tool-calling loop, so a single long autonomous turn that makes
many tool calls can grow context unbounded and overflow the model's window
mid-turn — today only a reactive post-error path and the Ollama hard-abort
catch it. Observed on a local model: a 6-tool-call turn reached 2.6x the
preflight threshold with zero compaction.

Add _maybe_inflight_compress(), called at the top of the tool-calling loop,
which re-checks compression but only once the request nears the model's real
context window — an emergency fraction (HERMES_INFLIGHT_COMPRESS_FRACTION,
default 0.85), well above the 50% preflight threshold. Normal turns stay
below it and keep their prompt-cache prefix; the one-time mid-turn cache bust
is paid only when a turn would otherwise overflow. Mirrors the preflight's
post-compression resets and clears the caller's history ref so the session-DB
flush writes the compacted messages. Complements (not replaces) the preflight
and reactive-on-error compaction paths.

Refs NousResearch#36624
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jun 9, 2026
@liuhao1024

Copy link
Copy Markdown
Contributor

Code Review — clean ✅

Reviewed the full diff (conversation_loop.py + tests).

The safety valve design is well-thought-out:

  • Only fires at the emergency fraction (default 85% of context window), well above the 50% preflight threshold — preserves prompt-cache prefix for normal turns.
  • Skips on first iteration (api_call_count <= 1): preflight already ran, no mid-turn drift yet.
  • should_compress() anti-thrash gate prevents repeat compaction when the compressor itself backed off.
  • Up to 3 compression passes with real token re-estimation between each — converges below the emergency line.
  • Post-compression resets (_empty_content_retries, _thinking_prefill_retries, etc.) mirror the preflight path so the model gets a clean budget.
  • conversation_history = None after compaction ensures session-DB flush writes the compacted messages.
  • HERMES_INFLIGHT_COMPRESS_FRACTION env override with clamped [0.5, 0.98] range.

One note: the estimate_request_tokens_rough() fallback (when last_prompt_tokens is 0) is used for the initial threshold check. This is acceptable because the real token count from compressor.update_from_response will be available after the first API call in the loop, and the api_call_count > 1 guard already ensures we only fire after at least one real call.

Tests cover: fire-at-limit, skip-first-iteration, skip-below-emergency, skip-disabled, anti-thrash-blocks, env-override. Good coverage.

No issues found. 🚀

@JimStenstrom

Copy link
Copy Markdown
Contributor Author

Closing as redundant. Re-reviewing against current main, the proactive mid-turn gap this valve targeted is already covered by the per-iteration post-response should_compress check in agent/conversation_loop.py (the _real_tokens/last_prompt_tokens path, on main since 2026-05-16), which re-evaluates compression after every tool-calling iteration using the real reported prompt count. The valve fires on the same last_prompt_tokens signal at a higher (0.85) emergency fraction, so it's dominated by the existing 50%-threshold check rather than adding a distinct trigger. Closing to keep #36624 uncluttered; the underlying umbrella issue remains open for the genuinely-uncovered sub-symptoms (#36626, #38723).

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

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants