fix(context): fail closed when preflight compression stalls

This commit is contained in:
anhtahaylove
2026-08-30 14:31:15 +07:00
committed by Teknium
parent ba0f5839d4
commit de49e1ef05
4 changed files with 47 additions and 0 deletions
+16
View File
@@ -405,6 +405,21 @@ def compression_made_progress(
_compression_made_progress = compression_made_progress
class PreflightCompressionTimedOut(RuntimeError):
"""Raised when an oversized turn cannot safely finish preflight."""
def _fail_closed_after_preflight_timeout(agent, request_tokens: int) -> None:
"""Stop an oversized turn instead of sending its unchanged provider payload."""
if not getattr(agent, "_last_compression_timed_out", False):
return
raise PreflightCompressionTimedOut(
"Context compression timed out before it could commit while the request "
f"was still approximately {request_tokens:,} tokens. The provider call "
"was not sent. Run /compress and wait for it to finish, then retry."
)
def _review_fork_first_request_pending(agent: Any) -> bool:
"""Whether a detached review fork has yet to send its first provider request.
@@ -1169,6 +1184,7 @@ def build_turn_context(
if not _compression_made_progress(
_orig_len, len(messages), _orig_tokens, _preflight_tokens
):
_fail_closed_after_preflight_timeout(agent, _preflight_tokens)
_preflight_compression_blocked = True
break # Cannot compress further: neither rows nor tokens moved
conversation_history = conversation_history_after_compression(
+6
View File
@@ -8068,6 +8068,11 @@ class AIAgent:
auto-compress abort. Auto-compress callers use the default
``force=False``.
"""
# Per-attempt signal consumed by turn-start preflight. A stalled
# compression must not be mistaken for a structural no-op and followed
# by the oversized provider request it was meant to prevent.
self._last_compression_timed_out = False
from agent.conversation_compression import (
CompressionCommitFence,
compress_context,
@@ -8194,6 +8199,7 @@ class AIAgent:
timeout_cause["progress_observed"] = progress_observed
def _on_timeout(idle, waited, since_progress):
self._last_compression_timed_out = True
total_exhausted = timeout_cause["total_exhausted"]
progress_observed = timeout_cause["progress_observed"]
if total_exhausted:
@@ -467,6 +467,7 @@ class TestCompressContextForwarderOwnsTimeout:
assert out_msgs is original
assert out_prompt == "sys"
assert agent._last_compression_timed_out is True
assert calls["n"] == 1
agent._emit_warning.assert_called_once()
assert agent.context_compressor._consecutive_timeout_failures == 1
@@ -643,5 +644,6 @@ class TestCompressContextForwarderOwnsTimeout:
commit_fence=fence,
)
assert seen["fence"] is fence
assert agent._last_compression_timed_out is False
assert prompt == "sys"
assert msgs[0]["content"] == "ok"
@@ -0,0 +1,23 @@
"""Regression coverage for stalled preflight compression."""
from types import SimpleNamespace
import pytest
from agent.turn_context import (
PreflightCompressionTimedOut,
_fail_closed_after_preflight_timeout,
)
def test_preflight_timeout_blocks_unchanged_provider_payload():
agent = SimpleNamespace(_last_compression_timed_out=True)
with pytest.raises(PreflightCompressionTimedOut, match="provider call was not sent"):
_fail_closed_after_preflight_timeout(agent, 190_035)
def test_structural_noop_keeps_existing_preflight_behavior():
agent = SimpleNamespace(_last_compression_timed_out=False)
_fail_closed_after_preflight_timeout(agent, 190_035)