{"repo":"NousResearch/hermes-agent","public_id":"36908","display_label":"Route compression warning to TUI via status emit","summary":"These issues and PRs address a bug where the compression count warning (indicating accuracy degradation) is silently lost and never displayed in the TUI. The fix routes this warning through the _emit_status mechanism so it reaches the user interface.","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":50302,"merged_at":"2026-06-21T18:34:47+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-06-21T18:34:47+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":36908,"pr_number":50302,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-21T18:34:47+00:00"}],"members":[{"kind":"issue","number":36908,"title":"TUI: compression count warning (accuracy degrade) is silently swallowed","url":null,"state":"closed"},{"kind":"pr","number":36922,"title":"fix(agent): route compression count warning through _emit_status for TUI delivery","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#50302","number":50302}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":50302,"title":"fix(agent): route repeated-compression warning through _emit_status (#36908)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/conversation_compression.py","tests/agent/test_compression_count_warning_36908.py"],"fix_hunks":[{"file":"agent/conversation_compression.py","hunks":["@@ -719,14 +719,20 @@ def _release_lock() -> None:\n     except Exception as _me_err:\n         logger.debug(\"memory manager on_session_switch (compression): %s\", _me_err)\n \n-    # Warn on repeated compressions (quality degrades with each pass)\n+    # Warn on repeated compressions (quality degrades with each pass).\n+    # Route through _emit_status (like the other compression warnings above)\n+    # so the warning reaches the TUI / Telegram / Discord via status_callback,\n+    # not just CLI stdout. _emit_status still _vprints for the CLI, and\n+    # storing it on _compression_warning lets replay_compression_warning\n+    # re-deliver it once a late-bound gateway status_callback is wired (#36908).\n     _cc = agent.context_compressor.compression_count\n     if _cc >= 2:\n-        agent._vprint(\n+        _cc_msg = (\n             f\"{agent.log_prefix}⚠️  Session compressed {_cc} times — \"\n-            f\"accuracy may degrade. Consider /new to start fresh.\",\n-            force=True,\n+            f\"accuracy may degrade. Consider /new to start fresh.\"\n         )\n+        agent._compression_warning = _cc_msg\n+        agent._emit_status(_cc_msg)\n \n     # Emit session:compress event so hooks (e.g. MemPalace sync) can ingest\n     # the completed old session before its details are lost. In in-place mode"]},{"file":"tests/agent/test_compression_count_warning_36908.py","hunks":["@@ -0,0 +1,87 @@\n+\"\"\"Regression for #36908: the repeated-compression warning must reach the\n+TUI / gateway, not just CLI stdout.\n+\n+When a session is compressed >= 2 times, ``compress_context`` warns that\n+accuracy may degrade. That warning used to go through ``_vprint`` (stdout\n+only), so the Ink TUI / Telegram / Discord never saw it — unlike the two\n+other compression warnings in the same module, which route through\n+``_emit_status`` (and store ``_compression_warning`` for late-bound\n+gateway replay). This pins the warning onto the gateway-aware channel.\n+\"\"\"\n+\n+from __future__ import annotations\n+\n+import os\n+from pathlib import Path\n+from unittest.mock import MagicMock, patch\n+\n+from hermes_state import SessionDB\n+\n+\n+def _build_agent_with_db(db: SessionDB, session_id: str, compression_count: int):\n+    with patch.dict(os.environ, {\"OPENROUTER_API_KEY\": \"[redacted:secret-assignment]\"}):\n+        from run_agent import AIAgent\n+\n+        agent = AIAgent(\n+            api_key=\"[redacted:secret-assignment]\",\n+            base_url=\"https://openrouter.ai/api/v1\",\n+            model=\"test/model\",\n+            quiet_mode=True,\n+            session_db=db,\n+            session_id=session_id,\n+            skip_context_files=True,\n+            skip_memory=True,\n+        )\n+\n+    compressor = MagicMock()\n+    compressor.compress.return_value = [\n+        {\"role\": \"user\", \"content\": \"[CONTEXT COMPACTION] summary\"},\n+        {\"role\": \"user\", \"content\": \"tail\"},\n+    ]\n+    compressor.compression_count = compression_count\n+    compressor.last_prompt_tokens = 0\n+    compressor.last_completion_tokens = 0\n+    compressor._last_summary_error = None\n+    compressor._last_compress_aborted = False\n+    compressor._last_aux_model_failure_model = None\n+    compressor._last_aux_model_failure_error = None\n+    agent.context_compressor = compressor\n+    return agent\n+\n+\n+def test_repeated_compression_warning_routed_through_emit_status(tmp_path: Path) -> None:\n+    db = SessionDB(db_path=tmp_path / \"state.db\")\n+    sid = \"PARENT_36908\"\n+    db.create_session(sid, source=\"cli\")\n+\n+    # compression_count == 2 → the \"compressed N times\" warning should fire.\n+    agent = _build_agent_with_db(db, sid, compression_count=2)\n+\n+    emitted: list[str] = []\n+    agent._emit_status = lambda message: emitted.append(message)\n+\n+    messages = [{\"role\": \"user\", \"content\": f\"m{i}\"} for i in range(20)]\n+    agent._compress_context(messages, \"sys\", approx_tokens=[redacted:secret-assignment]\n+\n+    # The warning reached the gateway-aware channel...\n+    assert any(\"compressed 2 times\" in m.lower() for m in emitted), (\n+        f\"repeated-compression warning not emitted via _emit_status: {emitted}\"\n+    )\n+    # ...and was stored for late-bound gateway status_callback replay.\n+    assert \"compressed 2 times\" in (getattr(agent, \"_compression_warning\", \"\") or \"\").lower()\n+\n+\n+def test_no_warning_below_threshold(tmp_path: Path) -> None:\n+    db = SessionDB(db_path=tmp_path / \"state.db\")\n+    sid = \"PARENT_36908_ONCE\"\n+    db.create_session(sid, source=\"cli\")\n+\n+    # compression_count == 1 → no repeated-compression warning.\n+    agent = _build_agent_with_db(db, sid, compression_count=1)\n+    emitted: list[str] = []\n+    agent._emit_status = lambda message: emitted.append(message)\n+\n+    messages = [{\"role\": \"user\", \"content\": f\"m{i}\"} for i in range(20)]\n+    agent._compress_context(messages, \"sys\", approx_tokens=[redacted:secret-assignment]\n+\n+    assert not any(\"compressed\" in m.lower() and \"times\" in m.lower() for m in emitted)\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}