test(compression): align no-op strike tests with structural backoff (#93022)
Two suites still encoded the pre-#93093 contract that the three
structural no-op branches (insufficient_messages, no_compressible_window,
empty_post_handoff_window) increment _ineffective_compression_count:
- tests/agent/test_compaction_anti_thrash.py::
TestMinimumMessagesBranch::test_too_few_messages_records_an_ineffective_pass
- tests/run_agent/test_infinite_compaction_loop.py::
TestCompressNoOpRegistersIneffective::{test_no_op_increments_counter,
test_two_no_ops_block_should_compress}
Structural no-ops are transcript-shape facts, not evidence of an
incompressible floor, so they now arm _structural_no_op_backoff_until
and leave the strike counter untouched. Update the tests to pin the new
contract (count unchanged, backoff armed via time.monotonic(),
should_compress blocked while it holds) and rename accordingly. The
outcome contract of test_two_no_ops_block_should_compress is preserved:
repeated no-ops still block further automatic compression.
This commit is contained in:
@@ -30,6 +30,8 @@ Two subtleties this pins:
|
||||
and disables compaction on a healthy session.
|
||||
``test_no_false_positive_under_tokenizer_skew``.
|
||||
"""
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from agent.context_compressor import ContextCompressor
|
||||
@@ -188,11 +190,13 @@ class TestFutilityGuard:
|
||||
|
||||
|
||||
class TestMinimumMessagesBranch:
|
||||
def test_too_few_messages_records_an_ineffective_pass(self):
|
||||
"""Returning the transcript unchanged must move the anti-thrash state.
|
||||
def test_too_few_messages_defers_via_structural_backoff(self):
|
||||
"""A structurally impossible compaction must not strike the breaker.
|
||||
|
||||
Otherwise should_compress() keeps saying True about a transcript that can
|
||||
never shrink, and every turn re-enters a no-op compaction.
|
||||
#93022 — too-few-messages is a transcript-shape fact, not evidence
|
||||
of an incompressible floor: striking it punished unrelated later
|
||||
failures. Instead the branch arms the structural no-op backoff so
|
||||
retries are deferred without burning anti-thrash strikes.
|
||||
"""
|
||||
cc = _compressor(threshold_tokens=1)
|
||||
msgs = _messages(3, size=10)
|
||||
@@ -202,7 +206,13 @@ class TestMinimumMessagesBranch:
|
||||
|
||||
assert len(out) == len(msgs), "nothing should have been compressed"
|
||||
assert cc._last_compression_made_progress is False
|
||||
assert cc._ineffective_compression_count == before + 1
|
||||
assert cc._ineffective_compression_count == before, (
|
||||
"structural no-op must leave the strike counter untouched"
|
||||
)
|
||||
assert cc._structural_no_op_backoff_until > time.monotonic(), (
|
||||
"structural no-op must arm the retry backoff"
|
||||
)
|
||||
assert cc._compression_block_reason().startswith("structural_backoff")
|
||||
|
||||
|
||||
class TestRejectedCompactionStrike:
|
||||
|
||||
@@ -10,8 +10,9 @@ compressor to fire on every subsequent turn with no progress.
|
||||
The fix adds two safeguards:
|
||||
1. _find_tail_cut_by_tokens: when the whole transcript fits in soft_ceiling,
|
||||
re-walk with the raw (non-inflated) budget to find a meaningful cut.
|
||||
2. compress(): when compress_start >= compress_end, record the no-op as
|
||||
an ineffective compression so should_compress() anti-thrashing fires.
|
||||
2. compress(): when compress_start >= compress_end, defer retries via the
|
||||
structural no-op backoff (#93022) so should_compress() anti-thrashing
|
||||
fires without burning anti-thrash strikes on transcript-shape facts.
|
||||
"""
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
@@ -56,15 +57,16 @@ def _build_session(n_turns: int, words_per_turn: int = 20) -> list:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestCompressNoOpRegistersIneffective:
|
||||
"""When compress_start >= compress_end, the fix records this as
|
||||
an ineffective compression so the anti-thrashing guard fires.
|
||||
"""When compress_start >= compress_end, the fix defers further attempts
|
||||
via the structural no-op backoff (#93022) so the anti-thrashing guard
|
||||
fires.
|
||||
|
||||
We trigger this path by having _find_tail_cut_by_tokens return
|
||||
head_end (which makes compress_end = head_end + 1, same as
|
||||
compress_start after alignment)."""
|
||||
|
||||
def test_no_op_increments_counter(self):
|
||||
"""compress_start >= compress_end -> _ineffective_compression_count += 1"""
|
||||
def test_no_op_arms_structural_backoff(self):
|
||||
"""compress_start >= compress_end -> backoff armed, strikes untouched."""
|
||||
comp = _make_compressor(
|
||||
summary_target_ratio=0.45,
|
||||
config_context_length=96000,
|
||||
@@ -80,8 +82,14 @@ class TestCompressNoOpRegistersIneffective:
|
||||
|
||||
result = comp.compress(messages, current_tokens=73_000)
|
||||
|
||||
assert comp._ineffective_compression_count >= 1, (
|
||||
f"Expected ineffective_compression_count >= 1, got {comp._ineffective_compression_count}"
|
||||
assert len(result) == len(messages), (
|
||||
"no-op compression must return the transcript unchanged"
|
||||
)
|
||||
assert comp._ineffective_compression_count == 0, (
|
||||
"a structural impossibility is not an ineffective strike (#93022)"
|
||||
)
|
||||
assert comp._structural_no_op_backoff_until > time.monotonic(), (
|
||||
"structural no-op must arm the retry backoff"
|
||||
)
|
||||
|
||||
|
||||
@@ -98,9 +106,11 @@ class TestCompressNoOpRegistersIneffective:
|
||||
comp.compress(messages, current_tokens=73_000)
|
||||
comp.compress(messages, current_tokens=73_000)
|
||||
|
||||
assert comp._ineffective_compression_count >= 2
|
||||
assert comp._ineffective_compression_count == 0, (
|
||||
"structural no-ops defer via backoff instead of striking (#93022)"
|
||||
)
|
||||
assert not comp.should_compress(73_000), (
|
||||
"should_compress should return False after 2+ ineffective compressions"
|
||||
"should_compress should return False while the structural backoff holds"
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user