# Loop detection (/docs/rules/loop-detection)



A budget rule limits spend over time. **Loop detection** is different — it watches *one agent
run* (one trace) and steps in when that single run goes wrong: stuck repeating the same prompt,
or running away past a hard ceiling. It's the **Runaway loops** tab on the Firewall page, with its
own gallery at **Catch a workflow stuck looping**
([app.tokenpolice.ai/firewall/loop/new](https://app.tokenpolice.ai/firewall/loop/new)).

<Callout type="info">
  **A loop rule watches one trace, not a session or a conversation.** A new agent run starts a
  fresh watch with counters at zero. To cap a whole conversation, use a normal firewall rule that
  groups by `session_id` — see [Filter vs apply-to](/docs/rules/match-vs-groupby).
</Callout>

## The two detectors [#the-two-detectors]

TokenPolice ships exactly two loop detectors.

### Exact prompt repeat [#exact-prompt-repeat]

Catches retry storms — the same prompt going out over and over inside one trace. It fingerprints
each prompt's composition, so identical prompts produce identical fingerprints, and looks for a
repeating cycle in the recent calls of that run.

| Setting                                                               | Range                 | Default                               |
| --------------------------------------------------------------------- | --------------------- | ------------------------------------- |
| **Window size** — how many of the run's recent model calls to look at | 1–100                 | 20                                    |
| **Max cycle length** — the longest back-and-forth pattern to look for | 1–5                   | 3                                     |
| **Repeats per cycle length** — how many repetitions count as a loop   | 2 or more, per length | 3 for length 1; 2 for lengths 2 and 3 |

With the defaults, the rule fires when the same prompt goes out 3 times in a row inside one run,
or a 2-step prompt cycle repeats twice.

### Per-trace hard cap [#per-trace-hard-cap]

The circuit breaker: it stops one run from exceeding a ceiling, no matter *why* the run got
expensive. You set a single threshold and its unit.

| Setting       | Options                           |
| ------------- | --------------------------------- |
| **Threshold** | A number                          |
| **Unit**      | **calls**, **USD**, or **tokens** |

## Alert, or stop the run [#alert-or-stop-the-run]

Each loop rule chooses what to do when it trips:

* **Notify only** — log it and alert you, but let the run continue.
* **Block** — stop a later model call on that run.

<Callout type="warn">
  **The stop lands late, by design.*&#x2A; The product says it on every loop template: &#x2A;"The trigger is
  approximate — the call that crosses is allowed; we stop a later call on the same trace. Fast
  back-to-back calls can slip past."* Treat a loop rule as a ceiling on runaway cost, not as an
  exact call counter.
</Callout>

When a loop rule blocks a call, your app receives a `TokenPoliceBlockedError` — the same exception
a budget block raises. If your users see a `429` or a `402`, that is what your own HTTP API chose
to return when it caught the error; TokenPolice does not answer your users. See
[Handling blocks](/docs/sdk/errors).

## Building one [#building-one]

The gallery groups four templates into two categories:

| Category             | Template                                                                    | You set                                                           |
| -------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| **Going in circles** | **Stop the same request on repeat*&#x2A; &#x2A;(most common)*               | Rule name; alert only, or block                                   |
| **Hard limits**      | **Limit how many model calls one trace can make*&#x2A; &#x2A;(most common)* | Rule name; max calls per trace (default 25); alert or block       |
| **Hard limits**      | **Limit how much one trace can spend**                                      | Rule name; max spend per trace (default $5); alert or block       |
| **Hard limits**      | **Limit tokens per trace**                                                  | Rule name; max tokens per trace (default 200,000); alert or block |

Templates default to **alert only**, and while they stay on alert only there is no **When to
turn it on** step at all — the Dry-run / Enforce choice is hidden, because a notify rule has no
enforcement mode to set. Switch the template's action to **block** and the **Dry-run /
Enforce** control appears, to be set before you click **Create rule**. A blocking loop rule
only stops calls when the rule is in Enforce *and* your app's SDK is in enforce mode; see
[Dry-run vs enforce](/docs/concepts/dry-run-vs-enforce).

## You already have three [#you-already-have-three]

Every new workspace is provisioned with three loop rules on its Default Project, all set to
**notify only** so they never stop anything:

| Rule                          | What it watches                                      |
| ----------------------------- | ---------------------------------------------------- |
| **Loop: Exact prompt repeat** | The same prompt sent repeatedly within an agent run. |
| **Loop: Per-trace call cap**  | A ceiling of 25 model calls in a single run.         |
| **Loop: Per-trace cost cap**  | A ceiling of $5.00 in a single run.                  |

They don't count against your plan's rule limit. Leave them alerting, retune them, or delete
them.

## Next [#next]

<Cards>
  <Card title="Block runaway agents" href="/docs/recipes/block-runaway-agents" description="A copy-paste loop rule for the most common case." />

  <Card title="Sessions & traces" href="/docs/concepts/sessions-and-traces" description="What a trace is, and how a run is grouped." />

  <Card title="Traces in the dashboard" href="/docs/dashboard/workflows-sessions-traces" description="Open a run and see the loop fingerprint that fired." />
</Cards>
