fix(agent): continue after recoverable tool loop guardrail - #37490
fix(agent): continue after recoverable tool loop guardrail#37490OmarB97 wants to merge 2 commits into
Conversation
combatsheep
left a comment
There was a problem hiding this comment.
LGTM — the recoverable stall/guardrail handling is well scoped, and the tests cover both the retry path and the hard-stop path.
Reviewer Notes (hermes-local-longctx-ko-mac)Self-review blocked — reviewer and author are both OmarB97. Formal approve/request-changes requires a different GitHub identity. Leaving this comment with findings; the structured verdict follows below. Findings SummaryBlocking (must fix before merge):
Concerns:
Suggestions:
Recommendation: — split into focused PRs, extract past the 1K line limit, address scope concerns. |
|
Reviewer Notes (hermes-local-longctx-ko-mac) Self-review blocked — reviewer and author are both OmarB97. Formal approve/request-changes requires a different GitHub identity. Leaving this comment with findings; the structured verdict follows below. Findings SummaryBlocking (must fix before merge):
Concerns:
Suggestions:
Recommendation: needs-changes — split into focused PRs, extract tool_guardrails.py past the 1K line limit, address scope concerns. |
4de46b9 to
dec03c6
Compare
|
Follow-up pushed in I rebuilt the branch from current upstream Addressed the reviewer notes:
Verification:
The remaining PR state is GitHub-side |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dec03c6 to
6021911
Compare
The conflict-refresh placed the action-preamble stall guardrail ahead of
upstream's refactored codex intermediate-ack gate. Codex acks that also
read like action preambles (e.g. "I'll inspect ...", "I'll check ...")
were then intercepted by the generic guardrail and issued the
ACTION_PREAMBLE_RECOVERY_PROMPT instead of the ack continuation
("[System: Continue now. Execute the required tool calls ...]").
Run the specialized codex-ack gate first and keep the general
action-preamble guardrail as a fallthrough for stalls it does not handle
(non-codex providers, ack_mode="off", or acks its detector misses). This
preserves both upstream's codex-ack behavior and the PR's guardrail
intent. Reset semantics for codex_ack_continuations are unchanged (the
counter is still only reset when neither gate continues the turn).
Fixes the two upstream ack tests that regressed after the refresh:
test_run_conversation_codex_continues_after_ack_stop_message and
test_run_conversation_codex_continues_after_ack_for_directory_listing_prompt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
teknium1
left a comment
There was a problem hiding this comment.
Thanks for narrowing this to the redirect and no-tool recovery paths. The target behavior remains absent on current main: default non-Codex intent-ack continuation is off (agent/agent_runtime_helpers.py:2788), and file/search BLOCKED loop results have no dedicated redirect handling (agent/tool_guardrails.py:298-345).
Problems
agent/conversation_loop.py:5110-5115tags both recovery messages with_action_preamble_recovery, butrun_agent.py:224-235does not include that flag in_EPHEMERAL_SCAFFOLDING_FLAGS. The session flusher skips only recognized scaffolding, so this internal assistant/user pair would persist and replay on resume.agent/conversation_loop.py:5117continues with the incomplete preamble still infinal_response. Current main's analogous ack continuation explicitly clears it atagent/conversation_loop.py:5132(commitf46e7647e); otherwise budget exhaustion can surface the preamble instead of usingagent/turn_finalizer.py:82-96's summary fallback.
Suggested changes
- Register
_action_preamble_recoveryas ephemeral scaffolding and add a persistence regression test. - Clear
final_responsebefore this continuation and add a max-iteration regression test.
Automated hermes-sweeper review.
| ) | ||
| interim_msg = agent._build_assistant_message(assistant_message, "incomplete") | ||
| interim_msg["_action_preamble_recovery"] = True | ||
| messages.append(interim_msg) |
There was a problem hiding this comment.
Blocking: this flag is not in run_agent.py:_EPHEMERAL_SCAFFOLDING_FLAGS, so _persist_session() will retain this internal assistant/user recovery pair. Add _action_preamble_recovery to that classifier and cover persistence after a successful recovery.
| "_action_preamble_recovery": True, | ||
| }) | ||
| agent._stream_needs_break = True | ||
| continue |
There was a problem hiding this comment.
Blocking: clear final_response before continuing. Current main does this for the analogous intent-ack continuation (agent/conversation_loop.py:5132, f46e7647e); otherwise exhausting the continuation budget returns the stale preamble instead of taking the summary fallback.
Summary
mainas a focused replacement for the old stacked PR.tool_reported_loop_blocktool results inside the same user turn by appending model-visible redirect guidance in default soft-guardrail mode.tool_loop_guardrails.hard_stop_enabledis true.Review Follow-Up
main.agent/tool_guardrails.pyis 531 lines.HERMES_LLM_BASE_URL, noHERMES_SKIP_PROFILE_OVERRIDE, no dflash stream-timeout resolver, no progress canary, noretry_on_stall, and nochat()error fallback change.tool_reported_loop_blockpath now respectshard_stop_enabled: default mode redirects/continues; hard-stop mode halts with the existing controlled guardrail response.Verification
python -m pytest tests/agent/test_action_preamble.py tests/agent/test_tool_guardrails.py tests/run_agent/test_tool_call_guardrail_runtime.py -q -o addopts=-> 32 passed, 1 warning.python -m pytest tests/agent/test_action_preamble.py tests/agent/test_tool_guardrails.py tests/run_agent/test_tool_call_guardrail_runtime.py tests/run_agent/test_streaming.py tests/agent/test_local_stream_timeout.py -q -o addopts=-> 108 passed, 1 warning.python -m py_compile agent/action_preamble.py agent/tool_guardrails.py agent/conversation_loop.py run_agent.py-> OK.git diff --check-> OK.Task:
hermes-dflash-post-retry-preamble-stall