fix(tool-guardrails): block deterministic tool retries - #35243
fix(tool-guardrails): block deterministic tool retries#35243EndeavorYen wants to merge 1 commit into
Conversation
|
Thanks for this, and for the thorough writeup — the guardrail approach is sound and the wiring is correct. Closing it though, because the memory half doesn't actually cover the failure mode the issue reports. The matcher keys on No "exceed the limit" substring, so We'll fold the non-retryable-classification idea into a fix that matches both real error strings (add + replace) and tests against the actual emitted text. Your design and authorship informed it — appreciate the contribution. |
Summary
This PR teaches the tool-loop guardrail to treat two deterministic tool failures as non-retryable for identical same-turn retries:
memoryquota overflow, where the store is already full and the same add/replace request cannot succeed unchanged.image_generateempty_response, where the provider returned noimage_generation_callresult for the completed request.The first failure still reaches the model with a clear recovery hint. If the model repeats the same tool call with identical arguments in the same turn, Hermes blocks the duplicate execution before calling the tool again and returns a synthetic guardrail result.
Fixes #35120
Fixes #35121
Why This Matters
Hermes already has general repeated-failure guardrails, but these two failure modes are more specific than "the same tool failed a few times." They are deterministic until something changes:
image_generateresponse with noimage_generation_callis not improved by immediately replaying the exact same request inside the same turn.Without a first-class classification, the agent can spend extra iterations repeating a call that has no new information to offer. In gateway contexts this also produces noisy logs, slower user-facing recovery, and weaker guidance for the model about what action would actually help.
The expected contribution is modest but practical: reduce avoidable tool-loop churn while preserving the agent's ability to recover by changing arguments or strategy.
Existing Upstream Work
This PR is intentionally scoped around the guardrail layer. It does not replace the existing image-generation provider work:
tool_choicerequest-shape failure for theopenai-codeximage generation path, but explicitly leaves the laterempty_response/ noimage_generation_callstage unresolved.image_generatetool results or prevent same-turn duplicate image tool executions.For memory quota overflow, I did not find an existing PR that treats the quota-exceeded result as a non-retryable same-turn tool failure.
Behavior
After this change:
warndecision.This preserves useful retries while avoiding deterministic duplicate work.
Scope
This PR only changes the pure tool-call guardrail controller and its tests:
agent/tool_guardrails.pytests/agent/test_tool_guardrails.pyIt does not change the image-generation provider request shape, memory storage limits, or global hard-stop defaults.
User And Developer Impact
For users, the visible effect should be less "stuck retrying" behavior when Hermes hits a full memory store or an image provider that completed without producing an image call. The agent should move sooner to an actionable recovery path: shorten/replace/remove memory content, skip the memory write, retry image generation later, or change the prompt/model/provider.
For developers and operators, the value is sharper failure semantics in a central guardrail primitive:
memory_quota_exceeded_non_retryableimage_generate_empty_response_non_retryableValidation
./scripts/run_tests.sh tests/agent/test_tool_guardrails.py- 15 passed./scripts/run_tests.sh tests/run_agent/test_tool_call_guardrail_runtime.py tests/agent/test_tool_guardrails.py- 24 passeduv run --with ruff ruff check agent/tool_guardrails.py tests/agent/test_tool_guardrails.py- passedgit diff --check- passed