diff --git a/agent/session_persistence.py b/agent/session_persistence.py index c882fc0dd8..857209cb5b 100644 --- a/agent/session_persistence.py +++ b/agent/session_persistence.py @@ -364,19 +364,16 @@ class SessionPersistenceMixin: self._session_db.flush_token_counts() note_turn_persisted(self) - def _drop_trailing_empty_response_scaffolding(self, messages: List[Dict]) -> bool: - """Pop empty-response retry scaffolding from the tail; True when any was present. The + def _drop_trailing_empty_response_scaffolding(self, messages: List[Dict]) -> None: + """Pop empty-response retry scaffolding from the tail. The assistant(tool_calls) / tool rows before it stay: they were saved before the tools ran, so dropping them from the live history only makes the model repeat a side effect the durable transcript already records.""" def tail(*keys: str) -> bool: return bool(messages) and isinstance(messages[-1], dict) and any(messages[-1].get(k) for k in keys) - dropped_scaffolding = False while tail("_empty_recovery_synthetic", "_empty_terminal_sentinel"): messages.pop() - dropped_scaffolding = True - return dropped_scaffolding _repair_message_sequence = _forward("agent.agent_runtime_helpers", "repair_message_sequence") diff --git a/agent/turn_api_call.py b/agent/turn_api_call.py index 1cec074c6f..36d8c977ab 100644 --- a/agent/turn_api_call.py +++ b/agent/turn_api_call.py @@ -17,7 +17,6 @@ from typing import Any, Dict, Optional from agent.error_classifier import FailoverReason from agent.agent_runtime_helpers import _INTERRUPTED_PLACEHOLDER from agent.message_metadata import append_message -from agent.message_sanitization import close_interrupted_tool_sequence from agent.repetition_guard import REPETITION_LOOP_INTERRUPTED, is_runaway_repetition from agent.turn_failure_copy import site_copy, stamp_failure @@ -189,8 +188,8 @@ def handle_api_interrupt( agent._vprint(f"{agent.log_prefix}⚡ Interrupted during API call.", force=True) interrupted = True # A Stop during the empty-response nudge request leaves the synthetic assistant+nudge - # pair after an executed tool result; strip it so the row appended below (or the close) - # follows the tool row, and this exit owner records its own reason. + # pair after an executed tool result; strip it so the row appended below follows the tool + # row (the finalizer then closes the tail with this exit's own reason). agent._drop_trailing_empty_response_scaffolding(messages) _partial = agent._strip_think_blocks( getattr(agent, "_current_streamed_assistant_text", "") or "" @@ -209,7 +208,6 @@ def handle_api_interrupt( final_response = _partial else: final_response = f"{INTERRUPT_WAITING_FOR_MODEL_PREFIX}{api_elapsed:.1f}s elapsed)." - close_interrupted_tool_sequence(messages, final_response) agent._persist_session(messages, conversation_history) return ApiInterruptVerdict("break", thinking_spinner, interrupted, final_response) diff --git a/tests/agent/test_empty_response_recovery_persistence.py b/tests/agent/test_empty_response_recovery_persistence.py index 06c4e1001a..2bf8d4612a 100644 --- a/tests/agent/test_empty_response_recovery_persistence.py +++ b/tests/agent/test_empty_response_recovery_persistence.py @@ -197,9 +197,6 @@ def real_loop(tmp_path, monkeypatch): monkeypatch.setenv("no_proxy", "") monkeypatch.setattr("agent.title_generator.maybe_auto_title", lambda *a, **k: None) monkeypatch.setattr("agent.title_generator.start_title_upgrade", lambda *a, **k: None) - # The first empty response backs off 5-7.5 s for real; the give-up path is reached via the - # deterministic-empty guard, not the wait (``turn_empty_response`` imports this lazily). - monkeypatch.setattr("agent.retry_utils.jittered_backoff", lambda *a, **k: 0.0) monkeypatch.chdir(tmp_path) db = SessionDB(db_path=tmp_path / "state.db") sid = "sess-empty-exit" diff --git a/tests/agent/test_message_sequence_repair.py b/tests/agent/test_message_sequence_repair.py index ba80f21eca..b5692f08ef 100644 --- a/tests/agent/test_message_sequence_repair.py +++ b/tests/agent/test_message_sequence_repair.py @@ -33,7 +33,7 @@ def test_drop_scaffolding_keeps_executed_tool_pair(): "_empty_terminal_sentinel": True}, ] - assert AIAgent._drop_trailing_empty_response_scaffolding(agent, messages) is True + AIAgent._drop_trailing_empty_response_scaffolding(agent, messages) assert messages == executed