refactor(agent): leave the API-interrupt tail close to the finalizer

handle_api_interrupt exits via break into finalize_turn, whose
_close_transcript_tail already closes the tail with the same final_response, so
its own close was a duplicate; the scaffold strip stays (the partial-text row
must follow the tool row). The scaffolding drop no longer returns a flag nobody
reads, and the give-up test relies on the autouse backoff stub in
tests/agent/conftest.py instead of re-stubbing it.
This commit is contained in:
kshitijk4poor
2026-09-24 14:55:44 +05:30
committed by kshitij
parent 013dcadc10
commit 0cf5e41fc4
4 changed files with 5 additions and 13 deletions

View File

@@ -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")

View File

@@ -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)

View File

@@ -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"

View File

@@ -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