> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Escalation Doom-Loop Detection

> Session-level doom-loop detection that records actions and progress markers and recommends a recovery action

The escalation `DoomLoopDetector` watches a long-running agent session for stuck states — repeated actions, repeated failures, no progress, resource exhaustion, or repeated output — and recommends a `RecoveryAction` so the agent can break out instead of burning its whole iteration budget.

<Tabs>
  <Tab title="Python">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents.escalation import DoomLoopDetector

    detector = DoomLoopDetector()
    detector.start_session()

    detector.record_action("read_file", {"path": "foo.py"}, "content", success=True)
    detector.record_action("read_file", {"path": "foo.py"}, "content", success=True)
    detector.record_action("read_file", {"path": "foo.py"}, "content", success=True)

    if detector.is_doom_loop():
        print(detector.get_recovery_action())  # e.g. RecoveryAction.RETRY_DIFFERENT
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import { DoomLoopDetector, RecoveryAction } from 'praisonai';

    const detector = new DoomLoopDetector();
    detector.startSession();

    detector.recordAction('read_file', { path: 'foo.py' }, 'content', true);
    detector.recordAction('read_file', { path: 'foo.py' }, 'content', true);
    detector.recordAction('read_file', { path: 'foo.py' }, 'content', true);

    if (detector.isDoomLoop()) {
      console.log(detector.getRecoveryAction()); // e.g. RecoveryAction.RETRY_DIFFERENT
    }
    ```
  </Tab>
</Tabs>

The detector consumes the actions and progress markers you feed it, classifies the stuck state into a `DoomLoopType`, and maps that to a `RecoveryAction` on the recovery ladder.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    IN["📥 Actions &<br/>progress markers"] --> DET["🛡️ DoomLoopDetector"]
    DET --> TYPE["🔎 DoomLoopType"]
    TYPE --> REC["🎯 RecoveryAction"]

    classDef input fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef detector fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef type fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef rec fill:#10B981,stroke:#7C90A0,color:#fff

    class IN input
    class DET detector
    class TYPE type
    class REC rec
```

<Note>
  This is the **escalation** doom-loop subsystem (session-level, recommends a `RecoveryAction`). It is separate from the always-on per-agent [Loop Detection](/docs/features/doom-loop-detection) that inspects tool-call fingerprints. Use this page for `DoomLoopDetector` / `DoomLoopConfig`.
</Note>

## Quick Start

<Steps>
  <Step title="Construct with defaults">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents.escalation import DoomLoopDetector

    detector = DoomLoopDetector()
    detector.start_session()
    ```
  </Step>

  <Step title="Record actions and progress markers">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    detector.record_action("search", {"q": "config"}, "no results", success=False)
    detector.mark_progress("wrote initial draft")  # call on real progress
    ```
  </Step>

  <Step title="Check for a loop and get the recovery action">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    if detector.is_doom_loop():
        event = detector.get_loop_event()
        print(event.loop_type, event.description)
        print(detector.get_recovery_action())
    ```
  </Step>
</Steps>

***

## How It Works

The detector keeps an ordered history of `record_action(...)` calls plus a separate list of `mark_progress(...)` markers. After each recorded action it re-runs every check; `is_doom_loop()` returns `True` as soon as any check fires, and `get_loop_type()` reports which one.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant Detector as DoomLoopDetector

    Agent->>Detector: record_action(...)
    Detector->>Detector: run all checks
    Detector-->>Agent: is_doom_loop()? loop_type?
    Agent->>Detector: get_recovery_action()
    Detector-->>Agent: RecoveryAction
```

### Loop types

| `DoomLoopType` | What it detects |
| - | - |
| `REPEATED_ACTION` | Same action (name + args) repeated `max_identical_actions` times, or same action type repeated `max_similar_actions` times |
| `REPEATED_FAILURE` | `max_consecutive_failures` failing actions in a row |
| `NO_PROGRESS` | `max_no_progress_steps` steps with identical results and no recent progress marker |
| `CIRCULAR_PLAN` | Plan loops back to a previous state |
| `RESOURCE_EXHAUSTION` | Elapsed time exceeds `max_total_time` |
| `REPEATED_OUTPUT` | Model output repeats — `max_repeated_chunks`+ identical text chunks (content chanting) |

### Recovery ladder

`get_recovery_action()` walks a ladder based on how many recovery attempts have already been made:

| `RecoveryAction` | When it is chosen |
| - | - |
| `CONTINUE` | No loop detected |
| `RETRY_DIFFERENT` | First recovery attempt — try a different approach |
| `ESCALATE_MODEL` | Second attempt, when `escalate_on_loop=True` — try a stronger model |
| `REQUEST_HELP` | After escalation — ask the user for clarification |
| `ABORT` | `RESOURCE_EXHAUSTION` always aborts; also when `max_recovery_attempts` is reached |

***

## Configuration Options

