Documentation Index

Fetch the complete documentation index at: /docs/llms.txt

Use this file to discover all available pages before exploring further.

Skip to main content
Loop detection prevents agents from calling the same tool repeatedly with no progress.
from praisonaiagents import Agent

agent = Agent(
    name="assistant",
    instructions="Stop repeating the same failing tool pattern",
)

agent.start("Fix the deployment issue")
Every Agent you create has this protection on by default — no import, no flag. Polling a status endpoint that actually changes state is not flagged; a genuine stall or A -> B -> A -> B oscillation is.
Result-aware tool-loop detection is wired into every Agent as of PR #3005 (release after 2026-07-14). Earlier releases required import praisonaiagents.plugins.loop_detection_plugin to activate the BEFORE_TOOL hook — that plugin still works but is no longer necessary. Upgrade to pick up the built-in detection plus the fixed doom-loop consecutive-failure signal.

Quick Start

1

It's already on

Result-aware tool-loop detection runs on every Agent. No import, no configuration.
from praisonaiagents import Agent

agent = Agent(
    name="assistant",
    instructions="You are a helpful assistant.",
)

# Result-aware tool-loop detection is enabled on every Agent.
# No import, no configuration - just run the agent.
agent.start("Read config.yaml and summarise it.")
2

Tune the thresholds

Override the per-agent config before the first tool call.
from praisonaiagents import Agent
from praisonaiagents.agent.loop_detection import LoopDetectionConfig

agent = Agent(instructions="Poll a job status endpoint.")

agent._loop_detector_config = LoopDetectionConfig(
    enabled=True,
    warn_threshold=5,
    critical_threshold=10,
)

agent.start("Check whether job-42 finished.")
3

Turn it off

Disable detection for deterministic replay agents that repeat identical calls on purpose.
from praisonaiagents import Agent
from praisonaiagents.agent.loop_detection import LoopDetectionConfig

agent = Agent(instructions="Deterministic replay agent")
agent._loop_detector_config = LoopDetectionConfig(enabled=False)
Disabling loop detection removes the safety net for tools that get stuck in identical-argument loops.

How It Works

The detector records each tool call before execution, checks three detectors, then back-fills the result hash after execution so progress breaks the streak. A warning fires once per autonomous turn — the next iteration’s prompt is prefixed with [System: repeated <detector> detected. Try a different approach. …], giving the model one chance to change course before a critical verdict blocks execution entirely.

Detectors

DetectorWhat It DetectsExample
generic_repeatSame tool + identical args N timesread_file("config.py") called 10 times
poll_no_progressSame args AND same result (no progress)check_status("job-1") returns identical “pending” 10 times
ping_pongAlternating A → B → A → B patternTwo tools oscillating back and forth
poll_no_progress uses heuristic tool name matching — tools with “status”, “poll”, “check”, “wait”, “ping”, or “health” in their name are classified as polling tools.

Configuration Options

OptionTypeDefaultDescription
enabledboolTrue (per-Agent) / False (bare LoopDetectionConfig())Agent._ensure_loop_detector() constructs a config with enabled=True. Constructing LoopDetectionConfig() directly still defaults to False, matching the module docstring.
history_sizeint30Sliding window of recent tool calls
warn_thresholdint10Identical calls before warning
critical_thresholdint20Identical calls before blocking (auto-corrected to critical > warn)
detectorsdict[str, bool]{generic_repeat: True, poll_no_progress: True, ping_pong: True}Enable / disable individual detectors
Per-Agent detection is created lazily on the first tool call, so Agent(...) init has zero overhead. Set agent._loop_detector_config = LoopDetectionConfig(...) before the first tool call to override defaults.

Common Patterns

Polling a status endpoint

Raise thresholds and keep detection on.
from praisonaiagents import Agent
from praisonaiagents.agent.loop_detection import LoopDetectionConfig

agent = Agent(instructions="Poll deployment status every second.")
agent._loop_detector_config = LoopDetectionConfig(
    enabled=True,
    warn_threshold=30,
    critical_threshold=60,
)
agent.start("Wait for deployment-42 to finish.")

Aggressive detection for short-lived agents

from praisonaiagents import Agent
from praisonaiagents.agent.loop_detection import LoopDetectionConfig

agent = Agent(instructions="Quick web scraper")
agent._loop_detector_config = LoopDetectionConfig(
    enabled=True,
    warn_threshold=3,
    critical_threshold=5,
)
agent.start("Fetch the homepage title.")

Disable a specific detector

Turn off ping_pong when two tools legitimately alternate.
from praisonaiagents import Agent
from praisonaiagents.agent.loop_detection import LoopDetectionConfig

agent = Agent(instructions="Two-tool workflow")
agent._loop_detector_config = LoopDetectionConfig(
    enabled=True,
    detectors={"generic_repeat": True, "poll_no_progress": True, "ping_pong": False},
)

User Interaction Flow

Here’s what a user sees when the detector fires during an autonomous run.
  • After the warning fires, the next autonomous turn’s prompt is prefixed with [System: repeated <detector> detected. Try a different approach. <message>].
  • If the agent still cannot break out of the pattern and hits critical_threshold on the same (tool, args) fingerprint, the offending call is skipped and the tool result becomes {"error": "...", "loop_blocked": True} — the model sees this on its next observation and typically self-corrects.
  • The whole path is a soft-block, not an exception — trace spans, stream events, and AFTER_TOOL hooks all still fire, so telemetry stays consistent.

Relationship to Loop Guard

Loop Detection is now always-on alongside Loop Guard (docs). Loop Detection catches identical-argument / no-progress / ping-pong fingerprints at any frequency and back-fills result hashes so polling with progress is not flagged. Loop Guard counts per-turn tool calls with idempotent-vs-mutating thresholds. Both fire through the same blocked_result path, so trace spans, stream events, and AFTER_TOOL hooks stay consistent regardless of which one triggers.

Best Practices

Trust the defaults

Every Agent already has result-aware detection with reasonable thresholds. Only tune when a legitimate use case (long polling, deterministic replay) requires it.

Tune thresholds, not the on/off switch

Set agent._loop_detector_config = LoopDetectionConfig(enabled=True, warn_threshold=...) before the first tool call. Disabling entirely removes the safety net.

Polling != loop

The detector uses name + args + result-hash, so a poll that returns changing output does not increment the streak. If your polling tool returns identical output while it waits, disable the poll_no_progress detector for that agent.

Zero overhead

The detector is stdlib-only (hashlib, json) and lazy-constructed on the first tool call. Agent(...) init is unaffected.

Legacy plugin (advanced)

The praisonaiagents.plugins.loop_detection_plugin module still exists as an advanced hook-based override — most users no longer need it. Use it only to override thresholds globally via the BEFORE_TOOL hook without per-agent code.

Loop Guard

Always-on per-turn tool-call guardrails

Autonomy Loop

Self-directed execution and doom loop thresholds
Morty Proxy This is a proxified and sanitized view of the page, visit original site.