fix: prevent context pressure warning spam after compression (#4012)

* feat: add /yolo slash command to toggle dangerous command approvals

Adds a /yolo command that toggles HERMES_YOLO_MODE at runtime, skipping
all dangerous command approval prompts for the current session. Works in
both CLI and gateway (Telegram, Discord, etc.).

- /yolo -> ON: all commands auto-approved, no confirmation prompts
- /yolo -> OFF: normal approval flow restored

The --yolo CLI flag already existed for launch-time opt-in. This adds
the ability to toggle mid-session without restarting.

Session-scoped — resets when the process ends. Uses the existing
HERMES_YOLO_MODE env var that check_all_command_guards() already
respects.

* fix: prevent context pressure warning spam (agent loop + gateway rate-limit)

Two complementary fixes for repeated context pressure warnings spamming
gateway users (Telegram, Discord, etc.):

1. Agent-level loop fix (run_agent.py):
   After compression, only reset _context_pressure_warned if the
   post-compression estimate is actually below the 85% warning level.
   Previously the flag was unconditionally reset, causing the warning
   to re-fire every loop iteration when compression couldn't reduce
   below 85% of the threshold (e.g. very low threshold like 15%,
   or system prompt alone exceeds the warning level).

2. Gateway-level rate-limit (gateway/run.py, salvaged from PR #3786):
   Per-chat_id cooldown of 1 hour on compression warning messages.
   Both warning paths ('still large after compression' and 'compression
   failed') are gated. Defense-in-depth — even if the agent-level fix
   has edge cases, users won't see more than one warning per hour.

Co-authored-by: dlkakbs <dlkakbs@users.noreply.github.com>

---------

Co-authored-by: dlkakbs <dlkakbs@users.noreply.github.com>
This commit is contained in:
Teknium
2026-03-30 13:18:21 -07:00
committed by GitHub
parent ed9af6e589
commit 7dac75f2ae
3 changed files with 72 additions and 7 deletions

View File

@@ -212,6 +212,49 @@ class TestSessionHygieneWarnThreshold:
assert post_compress_tokens < warn_threshold
class TestCompressionWarnRateLimit:
"""Compression warning messages must be rate-limited per chat_id."""
def _make_runner(self):
from unittest.mock import MagicMock, patch
with patch("gateway.run.load_gateway_config"), \
patch("gateway.run.SessionStore"), \
patch("gateway.run.DeliveryRouter"):
from gateway.run import GatewayRunner
runner = GatewayRunner.__new__(GatewayRunner)
runner._compression_warn_sent = {}
runner._compression_warn_cooldown = 3600
return runner
def test_first_warn_is_sent(self):
runner = self._make_runner()
now = 1_000_000.0
last = runner._compression_warn_sent.get("chat:1", 0)
assert now - last >= runner._compression_warn_cooldown
def test_second_warn_suppressed_within_cooldown(self):
runner = self._make_runner()
now = 1_000_000.0
runner._compression_warn_sent["chat:1"] = now - 60 # 1 minute ago
last = runner._compression_warn_sent.get("chat:1", 0)
assert now - last < runner._compression_warn_cooldown
def test_warn_allowed_after_cooldown(self):
runner = self._make_runner()
now = 1_000_000.0
runner._compression_warn_sent["chat:1"] = now - 3601 # just past cooldown
last = runner._compression_warn_sent.get("chat:1", 0)
assert now - last >= runner._compression_warn_cooldown
def test_rate_limit_is_per_chat(self):
"""Rate-limiting one chat must not suppress warnings for another."""
runner = self._make_runner()
now = 1_000_000.0
runner._compression_warn_sent["chat:1"] = now - 60 # suppressed
last_other = runner._compression_warn_sent.get("chat:2", 0)
assert now - last_other >= runner._compression_warn_cooldown
class TestEstimatedTokenThreshold:
"""Verify that hygiene thresholds are always below the model's context
limit — for both actual and estimated token counts.