fix(compression): /compress refusal no longer reports a successful rewrite
This commit is contained in:
@@ -1885,6 +1885,7 @@ class ContextCompressor(ContextEngine):
|
||||
self._cooldown_persist_failed = False
|
||||
self._last_summary_error = None
|
||||
self._last_compress_aborted = False
|
||||
self._last_compress_refused_would_grow = False
|
||||
self.last_real_prompt_tokens = 0
|
||||
self.last_compression_rough_tokens = 0
|
||||
self.last_rough_tokens_when_real_prompt_fit = 0
|
||||
@@ -2167,6 +2168,7 @@ class ContextCompressor(ContextEngine):
|
||||
self._summary_failure_cooldown_until = 0.0
|
||||
self._cooldown_persist_failed = False
|
||||
self._last_compress_aborted = False
|
||||
self._last_compress_refused_would_grow = False
|
||||
self._context_probed = False
|
||||
self._context_probe_persistable = False
|
||||
self.last_real_prompt_tokens = 0
|
||||
@@ -6927,6 +6929,7 @@ This compaction should PRIORITISE preserving all information related to the focu
|
||||
self._last_aux_model_failure_error = None
|
||||
self._last_aux_model_failure_model = None
|
||||
self._last_compress_aborted = False
|
||||
self._last_compress_refused_would_grow = False
|
||||
self._last_compression_made_progress = False
|
||||
# NOTE: do NOT reset _last_summary_auth_failure or
|
||||
# _last_summary_network_failure here. These flags are set by
|
||||
|
||||
@@ -3395,6 +3395,17 @@ def compress_context(
|
||||
f"{_rough_in:,}",
|
||||
f"{_rough_out:,}",
|
||||
)
|
||||
# Flag the refusal on the compressor state so manual
|
||||
# /compress feedback can report it honestly. Without this,
|
||||
# the CLI compared the returned list against its pre-call
|
||||
# snapshot, saw a difference (durable-snapshot adoption can
|
||||
# legitimately change the count), and printed
|
||||
# "✅ Compressed: 8 → 14 messages" directly under the
|
||||
# refusal warning (Aug 2026 full-surface CLI QA sweep).
|
||||
try:
|
||||
agent.context_compressor._last_compress_refused_would_grow = True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
agent._emit_warning(
|
||||
"⚠️ Compression refused: the generated summary "
|
||||
|
||||
@@ -53,6 +53,11 @@ def summarize_manual_compression(
|
||||
compression_state is not None
|
||||
and getattr(compression_state, "_last_compress_aborted", False) is True
|
||||
)
|
||||
refused_would_grow = (
|
||||
compression_state is not None
|
||||
and getattr(compression_state, "_last_compress_refused_would_grow", False)
|
||||
is True
|
||||
)
|
||||
fallback_used = (
|
||||
compression_state is not None
|
||||
and getattr(compression_state, "_last_summary_fallback_used", False) is True
|
||||
@@ -65,7 +70,12 @@ def summarize_manual_compression(
|
||||
if not isinstance(failure_reason, str) or not failure_reason.strip():
|
||||
failure_reason = None
|
||||
|
||||
if aborted:
|
||||
if refused_would_grow:
|
||||
headline = (
|
||||
f"Compression refused (summary would grow the conversation): "
|
||||
f"{before_count} messages preserved"
|
||||
)
|
||||
elif aborted:
|
||||
headline = f"Compression aborted: {before_count} messages preserved"
|
||||
elif fallback_used:
|
||||
headline = (
|
||||
@@ -78,6 +88,8 @@ def summarize_manual_compression(
|
||||
|
||||
if noop and after_tokens == before_tokens:
|
||||
token_line = f"Approx request size: ~{before_tokens:,} tokens (unchanged)"
|
||||
elif refused_would_grow:
|
||||
token_line = f"Approx request size: ~{before_tokens:,} tokens (unchanged)"
|
||||
else:
|
||||
token_line = (
|
||||
f"Approx request size: ~{before_tokens:,} → "
|
||||
@@ -85,7 +97,12 @@ def summarize_manual_compression(
|
||||
)
|
||||
|
||||
note = None
|
||||
if aborted:
|
||||
if refused_would_grow:
|
||||
note = (
|
||||
"The generated summary was larger than what it would replace; "
|
||||
"no messages were removed."
|
||||
)
|
||||
elif aborted:
|
||||
note = "Summary generation failed; no messages were removed."
|
||||
elif fallback_used:
|
||||
dropped_count = getattr(
|
||||
@@ -113,6 +130,7 @@ def summarize_manual_compression(
|
||||
return {
|
||||
"noop": noop,
|
||||
"aborted": aborted,
|
||||
"refused_would_grow": refused_would_grow,
|
||||
"fallback_used": fallback_used,
|
||||
"headline": headline,
|
||||
"token_line": token_line,
|
||||
|
||||
6
cli.py
6
cli.py
@@ -12970,7 +12970,11 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
self.agent, "context_compressor", None
|
||||
),
|
||||
)
|
||||
if summary.get("aborted") or summary.get("fallback_used"):
|
||||
if (
|
||||
summary.get("aborted")
|
||||
or summary.get("fallback_used")
|
||||
or summary.get("refused_would_grow")
|
||||
):
|
||||
icon = "⚠️"
|
||||
else:
|
||||
icon = "🗜️" if summary["noop"] else "✅"
|
||||
|
||||
58
tests/agent/test_manual_compression_refusal_feedback.py
Normal file
58
tests/agent/test_manual_compression_refusal_feedback.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Regression tests: manual /compress feedback for a would-grow refusal.
|
||||
|
||||
The anti-growth guard (#83339 / PR #86700) can refuse the commit AFTER the
|
||||
compressor produced a candidate. The CLI's feedback previously compared the
|
||||
returned list against its pre-call snapshot; durable-snapshot adoption can
|
||||
legitimately change that list, so a REFUSED compression printed
|
||||
"✅ Compressed: 8 → 14 messages" directly under the refusal warning
|
||||
(Aug 2026 full-surface CLI QA sweep). The refusal must surface as its own
|
||||
honest headline.
|
||||
"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from agent.manual_compression_feedback import summarize_manual_compression
|
||||
|
||||
|
||||
def _msgs(n):
|
||||
return [{"role": "user", "content": f"m{n_i}"} for n_i in range(n)]
|
||||
|
||||
|
||||
def _state(**flags):
|
||||
base = {
|
||||
"_last_compress_aborted": False,
|
||||
"_last_compress_refused_would_grow": False,
|
||||
"_last_summary_fallback_used": False,
|
||||
"_last_summary_error": None,
|
||||
"_last_summary_dropped_count": 0,
|
||||
}
|
||||
base.update(flags)
|
||||
return SimpleNamespace(**base)
|
||||
|
||||
|
||||
class TestWouldGrowRefusalFeedback:
|
||||
def test_refusal_reports_preserved_not_compressed(self):
|
||||
summary = summarize_manual_compression(
|
||||
_msgs(8), _msgs(14), 22025, 22453,
|
||||
compression_state=_state(_last_compress_refused_would_grow=True),
|
||||
)
|
||||
assert summary["refused_would_grow"] is True
|
||||
assert "refused" in summary["headline"].lower()
|
||||
assert "→" not in summary["headline"] # never claims a message rewrite
|
||||
assert "unchanged" in summary["token_line"]
|
||||
assert "no messages were removed" in summary["note"]
|
||||
|
||||
def test_normal_compression_unchanged(self):
|
||||
summary = summarize_manual_compression(
|
||||
_msgs(10), _msgs(4), 30000, 12000, compression_state=_state(),
|
||||
)
|
||||
assert summary["refused_would_grow"] is False
|
||||
assert summary["headline"].startswith("Compressed: 10 → 4")
|
||||
|
||||
def test_abort_still_wins_its_own_headline(self):
|
||||
summary = summarize_manual_compression(
|
||||
_msgs(6), _msgs(6), 9000, 9000,
|
||||
compression_state=_state(_last_compress_aborted=True),
|
||||
)
|
||||
assert summary["aborted"] is True
|
||||
assert summary["headline"].startswith("Compression aborted")
|
||||
Reference in New Issue
Block a user