fix(compression): add hard message-count safety valve to TUI/CLI preflight path - #56034
Conversation
8de8e88 to
870f495
Compare
6c260cd to
0e36e0c
Compare
|
Rebased onto current Post-rebase verification:
The rebased delta remains one focused commit and does not duplicate the newer configurable tail-floor or durable rotation-lock work. |
teknium1
left a comment
There was a problem hiding this comment.
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:486in the PR diff). Currentagent/context_compressor.py:1288-1315rejects 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-1018independently 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.
|
Follow-up CI compatibility fix pushed in |
56f8f94 to
a5e1e3f
Compare
4700178 to
52393d7
Compare
b2d4dfb to
c706e9c
Compare
ab498b2 to
5e4dfcd
Compare
5e4dfcd to
13ab864
Compare
…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.
13ab864 to
a882fea
Compare
|
Rebased onto current
5 tests ported (force semantics + ctor wiring); 157/157 in test_context_compressor locally. |
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'sprompt_tokenswere 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
should_defer_preflight_to_real_usage()defers compression (last successful API call was below threshold, growth within 5% tolerance)last_real_prompt_tokensexceeds threshold →should_deferreturnsFalseshould_compress()should fire, but anti-thrashing (_ineffective_compression_count >= 2) can block it if prior compressions were ineffectivelast_prompt_tokensstays stale → compression never triggersWhat this PR does
ContextCompressor.__init__hygiene_hard_message_limitparam (default 0 = disabled)agent_init.pycompression.hygiene_hard_message_limitfrom config, passes to constructorturn_context.pypreflightshould_compress()forceparam — bypasses anti-thrashing when hard limit triggeredHow it works
The existing
compression.hygiene_hard_message_limitconfig key (default 5000 inDEFAULT_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)Relationship to prior work
gateway/run.py(Layer 3). This PR extends the same concept to the TUI/CLI path.should_defer_preflight_to_real_usage()to prevent double-compression after a fresh compress. This PR adds an escape hatch when the session has grown too large.Files changed
agent/context_compressor.py— +15/-2 (hygiene_hard_message_limitfield +forceparam)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
TestShouldCompress(+2 force tests)TestHygieneHardMessageLimit(+3 new)TestPreflightDeferral(existing)TestUpdateFromResponse(existing)py_compileall 3 source filestest_context_compressor.pyNo existing tests modified — all additions are new test methods and a new test class.