hermes - 💡(How to fix) Fix [Bug]: Tool loop guardrails: add tool_repetition limits for identical mutating tool calls

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Repeated mutating tool calls that “succeed” but make no progress (e.g. browser_navigate to a 404 URL) are not currently caught by tool_loop_guardrails. Today, guardrails only track:

  • Exact failures (exact_failure_*)
  • Same-tool failures (same_tool_failure_*)
  • Idempotent no-progress for read-only tools (idempotent_no_progress_*)

This means a mutating tool that returns a non-error payload but never progresses can loop indefinitely until max_iterations is exhausted.

I had Hermes generate and apply a patch that adds an explicit tool_repetition dimension to the guardrails and it appears to work correctly in my tests.

Error Message

This means a mutating tool that returns a non-error payload but never progresses can loop indefinitely until max_iterations is exhausted.

  • Above warn_after.tool_repetition, the controller returns a warn decision.
  • Successful browser_navigate calls (including 404-style “success” payloads) do not increment any no-progress counter, only failure counters when they error.

Additional Logs / Traceback (optional)

The implementation reuses ToolCallSignature.from_call(...) and parallels the existing exact_failure / same_tool_failure logic, but based purely on “same tool + same args + same result pattern” rather than on error classification.

Root Cause

Root Cause Analysis (optional)

Fix Action

Fix / Workaround

I had Hermes generate and apply a patch that adds an explicit tool_repetition dimension to the guardrails and it appears to work correctly in my tests.

Patch details

  1. Start Hermes with tool_loop_guardrails.warnings_enabled: true, hard_stop_enabled: true, and a low warn_after.tool_repetition / hard_stop_after.tool_repetition (e.g. 2 / 3).
  2. Ask the agent to navigate repeatedly to a URL that consistently returns a 404 or other non-progressing page via browser_navigate.
  3. On v0.15.1, the agent will continue to call browser_navigate with identical args without any tool_repetition warning or halt, as long as the calls are not classified as failures.
  4. With the patch applied, after N repeated identical calls the controller returns a warning and then a block/halt decision for that signature.

Code Example

Report       https://paste.rs/2xu8F
  agent.log    https://paste.rs/BtCUZ
  gateway.log  https://paste.rs/rzvvb

---



---

tool_repetition_warn_after: int = 5
tool_repetition_block_after: int = 8

---

tool_repetition_warn_after=_positive_int(
    warn_after.get("tool_repetition", data.get("tool_repetition_warn_after")),
    defaults.tool_repetition_warn_after,
),
tool_repetition_block_after=_positive_int(
    hard_stop_after.get("tool_repetition", data.get("tool_repetition_block_after")),
    defaults.tool_repetition_block_after,
),

---

self._tool_repetition_counts: dict[ToolCallSignature, int] = {}
RAW_BUFFERClick to expand / collapse

Bug Description

Summary

Repeated mutating tool calls that “succeed” but make no progress (e.g. browser_navigate to a 404 URL) are not currently caught by tool_loop_guardrails. Today, guardrails only track:

  • Exact failures (exact_failure_*)
  • Same-tool failures (same_tool_failure_*)
  • Idempotent no-progress for read-only tools (idempotent_no_progress_*)

This means a mutating tool that returns a non-error payload but never progresses can loop indefinitely until max_iterations is exhausted.

I had Hermes generate and apply a patch that adds an explicit tool_repetition dimension to the guardrails and it appears to work correctly in my tests.

Patch details

Environment

  • Hermes Agent version: 0.15.1
  • Mode: CLI (local)
  • Config: tool_loop_guardrails enabled with custom warn_after.tool_repetition / hard_stop_after.tool_repetition thresholds

Proposal

If this behavior aligns with your design for mutating-tool loop guardrails, I’m happy to convert this into a proper PR. The change is localized to agent/tool_guardrails.py and adds one new axis (tool_repetition) alongside the existing three.

Steps to Reproduce

  1. Start Hermes with tool_loop_guardrails.warnings_enabled: true, hard_stop_enabled: true, and a low warn_after.tool_repetition / hard_stop_after.tool_repetition (e.g. 2 / 3).
  2. Ask the agent to navigate repeatedly to a URL that consistently returns a 404 or other non-progressing page via browser_navigate.
  3. On v0.15.1, the agent will continue to call browser_navigate with identical args without any tool_repetition warning or halt, as long as the calls are not classified as failures.
  4. With the patch applied, after N repeated identical calls the controller returns a warning and then a block/halt decision for that signature.

Expected Behavior

tool_loop_guardrails should support a tool_repetition threshold, so that:

  • Identical tool signatures (same tool name + canonicalized args) that keep returning the same result are counted.
  • Above warn_after.tool_repetition, the controller returns a warn decision.
  • If hard_stop_enabled is true and the count exceeds hard_stop_after.tool_repetition, the controller can block or halt the loop, similar to the existing axes.

Actual Behavior

  • browser_navigate is listed under MUTATING_TOOL_NAMES and is therefore excluded from the idempotent_no_progress checks.
  • Successful browser_navigate calls (including 404-style “success” payloads) do not increment any no-progress counter, only failure counters when they error.
  • As a result, the controller never warns or halts on “same tool + same args + same response” loops for mutating tools; it only reacts when they are classified as failures.

Affected Component

Tools (terminal, file ops, web, code execution, etc.)

Messaging Platform (if gateway-related)

No response

Debug Report

Report       https://paste.rs/2xu8F
  agent.log    https://paste.rs/BtCUZ
  gateway.log  https://paste.rs/rzvvb

Operating System

Docker

Python Version

No response

Hermes Version

No response

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

No response

Proposed Fix (optional)

I patched agent/tool_guardrails.py to:

  1. Extend the config

Add two new fields to ToolCallGuardrailConfig and wire them to tool_loop_guardrails.warn_after.tool_repetition and tool_loop_guardrails.hard_stop_after.tool_repetition:

tool_repetition_warn_after: int = 5
tool_repetition_block_after: int = 8

plus the corresponding parsing in from_mapping(...):

tool_repetition_warn_after=_positive_int(
    warn_after.get("tool_repetition", data.get("tool_repetition_warn_after")),
    defaults.tool_repetition_warn_after,
),
tool_repetition_block_after=_positive_int(
    hard_stop_after.get("tool_repetition", data.get("tool_repetition_block_after")),
    defaults.tool_repetition_block_after,
),
  1. Track per-signature repetitions

In ToolCallGuardrailController.reset_for_turn I added a per-signature repetition counter:

self._tool_repetition_counts: dict[ToolCallSignature, int] = {}
  1. Increment on every successful call

In after_call(...), when a call does not fail, I increment the repetition counter keyed by ToolCallSignature (tool name + args hash) regardless of idempotent/mutating classification, and use tool_repetition_* thresholds to emit warnings or hard stops for repeated identical calls.

The implementation reuses ToolCallSignature.from_call(...) and parallels the existing exact_failure / same_tool_failure logic, but based purely on “same tool + same args + same result pattern” rather than on error classification.

tool_guardrails.py

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix [Bug]: Tool loop guardrails: add tool_repetition limits for identical mutating tool calls