fix(agent): re-anchor after post-tool compression

This commit is contained in:
Andrey
2026-08-31 20:12:58 -04:00
committed by Teknium
parent f43eef9e27
commit f27417fc40
3 changed files with 67 additions and 4 deletions

View File

@@ -22,7 +22,7 @@ from agent.conversation_compression import (
from agent.turn_context import _review_fork_first_request_pending
from agent.turn_context_compaction import (
_apply_grown_window, _blocked_compress_reason, _clear_overflow_warn, _refund_api_call,
_reset_retry_state_after_compaction,
_reanchor, _reset_retry_state_after_compaction,
)
logger = logging.getLogger("agent.conversation_loop")
@@ -238,13 +238,14 @@ class PostToolCompressionVerdict:
compression_attempts: int
final_response: Any
turn_exit_reason: Any
current_turn_user_idx: int
def compress_after_tool_results(
agent: Any, *, messages: List[Dict[str, Any]], system_message: Any, user_message: Any,
active_system_prompt: Any, conversation_history: Any, compression_attempts: int,
max_compression_attempts: int, effective_task_id: Any, final_response: Any,
turn_exit_reason: Any,
turn_exit_reason: Any, current_turn_user_idx: int,
) -> PostToolCompressionVerdict:
"""Post-tool-call compression decision. Pressure comes from API-reported
``prompt_tokens`` (a tight lower bound; thinking models inflate completion tokens),
@@ -263,6 +264,7 @@ def compress_after_tool_results(
end_turn=end_turn, messages=messages, active_system_prompt=active_system_prompt,
conversation_history=conversation_history, compression_attempts=compression_attempts,
final_response=final_response, turn_exit_reason=turn_exit_reason,
current_turn_user_idx=current_turn_user_idx,
)
_compressor = agent.context_compressor
@@ -355,6 +357,7 @@ def compress_after_tool_results(
final_response = _HANDOFF_SKIP_FINAL_RESPONSE
turn_exit_reason = "compaction_handoff_not_actionable"
return _verdict(True)
current_turn_user_idx = _reanchor(agent, messages, user_message)
elif agent.compression_enabled:
# Over threshold but compression blocked (cooldown/anti-thrash): deduped
# warning so context can't silently overflow. ``attempts_spent`` names the

View File

@@ -39,6 +39,7 @@ class ToolRoundVerdict:
failed: Any
_turn_exit_reason: Any
truncated_tool_call_retries: Any
current_turn_user_idx: Any
result: Optional[Dict[str, Any]] = None
@@ -47,7 +48,7 @@ def run_tool_round(
conversation_history: Any, api_call_count: Any, effective_task_id: Any, user_message: Any,
system_message: Any, active_system_prompt: Any, compression_attempts: Any,
max_compression_attempts: Any, final_response: Any, failed: Any, _turn_exit_reason: Any,
truncated_tool_call_retries: Any,
truncated_tool_call_retries: Any, current_turn_user_idx: Any,
) -> ToolRoundVerdict:
"""Execute one tool round in the exact original order. Persist-before-execute is a
durability invariant: resume must see the executed block if a destructive tool restarts
@@ -60,7 +61,8 @@ def run_tool_round(
action=action, messages=messages, conversation_history=conversation_history,
active_system_prompt=active_system_prompt, compression_attempts=compression_attempts,
final_response=final_response, failed=failed, _turn_exit_reason=_turn_exit_reason,
truncated_tool_call_retries=truncated_tool_call_retries, result=result,
truncated_tool_call_retries=truncated_tool_call_retries,
current_turn_user_idx=current_turn_user_idx, result=result,
)
if not agent.quiet_mode:
@@ -191,6 +193,7 @@ def run_tool_round(
compression_attempts=compression_attempts,
max_compression_attempts=max_compression_attempts, effective_task_id=effective_task_id,
final_response=final_response, turn_exit_reason=_turn_exit_reason,
current_turn_user_idx=current_turn_user_idx,
)
messages = _ptc.messages
active_system_prompt = _ptc.active_system_prompt
@@ -198,6 +201,7 @@ def run_tool_round(
compression_attempts = _ptc.compression_attempts
final_response = _ptc.final_response
_turn_exit_reason = _ptc.turn_exit_reason
current_turn_user_idx = _ptc.current_turn_user_idx
if _ptc.end_turn:
return _verdict("break")

View File

@@ -0,0 +1,56 @@
from types import SimpleNamespace
from agent.turn_preflight import compress_after_tool_results
def test_post_tool_compression_reanchors_the_active_user_boundary(monkeypatch):
compressed = [
{"role": "user", "content": "compressed history"},
{"role": "user", "content": "current ask"},
{"role": "assistant", "tool_calls": [{"id": "2"}]},
{"role": "tool", "content": "fresh result", "tool_call_id": "2"},
]
class Compressor:
last_prompt_tokens = 100
threshold_tokens = 50
@staticmethod
def should_compress(_tokens):
return True
agent = SimpleNamespace(
context_compressor=Compressor(),
compression_enabled=True,
_clear_context_overflow_warn=lambda: None,
_safe_print=lambda *_args: None,
_compress_context=lambda *_args, **_kwargs: (compressed, "system"),
_persist_user_message_idx=4,
)
monkeypatch.setattr(
"agent.turn_preflight.conversation_history_after_compression",
lambda _agent, _messages, _history: [],
)
monkeypatch.setattr(
"agent.conversation_loop._should_skip_model_call_for_reference_handoff",
lambda _messages, _user_message: False,
)
verdict = compress_after_tool_results(
agent,
messages=[{"role": "user", "content": "current ask"}],
system_message="system",
user_message="current ask",
active_system_prompt="system",
conversation_history=[],
compression_attempts=0,
max_compression_attempts=1,
effective_task_id="task",
final_response="",
turn_exit_reason=None,
current_turn_user_idx=0,
)
assert verdict.messages is compressed
assert verdict.current_turn_user_idx == 1
assert agent._persist_user_message_idx == 1