hermes-agent/tests/gateway/test_compression_progress_notices.py
Teknium 4025329ac4
feat(gateway): opt-in compression progress notices via compression.progress_notices (#52995) (#70457)
Routine automatic compression stays silent-by-design on chat platforms
(default unchanged, byte-identical). New opt-in config key
compression.progress_notices (bool, default false) opens a gate on the
gateway noise filter (_prepare_gateway_status_message) that lets ROUTINE
compression progress statuses through to chat surfaces.

- Membership is derived from the #69550 status template constants in
  agent/conversation_compression.py (compiled to a literal-escaped regex,
  never re-inlined wording), so unrelated noisy statuses (aux failures,
  provider retry/rate-limit chatter) stay suppressed even when enabled.
- The compaction completion notice (COMPACTION_DONE_STATUS, #69546
  lifecycle 'compacted' edge) already flows through the status path and
  passes the filter — no new emit site needed.
- Config wired everywhere: hermes_cli/config.py DEFAULT_CONFIG,
  cli-config.yaml.example, gateway raw-YAML read (live, mtime-cached),
  gateway hot-reload cache-busting key list, website configuration docs.
- Failure notices and manual /compress feedback remain always-visible;
  VISIBLE_COMPRESSION_MESSAGES and emit sites untouched.

Design by @havok-training (issue #52995).
2026-07-23 19:44:19 -07:00

172 lines
7.1 KiB
Python

"""Opt-in compression progress notices on chat gateways (#52995).
Routine automatic compression is silent-by-design on human-facing chat
platforms: the gateway noise filter (`_TELEGRAM_NOISY_STATUS_RE` via
`_prepare_gateway_status_message`) swallows every ROUTINE compression status.
`compression.progress_notices: true` (default: false) opens an opt-in gate
that lets those ROUTINE compression statuses through — scoped strictly to
the #69550 compression status template constants, so unrelated operational
noise (auxiliary failures, provider retry/rate-limit chatter) stays
suppressed even when the gate is enabled.
The default-OFF path must stay byte-identical to silent-by-design main:
tests/gateway/test_telegram_noise_filter.py pins that suite unchanged.
"""
import pytest
import gateway.run as gateway_run
from agent.conversation_compression import (
COMPACTION_DONE_STATUS,
ROUTINE_COMPRESSION_STATUS_SAMPLES,
)
from gateway.run import _prepare_gateway_status_message
# Chat surfaces the opt-in must deliver to (subset of the noise-filter
# suite's CHAT_PLATFORMS; telegram + discord are the required anchors).
CHAT_PLATFORMS = ["telegram", "discord", "slack", "whatsapp"]
# Noisy statuses that are NOT routine compression progress — they must stay
# suppressed on chat platforms even when progress_notices is enabled.
NON_COMPRESSION_NOISE = [
"⚠ Auxiliary title generation failed: HTTP 400: Operation contains cybersecurity risk",
"⏳ Retrying in 4.2s (attempt 1/3)...",
"⏱️ Rate limited. Waiting 30.0s (attempt 2/3)...",
"⚠️ Max retries (3) exhausted — trying fallback...",
"⚠ Compression summary failed: upstream error. Inserted a fallback context marker.",
(
"⚠ Configured auxiliary compression provider 'openai' is unavailable — "
"context compression will drop middle turns without a summary. Check "
"auxiliary.compression in config.yaml and reauthenticate that provider."
),
(
"⚠ Skipping concurrent compression — another path is already "
"compressing this session. Will retry after it finishes."
),
]
@pytest.fixture
def progress_notices_enabled(monkeypatch):
"""Gateway config with compression.progress_notices: true."""
monkeypatch.setattr(
gateway_run,
"_load_gateway_config",
lambda: {"compression": {"progress_notices": True}},
)
@pytest.fixture
def progress_notices_default(monkeypatch):
"""Gateway config without the key — the silent-by-design default."""
monkeypatch.setattr(gateway_run, "_load_gateway_config", lambda: {})
@pytest.mark.parametrize("platform", CHAT_PLATFORMS)
@pytest.mark.parametrize(
"message", ROUTINE_COMPRESSION_STATUS_SAMPLES, ids=lambda m: m[:32]
)
def test_enabled_delivers_routine_compression_statuses(
progress_notices_enabled, platform, message
):
"""Opt-in ON: every ROUTINE compression status reaches chat platforms.
Iterates the sample strings formatted from the SAME template constants
the emit sites use, so wording drift at an emit site cannot silently
detach the opt-in gate from the real messages.
"""
assert _prepare_gateway_status_message(platform, "lifecycle", message) == message
@pytest.mark.parametrize("platform", CHAT_PLATFORMS)
@pytest.mark.parametrize(
"message", ROUTINE_COMPRESSION_STATUS_SAMPLES, ids=lambda m: m[:32]
)
def test_default_stays_silent(progress_notices_default, platform, message):
"""Default (key absent): routine compression statuses stay suppressed."""
assert _prepare_gateway_status_message(platform, "lifecycle", message) is None
@pytest.mark.parametrize("platform", CHAT_PLATFORMS)
def test_explicit_false_stays_silent(monkeypatch, platform):
"""compression.progress_notices: false behaves exactly like the default."""
monkeypatch.setattr(
gateway_run,
"_load_gateway_config",
lambda: {"compression": {"progress_notices": False}},
)
for message in ROUTINE_COMPRESSION_STATUS_SAMPLES:
assert _prepare_gateway_status_message(platform, "lifecycle", message) is None
@pytest.mark.parametrize("platform", CHAT_PLATFORMS)
@pytest.mark.parametrize("message", NON_COMPRESSION_NOISE, ids=lambda m: m[:32])
def test_enabled_still_suppresses_non_compression_noise(
progress_notices_enabled, platform, message
):
"""The gate is scoped to compression progress statuses ONLY.
Aux-model failures, provider retry/rate-limit chatter, and other noisy
statuses that are not #69550 compression progress templates must stay
suppressed on chat surfaces even when progress_notices is enabled.
"""
assert _prepare_gateway_status_message(platform, "warn", message) is None
@pytest.mark.parametrize("enabled", [True, False], ids=["enabled", "default"])
@pytest.mark.parametrize("platform", CHAT_PLATFORMS)
def test_compaction_completion_notice_reaches_chat(monkeypatch, platform, enabled):
"""The #69546 'compacted' lifecycle edge is deliverable on chat surfaces.
COMPACTION_DONE_STATUS already flows through the status callback on
compaction completion and is not matched by the noise regex — the opt-in
gate must not change that in either mode, so users who enable
progress_notices see the completion stat notice paired with the start.
"""
monkeypatch.setattr(
gateway_run,
"_load_gateway_config",
lambda: {"compression": {"progress_notices": enabled}},
)
assert (
_prepare_gateway_status_message(platform, "compacted", COMPACTION_DONE_STATUS)
== COMPACTION_DONE_STATUS
)
def test_config_read_errors_fail_closed(monkeypatch):
"""A broken config read keeps the silent-by-design default."""
def _boom():
raise RuntimeError("config unreadable")
monkeypatch.setattr(gateway_run, "_load_gateway_config", _boom)
message = ROUTINE_COMPRESSION_STATUS_SAMPLES[0]
assert _prepare_gateway_status_message("telegram", "lifecycle", message) is None
def test_enabled_gate_does_not_leak_to_raw_platforms(progress_notices_enabled):
"""Programmatic surfaces keep raw text regardless of the gate."""
message = ROUTINE_COMPRESSION_STATUS_SAMPLES[0]
for platform in ("local", "api_server", "webhook", "msgraph_webhook"):
assert (
_prepare_gateway_status_message(platform, "lifecycle", message) == message
)
def test_progress_notices_is_a_hot_reload_cache_busting_key():
"""Editing compression.progress_notices on a running gateway must take
effect like every other compression.* key (hot-reload key list)."""
assert ("compression", "progress_notices") in gateway_run.GatewayRunner._CACHE_BUSTING_CONFIG_KEYS
def test_progress_regex_covers_every_routine_sample():
"""The template-derived membership regex matches every ROUTINE sample.
Guards the coupling: a new #69550 template constant added to
ROUTINE_COMPRESSION_STATUS_SAMPLES without being added to the gateway's
_COMPRESSION_PROGRESS_STATUS_RE alternatives fails here.
"""
for message in ROUTINE_COMPRESSION_STATUS_SAMPLES:
assert gateway_run._COMPRESSION_PROGRESS_STATUS_RE.search(message), (
f"routine compression sample not covered by the opt-in gate: {message!r}"
)