fix(agent): close a failed turn's durable user tail so the next prompt is not merged into it
Terminal-failure paths (HTTP-200 content-policy refusal, ``_Trunc.end_turn``, retry exhaustion, interrupt before any assistant text) persist the accepted user row and return before ``finalize_turn``, so ``user`` stays the durable conversation tail. The next prompt appends a second user row, ``repair_message_sequence`` merges the pair, and the provider is asked to act on the failed request again. The gateway compensates with ``_hmwa_close_failed_turn`` (#108033); standalone ACP, the CLI and the TUI/Desktop hand ``result["messages"]`` straight back as history and had no closer. Close it once, at ``agent/conversation_loop.py::run_conversation`` — the seam every envelope leaves through — with a Hermes-authored assistant boundary (``agent/turn_failure_copy.py::FAILED_TURN_NOTICE`` / ``PARTIAL_FAILED_TURN_NOTICE``, which the gateway now aliases instead of keeping its own copy). Idempotence is keyed on ``SessionDB.latest_conversation_role`` (durable state, not content), so a redelivery or a tail another writer already closed is a no-op and the gateway's closer no-ops in turn. The context-pressure classes (``compression_exhausted``, ``compression_deferred``, ``failure_reason == "context_overflow"``) are excluded: appending to an oversized session is the #1630 growth loop; their repair is rotation. Adjacent defect from the same report: ``acp_adapter/server.py::_finish_turn`` called ``final_response.startswith`` on ``None`` for an interrupted turn — the same one-line fix PR #64471 by @israellot filed first (its wider prompt()-restructure is superseded by the current ``_finish_turn`` shape). Slimmer redo of #114168 by @kendrickkester (same seam and invariants; the +1023-line PR carried a new copy module, an accepted-turn re-anchoring scan and an 859-line suite). Two invariant tests: the real ACP path (loopback provider, refusal then a new prompt) and the durable-tail idempotence / overflow exclusion. Co-authored-by: Kendrick Kester <kendrick.kester@gmail.com> Co-authored-by: Israel Lot <israel.lot@gmail.com>
This commit is contained in:
@@ -913,7 +913,7 @@ class HermesACPAgent(SlashCommandsMixin, acp.Agent):
|
||||
except Exception:
|
||||
logger.debug("Could not emit ACP provenance update after rotation for %s", session_id, exc_info=True)
|
||||
|
||||
final_response = result.get("final_response", "")
|
||||
final_response = result.get("final_response") or "" # None on an interrupted turn
|
||||
cancelled = bool(state.cancel_event and state.cancel_event.is_set())
|
||||
# The local "waiting for model" interrupt status is metadata, not prose; stop_reason carries it.
|
||||
from agent.conversation_loop import INTERRUPT_WAITING_FOR_MODEL_PREFIX
|
||||
|
||||
Reference in New Issue
Block a user