feat(guardrails): add tool_repetition limits for identical mutating tool calls - #34778
Closed
ousiaresearch wants to merge 1 commit into
Closed
feat(guardrails): add tool_repetition limits for identical mutating tool calls#34778ousiaresearch wants to merge 1 commit into
ousiaresearch wants to merge 1 commit into
Conversation
…ool calls Closes NousResearch#34610. Adds a fourth guardrail axis ('tool_repetition') alongside exact_failure, same_tool_failure, and idempotent_no_progress. Tracks repeated calls by ToolCallSignature (tool_name + canonicalized args hash) and triggers warn/block decisions when the same mutating call is made repeatedly without progress — even when each individual call 'succeeds'. Currently, mutating tools are excluded from idempotent_no_progress checks, so a browser_navigate to a 404 or a terminal('ls') in a loop can repeat indefinitely without guardrail intervention. The new axis catches these. Changes: - agent/tool_guardrails.py: Add tool_repetition_warn_after (default 5) and tool_repetition_block_after (default 8) config fields, config parsing from both nested sections and flat keys, per-signature repetition counter, warn/block decisions in after_call, and block gating in before_call (prevents next identical attempt). - tests/agent/test_tool_guardrails.py: 10 tests covering defaults, config parsing (nested + flat), soft-warning path, hard-stop block path, signature independence, persistence across other-tool calls, browser_navigate scenario, reset_for_turn clearing, and failure exclusion (failures handled by existing failure guardrails). All 23 tests pass (13 pre-existing + 10 new).
ousiaresearch
force-pushed
the
fix/tool-repetition-guardrails-34610
branch
from
May 29, 2026 18:42
a95e7df to
2413472
Compare
Collaborator
This was referenced Aug 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a fourth guardrail axis —
tool_repetition— alongside the existingexact_failure,same_tool_failure, andidempotent_no_progressaxes. Tracks repeated calls byToolCallSignature(tool name + canonicalized args hash) and triggers warn/block decisions when the same mutating call is made repeatedly without progress — even when each individual call formally succeeds.Closes #34610.
Motivation
Today, mutating tools (
browser_navigate,terminal,write_file, etc.) are excluded fromidempotent_no_progresschecks. Abrowser_navigateto a 404 URL, orterminal('ls')in a loop, can repeat indefinitely without guardrail intervention as long as the HTTP response is 200 or the exit code is 0.The reporter's scenario: Hermes was asked to navigate to a URL that returned 404. With
tool_loop_guardrailsenabled, the agent calledbrowser_navigatewith identical args repeatedly untilmax_iterationswas exhausted — no warning, no halt. The fix adds a per-signature repetition counter that fires regardless of success/failure classification.Changes
agent/tool_guardrails.pytool_repetition_warn_after(default 5) andtool_repetition_block_after(default 8). Parsed from both nested sections (warn_after.tool_repetition,hard_stop_after.tool_repetition) and legacy flat keys (tool_repetition_warn_after,tool_repetition_block_after).reset_for_turninitializesself._tool_repetition_counts: dict[ToolCallSignature, int].after_call: On every non-failed call, increments the per-signature repetition counter. Whenrepetition >= tool_repetition_warn_after, emitstool_repetition_warning. Whenhard_stop_enabledandrepetition >= tool_repetition_block_after, emitstool_repetition_blockand setshalt_decision.before_call: Whenhard_stop_enabledand the per-signature counter is at or above the block threshold, returnstool_repetition_block— preventing the N+1th identical call before execution.tests/agent/test_tool_guardrails.py10 new tests (23 total, all passing):
test_default_tool_repetition_config_valuestest_config_parses_tool_repetition_thresholds_from_nested_sectionswarn_after.tool_repetition+hard_stop_after.tool_repetitiontest_config_parses_tool_repetition_thresholds_from_flat_keystest_warns_on_repeated_identical_mutating_tool_call_without_hard_stoptest_hard_stop_blocks_repeated_identical_mutating_tool_call_before_nexttest_different_args_create_different_signatures_so_repetition_independenttest_single_success_between_repeated_calls_resets_repetition_countertest_tool_repetition_warns_for_mutating_tools_including_browser_navigatetest_reset_for_turn_clears_tool_repetition_statetest_failed_call_does_not_count_toward_tool_repetitionTradeoffs
terminal('ls')andterminal('pwd')have independent counters. Only truly identical calls (same tool + same args hash) are tracked.Verification