Skip to content

Feature: Tool-Call Loop Guard — SHA-256 Pattern Detection for Stuck Agent Loops (inspired by OpenFang) #481

@teknium1

Description

@teknium1

Overview

OpenFang (RightNow-AI/openfang), a Rust-based Agent Operating System, implements a sophisticated Loop Guard system (loop_guard.rs) that detects when an agent is stuck in a repetitive tool-call cycle. Rather than simply counting iterations, it uses SHA-256 hashing of tool call signatures to identify repeating patterns — including ping-pong patterns (A→B→A→B) and single-call loops (A→A→A) — then escalates from warnings to hard blocks.

Hermes Agent currently relies solely on a hard max_iterations cap (default: 60) in run_agent.py. When the agent gets stuck in a loop — retrying the same failing command, alternating between two approaches, or repeatedly checking a condition — it burns through all 60 iterations before being forcibly stopped. This wastes tokens, time, and money. A loop guard would detect repetition early (e.g., after 3-4 identical calls) and inject a warning or break, saving resources and improving UX.

This is distinct from #414 (Iteration Budget Pressure), which warns the LLM that iterations are running low. A loop guard detects behavioral patterns regardless of how many iterations remain — an agent might loop on iteration 5 of 60.


Research Findings

How OpenFang's Loop Guard Works

OpenFang's implementation (openfang-runtime/src/loop_guard.rs) tracks tool calls using:

  1. Signature Hashing: Each tool call is hashed as SHA256(tool_name + serialized_args). The guard maintains a sliding window of recent hashes.

  2. Pattern Detection:

    • Exact repetition: Same hash appears N times in a row (configurable threshold, default: 3)
    • Ping-pong detection: Alternating pattern of 2 hashes (A-B-A-B) detected over a window
    • Poll tool allowlist: Some tools (like status checks) are expected to repeat and are exempted
  3. Escalation Strategy:

    • Warning phase: After detecting a pattern, inject a system message telling the LLM it's looping and suggesting alternative approaches
    • Backoff suggestion: Recommend specific behavioral changes ("try a different approach", "check if the precondition is met")
    • Block phase: After continued looping despite warnings, refuse to execute the repeated call and force the LLM to change strategy
  4. Configurable thresholds: repetition_threshold, window_size, max_warnings_before_block, exempt_tools

Key Design Decisions

  • Hashing instead of string comparison: efficient for large argument payloads
  • Sliding window (not full history): bounded memory, focuses on recent patterns
  • Warning-then-block escalation: gives the LLM a chance to self-correct before forcing a break
  • Tool exemptions: prevents false positives for legitimately repetitive operations (polling, status checks)

Current State in Hermes Agent

What we have:

What's missing:

  • No pattern detection for repeated tool calls
  • No way to detect ping-pong or cycling behavior
  • No escalating intervention (warn → suggest → block)
  • No exemption list for legitimately repetitive tools (e.g., process polling)

Real-world impact: When the agent gets stuck (e.g., retrying a terminal command that keeps failing, alternating between read_file and patch without progress), it currently burns through all remaining iterations. Users see 20+ identical failed attempts before the agent finally stops.


Implementation Plan

Classification: Core Codebase Change

This should be a core codebase change in run_agent.py, not a skill or tool. Reasons:

  • Requires deterministic processing logic that must execute precisely every time
  • Needs to inspect tool call patterns in real-time during the agent loop
  • Must intercept and potentially block tool execution — can't be expressed as instructions

What We'd Need

  • A LoopGuard class (new file: agent/loop_guard.py)
  • Integration point in the tool-call processing section of run_agent.py
  • Configuration in cli-config.yaml for thresholds and exemptions
  • System message injection when patterns are detected

Phased Rollout

Phase 1: Basic Repetition Detection

  • LoopGuard class with SHA-256 hashing of (tool_name, sorted_args_json)
  • Sliding window of last N tool calls (default: 10)
  • Detect exact repetition (same hash 3+ times consecutively)
  • Inject a system message: "You appear to be repeating the same action. The last 3 tool calls were identical. Try a different approach."
  • Log detection events for debugging
  • Exempt list: ["process"] (polling is expected to repeat)

Phase 2: Pattern Detection & Escalation

  • Ping-pong detection (A-B-A-B over window)
  • Cycle detection (A-B-C-A-B-C)
  • Warning → backoff suggestion → block escalation
  • Configurable thresholds via cli-config.yaml:
    loop_guard:
      enabled: true
      window_size: 10
      repetition_threshold: 3
      max_warnings_before_block: 2
      exempt_tools: ["process"]

Phase 3: Smart Suggestions


Pros & Cons

Pros

  • Token savings: Catch loops after 3-4 calls instead of 60, saving potentially thousands of tokens
  • Better UX: Users don't watch the agent spin for minutes on a stuck loop
  • Self-correction: The warning gives the LLM a chance to change strategy — many loops are recoverable
  • Low complexity: SHA-256 hashing + sliding window is ~100-150 lines of Python
  • No false positives with exempt list: Legitimate polling (process status checks) won't trigger

Cons / Risks

  • False positives: Some legitimate workflows repeat similar calls (e.g., batch file processing). Needs careful threshold tuning.
  • Blocking valid retries: Sometimes retrying IS the right strategy (network flakes). The warning-then-block escalation mitigates this.
  • Args sensitivity: Tool calls with slightly different args (e.g., different line numbers in read_file) won't be detected as loops. Could add "fuzzy" mode later.
  • Maintenance: Another piece of middleware in the critical path of the agent loop.

Open Questions

  • Should the loop guard be opt-out (enabled by default) or opt-in? Given the token savings, default-on seems right with easy config to disable.
  • What's the right default repetition threshold? 3 seems safe (3 identical calls is almost always a loop).
  • Should we hash just tool_name for broad detection, or tool_name+args for precise detection? Probably both: broad for "you keep calling terminal" and precise for "you keep running the exact same command."
  • Should loop guard detections be surfaced to the user (e.g., a subtle indicator in CLI output)?

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions