Skip to main content
Loop detection prevents agents from calling the same tool repeatedly with no progress.
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.
2

Tune the thresholds

Override the per-agent config before the first tool call.
3

Turn it off

Disable detection for deterministic replay agents that repeat identical calls on purpose.
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

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

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.

Aggressive detection for short-lived agents

Disable a specific detector

Turn off ping_pong when two tools legitimately alternate.

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

Every Agent already has result-aware detection with reasonable thresholds. Only tune when a legitimate use case (long polling, deterministic replay) requires it.
Set agent._loop_detector_config = LoopDetectionConfig(enabled=True, warn_threshold=...) before the first tool call. Disabling entirely removes the safety net.
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.
The detector is stdlib-only (hashlib, json) and lazy-constructed on the first tool call. Agent(...) init is unaffected.
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