{"repo":"NousResearch/hermes-agent","public_id":"11529","display_label":"Context limit handling and compression freeze issues","summary":"These items relate to bugs and fixes around context length detection, exceeding context limits, and CLI freezes caused by compression behavior during LLM cooldown periods. Topics span both crash/overflow bugs and improvements to context-aware model resolution.","subsystem":"agent","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":55926,"merged_at":"2026-06-30T22:57:59+00:00"},"resolution":{"state":"resolved","by":"pr-55926"},"freshness":{"state":"verified","last_verified_at":"2026-06-30T22:57:59+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":11529,"pr_number":55926,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-30T22:57:59+00:00"}],"members":[{"kind":"issue","number":11529,"title":"[Bug]: Context bursting in long context session","url":null,"state":"closed"},{"kind":"pr","number":11559,"title":"fix(compressor): skip compression during summary LLM cooldown to prevent CLI freeze","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"812236bff","number":0},{"kind":"pr","value":"#55926","number":55926}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":55926,"title":"fix(compressor): skip compression during summary LLM cooldown to prevent CLI freeze","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/context_compressor.py","tests/run_agent/test_infinite_compaction_loop.py"],"fix_hunks":[{"file":"agent/context_compressor.py","hunks":["@@ -1073,6 +1073,23 @@ def should_compress(self, prompt_tokens: int = None) -> bool:\n         tokens = [redacted:secret-assignment] if prompt_tokens is not None else self.last_prompt_tokens\n         if tokens < self.threshold_tokens:\n             return False\n+        # Do not trigger compression while the summary LLM is in cooldown.\n+        # On a 429/transient failure _generate_summary() sets a cooldown and\n+        # returns None; compress() then inserts a static fallback marker and\n+        # returns. Tokens stay above threshold, so without this guard every\n+        # subsequent turn re-fires _compress_context() — re-inserting the\n+        # marker and re-entering the loop, making the CLI appear frozen until\n+        # the cooldown expires (issue #11529). Manual /compress passes\n+        # force=True, which clears this cooldown in compress() before running,\n+        # so it still retries immediately.\n+        _cooldown_remaining = self._summary_failure_cooldown_until - time.monotonic()\n+        if _cooldown_remaining > 0:\n+            if not self.quiet_mode:\n+                logger.debug(\n+                    \"Compression deferred — summary LLM in cooldown for %.0fs more\",\n+                    _cooldown_remaining,\n+                )\n+            return False\n         # Anti-thrashing: back off if recent compressions were ineffective\n         if self._ineffective_compression_count >= 2:\n             if not self.quiet_mode:"]},{"file":"tests/run_agent/test_infinite_compaction_loop.py","hunks":["@@ -16,6 +16,8 @@\n \n from unittest.mock import patch, MagicMock\n \n+import time\n+\n from agent.context_compressor import ContextCompressor, _CHARS_PER_TOKEN\n \n ","@@ -248,3 +250,36 @@ def test_below_threshold_allows(self):\n         comp = _make_compressor(config_context_length=96000)\n         comp.last_prompt_tokens = [redacted:secret-assignment]\n         assert not comp.should_compress(10_000)\n+\n+\n+# ---------------------------------------------------------------------------\n+# Test: summary-LLM cooldown guard in should_compress (#11529)\n+# ---------------------------------------------------------------------------\n+\n+class TestCooldownGuard:\n+    \"\"\"should_compress() must skip compression while the summary LLM is in\n+    cooldown, otherwise a 429/transient failure re-fires _compress_context()\n+    every turn (inserting a fallback marker repeatedly) and freezes the CLI.\n+    \"\"\"\n+\n+    def test_active_cooldown_blocks(self):\n+        \"\"\"A future cooldown deadline -> should_compress returns False even\n+        when tokens are over threshold.\"\"\"\n+        comp = _make_compressor(config_context_length=96000)\n+        comp.last_prompt_tokens = [redacted:secret-assignment]\n+        comp._summary_failure_cooldown_until = time.monotonic() + 60\n+        assert not comp.should_compress(65_000)\n+\n+    def test_expired_cooldown_allows(self):\n+        \"\"\"A past cooldown deadline -> compression resumes normally.\"\"\"\n+        comp = _make_compressor(config_context_length=96000)\n+        comp.last_prompt_tokens = [redacted:secret-assignment]\n+        comp._summary_failure_cooldown_until = time.monotonic() - 1\n+        assert comp.should_compress(65_000)\n+\n+    def test_no_cooldown_allows(self):\n+        \"\"\"The default (no cooldown set) does not block compression.\"\"\"\n+        comp = _make_compressor(config_context_length=96000)\n+        comp.last_prompt_tokens = [redacted:secret-assignment]\n+        assert comp._summary_failure_cooldown_until == 0.0\n+        assert comp.should_compress(65_000)\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}