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:
-
Signature Hashing: Each tool call is hashed as SHA256(tool_name + serialized_args). The guard maintains a sliding window of recent hashes.
-
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
-
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
-
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
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_iterationscap (default: 60) inrun_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:Signature Hashing: Each tool call is hashed as
SHA256(tool_name + serialized_args). The guard maintains a sliding window of recent hashes.Pattern Detection:
Escalation Strategy:
Configurable thresholds:
repetition_threshold,window_size,max_warnings_before_block,exempt_toolsKey Design Decisions
Current State in Hermes Agent
What we have:
max_iterationshard cap inrun_agent.py(line 117, default: 60)_handle_max_iterations()method (line 2643) that requests a summary when the cap is hitwhile api_call_count < self.max_iterationsWhat's missing:
processpolling)Real-world impact: When the agent gets stuck (e.g., retrying a
terminalcommand that keeps failing, alternating betweenread_fileandpatchwithout 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:What We'd Need
LoopGuardclass (new file:agent/loop_guard.py)run_agent.pycli-config.yamlfor thresholds and exemptionsPhased Rollout
Phase 1: Basic Repetition Detection
LoopGuardclass with SHA-256 hashing of(tool_name, sorted_args_json)["process"](polling is expected to repeat)Phase 2: Pattern Detection & Escalation
cli-config.yaml:Phase 3: Smart Suggestions
Pros & Cons
Pros
Cons / Risks
read_file) won't be detected as loops. Could add "fuzzy" mode later.Open Questions
References
openfang-runtime/src/loop_guard.rs