Pass a `DoomLoopConfig` to tune thresholds:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.escalation import DoomLoopDetector, DoomLoopConfig

detector = DoomLoopDetector(
    DoomLoopConfig(
        max_no_progress_steps=3,
        max_consecutive_failures=2,
        escalate_on_loop=True,
    )
)
```

| Option | Type | Default | Description |
| - | - | - | - |
| `max_identical_actions` | `int` | `3` | Max identical consecutive actions |
| `max_similar_actions` | `int` | `5` | Max same-type actions (fuzzy match) |
| `max_consecutive_failures` | `int` | `3` | Max failures before intervention |
| `max_no_progress_steps` | `int` | `5` | Max steps without progress |
| `max_time_per_action` | `float` | `60.0` | Max seconds per action |
| `max_total_time` | `float` | `300.0` | Max total execution time (seconds) |
| `enable_auto_recovery` | `bool` | `True` | Auto-attempt recovery |
| `max_recovery_attempts` | `int` | `2` | Max recovery attempts before abort |
| `escalate_on_loop` | `bool` | `True` | Escalate model on loop detection |
| `initial_backoff` | `float` | `1.0` | Initial backoff in seconds |
| `backoff_multiplier` | `float` | `2.0` | Backoff multiplier |
| `max_backoff` | `float` | `30.0` | Maximum backoff in seconds |
| `max_repeated_chunks` | `int` | `8` | Max identical output chunks before flagging |
| `content_chunk_size` | `int` | `50` | Sliding-window chunk size (chars) |

<Note>
  `similarity_threshold` (`0.85`) is **retained for backward compatibility only** and is not consulted by any current detector — fuzzy result-similarity is handled by the result-aware [Loop Detection](/docs/features/doom-loop-detection) subsystem.
</Note>

***

## How progress markers work

`mark_progress()` records a `(marker, timestamp)` tuple. When checking `NO_PROGRESS`, the detector only counts markers within the **current no-progress window** — markers older than the window boundary (the timestamp of the action immediately preceding the window) are filtered out.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.escalation import DoomLoopDetector, DoomLoopConfig

detector = DoomLoopDetector(DoomLoopConfig(max_no_progress_steps=5))
detector.start_session()

detector.mark_progress("read config.yaml")  # early success

# ... many later steps that repeat the same unproductive result ...
for _ in range(5):
    detector.record_action("retry", {"n": 1}, "same error", success=False)

detector.is_doom_loop()  # True — the early marker no longer suppresses NO_PROGRESS
```

Before this recency filter ([PR #3960](https://github.com/MervinPraison/PraisonAI/pull/3960)), one early `mark_progress(...)` disabled `NO_PROGRESS` detection for the rest of the run, no matter how long the agent then spun on unproductive work. Now `NO_PROGRESS` fires correctly on long runs where an early success previously suppressed it forever.

***

## Common Patterns

### Drive recovery from the detected action

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.escalation import DoomLoopDetector
from praisonaiagents.escalation.doom_loop import RecoveryAction

detector = DoomLoopDetector()
detector.start_session()

# ... record actions during the run ...

if detector.is_doom_loop():
    action = detector.get_recovery_action()
    if action == RecoveryAction.RETRY_DIFFERENT:
        detector.increment_recovery()
        # switch strategy and continue
    elif action == RecoveryAction.ESCALATE_MODEL:
        detector.increment_recovery()
        # swap in a stronger model
    elif action in (RecoveryAction.REQUEST_HELP, RecoveryAction.ABORT):
        # stop and surface to the user
        pass
```

### Inspect session statistics

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
stats = detector.get_stats()
print(stats["total_actions"], stats["failed_actions"], stats["progress_markers"])
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Call mark_progress() on real progress only">
    Mark progress when the agent genuinely advances (file modified, test passed, sub-goal reached) — not on every log line. Markers gate `NO_PROGRESS` detection, so noisy markers hide real stalls.
  </Accordion>

  <Accordion title="Keep max_no_progress_steps small">
    A smaller `max_no_progress_steps` catches stalls sooner. Start at `3–5` for autonomous runs and raise it only if you see false positives.
  </Accordion>

  <Accordion title="Increment recovery between attempts">
    Call `increment_recovery()` after acting on a recommendation so the ladder advances (`RETRY_DIFFERENT → ESCALATE_MODEL → REQUEST_HELP`) and eventually aborts at `max_recovery_attempts`.
  </Accordion>

  <Accordion title="Reset per session">
    Call `start_session()` at the top of each run to clear action history, markers, and recovery counters.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Loop Detection" icon="rotate" href="/docs/features/doom-loop-detection">
    The always-on, per-agent result-aware tool-loop detector
  </Card>

  <Card title="Autonomy Loop" icon="repeat" href="/docs/features/autonomy-loop">
    `doom_loop_threshold` and the `doom_loop` completion reason
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.