Skip to content

fix(compression): add hard message-count safety valve to TUI/CLI preflight path - #56034

Open
yingliang-zhang wants to merge 1 commit into
NousResearch:mainfrom
yingliang-zhang:fix/tui-hard-message-limit-safety-valve-v2
Open

fix(compression): add hard message-count safety valve to TUI/CLI preflight path#56034
yingliang-zhang wants to merge 1 commit into
NousResearch:mainfrom
yingliang-zhang:fix/tui-hard-message-limit-safety-valve-v2

Conversation

@yingliang-zhang

Copy link
Copy Markdown
Contributor

Summary

The gateway has had a hard message-count safety valve since #4750 (gateway/run.py), but the TUI/CLI preflight path (agent/turn_context.py) lacks an equivalent backstop.

When should_defer_preflight_to_real_usage() (#50762) keeps deferring compression because the last successful API call's prompt_tokens were below threshold, a TUI session can grow unbounded until the provider starts disconnecting — at which point no usage data is returned to update the compressor, creating an unrecoverable death spiral.

The death spiral

  1. Session grows past the compression threshold
  2. should_defer_preflight_to_real_usage() defers compression (last successful API call was below threshold, growth within 5% tolerance)
  3. Session continues growing — API still succeeds (model context > threshold)
  4. last_real_prompt_tokens exceeds threshold → should_defer returns False
  5. should_compress() should fire, but anti-thrashing (_ineffective_compression_count >= 2) can block it if prior compressions were ineffective
  6. Provider starts timing out / disconnecting → no usage data returned
  7. No usage → last_prompt_tokens stays stale → compression never triggers
  8. Every subsequent message repeats the cycle → session is unrecoverable

What this PR does

Layer Change
ContextCompressor.__init__ New hygiene_hard_message_limit param (default 0 = disabled)
agent_init.py Reads compression.hygiene_hard_message_limit from config, passes to constructor
turn_context.py preflight When message count ≥ hard limit, bypasses deferral, cooldown, and anti-thrashing
should_compress() New force param — bypasses anti-thrashing when hard limit triggered

How it works

The existing compression.hygiene_hard_message_limit config key (default 5000 in DEFAULT_CONFIG) was already used by the gateway hygiene layer. This PR extends it to the TUI/CLI preflight path — no new config key needed.

When the message count exceeds the hard limit:

  • should_defer_preflight_to_real_usage() is bypassed (no more deferring)
  • get_active_compression_failure_cooldown() is bypassed (no cooldown blocking)
  • should_compress(force=True) bypasses anti-thrashing (no more ineffective-compression blocking)
  • Compression fires immediately

Relationship to prior work

Files changed

  • agent/context_compressor.py — +15/-2 (hygiene_hard_message_limit field + force param)
  • agent/turn_context.py — +38/-11 (hard-limit check in preflight)
  • agent/agent_init.py — +9/-0 (config wiring)
  • tests/agent/test_context_compressor.py — +45/-0 (5 new tests)

Test plan

Result
TestShouldCompress (+2 force tests) 6 passed
TestHygieneHardMessageLimit (+3 new) 3 passed
TestPreflightDeferral (existing) 5 passed
TestUpdateFromResponse (existing) 2 passed
py_compile all 3 source files OK
Full file test_context_compressor.py 130 passed

No existing tests modified — all additions are new test methods and a new test class.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 1, 2026
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch from 8de8e88 to 870f495 Compare July 1, 2026 03:29
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch 3 times, most recently from 6c260cd to 0e36e0c Compare July 14, 2026 13:01
@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Rebased onto current main@444b5e96f and resolved the compression API conflicts while preserving the hard message-count preflight safety valve. New head: 0e36e0c43.

Post-rebase verification:

  • full tests/agent/test_context_compressor.py + tests/agent/test_turn_context.py: 174 passed
  • py_compile, Ruff, and git diff --check: PASS

The rebased delta remains one focused commit and does not duplicate the newer configurable tail-floor or durable rotation-lock work.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for carrying the gateway safety-valve concept into the interactive path. The current main premise is real: agent/turn_context.py:368-434 has no count-based escape hatch, while the existing config is documented gateway-only at website/docs/user-guide/configuration.md:758.

Problems

  • The new hard-limit branch still calls should_compress() (agent/turn_context.py:486 in the PR diff). Current agent/context_compressor.py:1288-1315 rejects both active summary-failure cooldowns and two ineffective compactions; the PR's added tests deliberately preserve those blocks. That does not recover the cooldown/anti-thrashing cases described in the PR.
  • agent/conversation_loop.py:1012-1018 independently mirrors the same defer/cooldown/should_compress() chain for in-turn pre-API pressure, but the PR does not add the count valve there.
  • The configuration docs still state this setting is gateway-only (website/docs/user-guide/configuration.md:758).

Suggested changes

  • Specify and test the intended bounded recovery semantics for a count breach, including the cooldown and anti-thrashing cases.
  • Cover the in-turn pre-API guard or explain its exclusion, and update the configuration docs.

Automated hermes-sweeper review.

Comment thread agent/turn_context.py Outdated
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Follow-up CI compatibility fix pushed in 56f8f941e: ordinary token-pressure calls no longer pass a new force=False keyword to legacy/strict _compress_context implementations; only hard-limit breaches attempt force=True, with an exact unexpected-keyword fallback. Verified with the full affected matrix: tests/run_agent/test_run_agent_codex_responses.py, tests/agent/test_turn_context.py, and tests/run_agent/test_413_compression.py136 passed, 0 failed; Ruff and git diff --check passed.

@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch from 56f8f94 to a5e1e3f Compare July 19, 2026 05:06
@teknium1 teknium1 added the area/compression Context compression and continuation sessions label Jul 19, 2026
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch 5 times, most recently from 4700178 to 52393d7 Compare July 26, 2026 13:09
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch 2 times, most recently from b2d4dfb to c706e9c Compare August 17, 2026 00:54
@yingliang-zhang
yingliang-zhang requested a review from a team August 17, 2026 00:54
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch 5 times, most recently from ab498b2 to 5e4dfcd Compare August 20, 2026 23:06
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch from 5e4dfcd to 13ab864 Compare August 28, 2026 05:16
…light path

Port of NousResearch#56034 onto post-NousResearch#102117 main. The refactor moved the preflight
decision chain into agent/turn_context_compaction.py
(_preflight_compression), so the valve now lives there; the compressor
gains the ctor attr plus a force flag threading through
should_compress / should_compress_info /
_automatic_compression_blocked[_locally] that bypasses the anti-thrash
breaker (orthogonal to the existing ignore_cooldown for the
summary-LLM cooldown).

Config note: the knob is compression.preflight_hard_message_limit
(default 0 = off), deliberately distinct from the gateway's
compression.hygiene_hard_message_limit (default 5000) — that key gates
the gateway's pre-agent hygiene pass and predates this change; reusing
it would have silently enabled the agent-side valve at 5000 for every
existing config.

When the session message count reaches the limit, preflight compression
is forced regardless of deferral (real-usage
should_defer_preflight_to_real_usage), summary-LLM cooldown, or the
anti-thrash breaker — breaking the death spiral where token-based checks
never fire because the last real usage reading predates the growth, until
the provider disconnects and the session becomes unrecoverable (NousResearch#2153 /
NousResearch#4750 parity for the TUI/CLI path).

Tests: 5 ported tests (force bypasses anti-thrash; force keeps the
threshold floor; ctor default/set/zero); 157/157 in test_context_compressor.
@yingliang-zhang
yingliang-zhang force-pushed the fix/tui-hard-message-limit-safety-valve-v2 branch from 13ab864 to a882fea Compare September 4, 2026 16:15
@yingliang-zhang

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (d3630f8532, post-#102117 refactor) — ported with two adaptation decisions:

  1. The preflight decision chain now lives in agent/turn_context_compaction.py (_preflight_compression), so the valve gates there; the compressor adds a force flag threaded through should_compress_automatic_compression_blocked that bypasses the anti-thrash breaker — orthogonal to the existing ignore_cooldown (summary-LLM cooldown), which main added since the original PR.
  2. Config key renamed to compression.preflight_hard_message_limit (default 0): main's gateway layer already owns compression.hygiene_hard_message_limit (default 5000) for its pre-agent hygiene pass — reusing that key would have silently enabled the agent-side valve at 5000 for every existing config, changing default TUI/CLI behavior. Distinct key = agent-side valve stays opt-in as the original PR intended.

5 tests ported (force semantics + ctor wiring); 157/157 in test_context_compressor locally.

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

Labels

area/compression Context compression and continuation sessions comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants