fix(agent): recover anti-thrashing after prompt growth

This commit is contained in:
Tranquil-Flow 2026-04-25 10:08:53 +10:00
parent e1393670ed
commit 55a4c5385f
2 changed files with 51 additions and 15 deletions

View file

@ -288,6 +288,10 @@ class ContextCompressor(ContextEngine):
def name(self) -> str:
return "compressor"
def _recovery_growth_tokens(self) -> int:
"""How much new prompt growth warrants another compression attempt."""
return max(int(self.threshold_tokens * 0.15), 8_000)
def on_session_reset(self) -> None:
"""Reset all per-session state for /new or /reset."""
super().on_session_reset()
@ -298,6 +302,7 @@ class ContextCompressor(ContextEngine):
self._last_compression_savings_pct = 100.0
self._ineffective_compression_count = 0
self._last_compression_time = 0.0
self._last_compression_prompt_tokens = 0
def update_model(
self,
@ -391,6 +396,7 @@ class ContextCompressor(ContextEngine):
self._last_compression_savings_pct: float = 100.0
self._ineffective_compression_count: int = 0
self._last_compression_time: float = 0.0
self._last_compression_prompt_tokens: int = 0
self._summary_failure_cooldown_until: float = 0.0
self._last_summary_error: Optional[str] = None
@ -409,18 +415,28 @@ class ContextCompressor(ContextEngine):
tokens = prompt_tokens if prompt_tokens is not None else self.last_prompt_tokens
if tokens < self.threshold_tokens:
return False
# Anti-thrashing: time-based recovery — if enough time has passed since
# the last compression, reset the ineffective counter so the session
# gets another chance. 300s is enough for significant new context to
# accumulate, making another attempt worthwhile.
if (self._ineffective_compression_count >= 2
and self._last_compression_time > 0
and time.monotonic() - self._last_compression_time >= 300):
self._ineffective_compression_count = 0
if not self.quiet_mode:
logger.info(
"Anti-thrashing cooldown expired — re-enabling auto-compression"
)
# Anti-thrashing recovery: allow another attempt after either enough
# wall-clock time has passed or enough new prompt growth has
# accumulated to make compression worthwhile again.
if self._ineffective_compression_count >= 2:
recovered = False
if (self._last_compression_time > 0
and time.monotonic() - self._last_compression_time >= 300):
recovered = True
if not self.quiet_mode:
logger.info(
"Anti-thrashing cooldown expired — re-enabling auto-compression"
)
elif (self._last_compression_prompt_tokens > 0
and tokens - self._last_compression_prompt_tokens >= self._recovery_growth_tokens()):
recovered = True
if not self.quiet_mode:
logger.info(
"Anti-thrashing reset after prompt growth (%d new tokens)",
tokens - self._last_compression_prompt_tokens,
)
if recovered:
self._ineffective_compression_count = 0
# Anti-thrashing: back off if recent compressions were ineffective
if self._ineffective_compression_count >= 2:
if not self.quiet_mode:
@ -1294,6 +1310,7 @@ The user has requested that this compaction PRIORITISE preserving all informatio
# Anti-thrashing: track compression effectiveness
self._last_compression_time = time.monotonic()
self._last_compression_prompt_tokens = display_tokens
savings_pct = (saved_estimate / display_tokens * 100) if display_tokens > 0 else 0
self._last_compression_savings_pct = savings_pct
if savings_pct < 10:

View file

@ -65,20 +65,39 @@ class TestAntiThrashingRecovery:
compressor.last_prompt_tokens = 90000
assert compressor.should_compress() is False
def test_anti_thrash_recovers_after_meaningful_prompt_growth(self, compressor):
"""Large prompt growth should re-enable compression even before cooldown."""
compressor._ineffective_compression_count = 2
compressor._last_compression_time = time.monotonic() - 60
compressor._last_compression_prompt_tokens = 90_000
compressor.last_prompt_tokens = 105_000
assert compressor.should_compress() is True
assert compressor._ineffective_compression_count == 0
def test_anti_thrash_small_growth_does_not_reset(self, compressor):
"""Minor growth should not immediately re-enable compression."""
compressor._ineffective_compression_count = 2
compressor._last_compression_time = time.monotonic() - 60
compressor._last_compression_prompt_tokens = 90_000
compressor.last_prompt_tokens = 94_000
assert compressor.should_compress() is False
def test_anti_thrash_no_recovery_without_timestamp(self, compressor):
"""If no compression has ever run (_last_compression_time == 0),
the time-based recovery should not trigger."""
"""Without time or growth metadata, recovery should not trigger."""
compressor._ineffective_compression_count = 2
compressor._last_compression_time = 0.0
compressor._last_compression_prompt_tokens = 0
compressor.last_prompt_tokens = 90000
assert compressor.should_compress() is False
def test_session_reset_clears_compression_time(self, compressor):
"""on_session_reset() should reset _last_compression_time."""
"""on_session_reset() should reset anti-thrash recovery metadata."""
compressor._last_compression_time = 12345.0
compressor._last_compression_prompt_tokens = 90000
compressor._ineffective_compression_count = 2
compressor.on_session_reset()
assert compressor._last_compression_time == 0.0
assert compressor._last_compression_prompt_tokens == 0
assert compressor._ineffective_compression_count == 0