Skip to content

feat(guardrails): add tool_repetition limits for identical mutating tool calls - #34778

Closed
ousiaresearch wants to merge 1 commit into
NousResearch:mainfrom
ousiaresearch:fix/tool-repetition-guardrails-34610
Closed

feat(guardrails): add tool_repetition limits for identical mutating tool calls#34778
ousiaresearch wants to merge 1 commit into
NousResearch:mainfrom
ousiaresearch:fix/tool-repetition-guardrails-34610

Conversation

@ousiaresearch

Copy link
Copy Markdown
Contributor

Summary

Adds a fourth guardrail axis — tool_repetition — alongside the existing exact_failure, same_tool_failure, and idempotent_no_progress axes. 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 formally succeeds.

Closes #34610.

Motivation

Today, mutating tools (browser_navigate, terminal, write_file, etc.) are excluded from idempotent_no_progress checks. A browser_navigate to a 404 URL, or terminal('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_guardrails enabled, the agent called browser_navigate with identical args repeatedly until max_iterations was exhausted — no warning, no halt. The fix adds a per-signature repetition counter that fires regardless of success/failure classification.

Changes

agent/tool_guardrails.py

  • Config: New fields tool_repetition_warn_after (default 5) and tool_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).
  • State: reset_for_turn initializes self._tool_repetition_counts: dict[ToolCallSignature, int].
  • after_call: On every non-failed call, increments the per-signature repetition counter. When repetition >= tool_repetition_warn_after, emits tool_repetition_warning. When hard_stop_enabled and repetition >= tool_repetition_block_after, emits tool_repetition_block and sets halt_decision.
  • before_call: When hard_stop_enabled and the per-signature counter is at or above the block threshold, returns tool_repetition_block — preventing the N+1th identical call before execution.

tests/agent/test_tool_guardrails.py

10 new tests (23 total, all passing):

Test What it covers
test_default_tool_repetition_config_values Default thresholds (5/8)
test_config_parses_tool_repetition_thresholds_from_nested_sections warn_after.tool_repetition + hard_stop_after.tool_repetition
test_config_parses_tool_repetition_thresholds_from_flat_keys Legacy flat key compat
test_warns_on_repeated_identical_mutating_tool_call_without_hard_stop Soft warning path (no blocking)
test_hard_stop_blocks_repeated_identical_mutating_tool_call_before_next Hard stop: block on 3rd call, 4th blocked before exec
test_different_args_create_different_signatures_so_repetition_independent Different args = independent counters
test_single_success_between_repeated_calls_resets_repetition_counter Repetition persists per-signature across other tool calls
test_tool_repetition_warns_for_mutating_tools_including_browser_navigate Exact reporter scenario
test_reset_for_turn_clears_tool_repetition_state State reset works
test_failed_call_does_not_count_toward_tool_repetition Failures excluded (handled by existing axes)

Tradeoffs

  • Counting all non-failed calls means the counter increments even when the tool makes progress. The thresholds are intentionally high (5/8 defaults) so normal iteration is unaffected — only tight loops trigger warnings. This mirrors the philosophy of the existing axes.
  • Per-signature, not per-toolterminal('ls') and terminal('pwd') have independent counters. Only truly identical calls (same tool + same args hash) are tracked.
  • No config migration needed — omitted thresholds use defaults, so existing configs continue working unchanged.

Verification

$ python3 -m pytest tests/agent/test_tool_guardrails.py -q -o "addopts="
.......................  [100%]
23 passed in 0.13s

…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
ousiaresearch force-pushed the fix/tool-repetition-guardrails-34610 branch from a95e7df to 2413472 Compare May 29, 2026 18:42
@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels May 29, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #34614 — both add a tool_repetition guardrail axis for identical mutating tool calls. #34614 was filed first and closes #34610.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Tool loop guardrails: add tool_repetition limits for identical mutating tool calls

2 participants