From fb0ab27649bac911bec4330d29cf4376d75a2552 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Fri, 29 May 2026 13:20:13 -0700 Subject: [PATCH] fix(agent): register explainer config key + shorten footer prefix Follow-up to the salvaged #34452 turn-completion explainer: - Register display.turn_completion_explainer: True in DEFAULT_CONFIG so the setting is discoverable, matching the file_mutation_verifier precedent. - Shorten the repeated footer prefix from 'Turn ended without a usable reply: ' to 'No reply: ' so the 10 reason variants don't all open with the same 8-word boilerplate. - Update the 7 assertions that referenced the old prefix. --- hermes_cli/config.py | 7 +++++++ run_agent.py | 2 +- tests/run_agent/test_run_agent.py | 10 +++++----- tests/run_agent/test_turn_completion_explainer.py | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index e2c59a694..87aac11b8 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -1202,6 +1202,13 @@ DEFAULT_CONFIG = { # class of over-claim that otherwise forces users to run # `git status` to verify edits landed. Set false to suppress. "file_mutation_verifier": True, + # Turn-completion explainer. When true (default), the agent appends a + # one-line explanation to its final response whenever a turn ends + # abnormally with no usable reply — empty content after retries, a + # partial/truncated stream, a still-pending tool result, or an + # iteration/budget limit. Replaces the bare "(empty)" sentinel so the + # failure isn't silent from the UI's perspective. Set false to suppress. + "turn_completion_explainer": True, "show_cost": False, # Show $ cost in the status bar (off by default) "skin": "default", # UI language for static user-facing messages (approval prompts, a diff --git a/run_agent.py b/run_agent.py index a737fbd78..88b93a0b2 100644 --- a/run_agent.py +++ b/run_agent.py @@ -2189,7 +2189,7 @@ class AIAgent: if reason.startswith("text_response"): return "" - prefix = "⚠️ Turn ended without a usable reply: " + prefix = "⚠️ No reply: " if reason == "empty_response_exhausted": return ( prefix diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index 0da60572c..f5112824a 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -3049,7 +3049,7 @@ class TestRunConversation: # #34452: the bare "(empty)" sentinel is now replaced by a # user-visible end-of-turn explanation so the failure isn't silent. assert result["final_response"] != "(empty)" - assert "without a usable reply" in result["final_response"] + assert "No reply:" in result["final_response"] assert result["turn_exit_reason"] == "empty_response_exhausted" assert result["api_calls"] == 6 # 1 original + 2 prefill + 3 retries @@ -3072,7 +3072,7 @@ class TestRunConversation: assert result["completed"] is True # #34452: explanation replaces the bare "(empty)" sentinel. assert result["final_response"] != "(empty)" - assert "without a usable reply" in result["final_response"] + assert "No reply:" in result["final_response"] assert result["api_calls"] == 6 # 1 original + 2 prefill + 3 retries def test_reasoning_only_prefill_succeeds_on_continuation(self, agent): @@ -3121,7 +3121,7 @@ class TestRunConversation: assert result["completed"] is True # #34452: explanation replaces the bare "(empty)" sentinel. assert result["final_response"] != "(empty)" - assert "without a usable reply" in result["final_response"] + assert "No reply:" in result["final_response"] assert result["api_calls"] == 4 # 1 original + 3 retries def test_truly_empty_response_succeeds_on_nudge(self, agent): @@ -3219,7 +3219,7 @@ class TestRunConversation: assert result["completed"] is True # #34452: explanation replaces the bare "(empty)" sentinel. assert result["final_response"] != "(empty)" - assert "without a usable reply" in result["final_response"] + assert "No reply:" in result["final_response"] def test_empty_response_emits_status_for_gateway(self, agent): """_emit_status is called during empty retries so gateway users see feedback.""" @@ -3248,7 +3248,7 @@ class TestRunConversation: # #34452: explanation replaces the bare "(empty)" sentinel, but the # status emissions during retries are unchanged. assert result["final_response"] != "(empty)" - assert "without a usable reply" in result["final_response"] + assert "No reply:" in result["final_response"] # Should have emitted retry statuses (3 retries) + final failure retry_msgs = [m for m in status_messages if "retrying" in m.lower()] assert len(retry_msgs) == 3, f"Expected 3 retry status messages, got {len(retry_msgs)}: {status_messages}" diff --git a/tests/run_agent/test_turn_completion_explainer.py b/tests/run_agent/test_turn_completion_explainer.py index b120272b0..a04cc1e5e 100644 --- a/tests/run_agent/test_turn_completion_explainer.py +++ b/tests/run_agent/test_turn_completion_explainer.py @@ -159,7 +159,7 @@ def test_run_conversation_empty_exhausted_surfaces_explanation(): # The user must NOT be left with a bare sentinel; the explanation wins. assert result["final_response"] != "(empty)" assert result["final_response"].strip() != "" - assert "without a usable reply" in result["final_response"] + assert "No reply:" in result["final_response"] def test_run_conversation_normal_reply_stays_quiet(): @@ -178,4 +178,4 @@ def test_run_conversation_normal_reply_stays_quiet(): assert result["turn_exit_reason"].startswith("text_response") assert result["final_response"] == "Done." - assert "without a usable reply" not in result["final_response"] + assert "No reply:" not in result["final_response"]