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

# Stop Runaway Agents with AgentDbg Run Guardrails

> Configure AgentDbg guardrails to automatically abort agent runs that loop indefinitely, exceed LLM call budgets, or run longer than expected.

AgentDbg guardrails give you an automatic circuit breaker for the most common ways an agent run goes wrong: infinite loops, runaway token spend, and runs that simply take too long. They are designed for local debugging loops — every guardrail fires only when you opt in, records full trace evidence before aborting, and raises a typed exception so your code can respond deliberately.

## What guardrails do

When a configured threshold is crossed, AgentDbg:

<Steps>
  <Step title="Records evidence">
    Writes the event or warning that triggered the guardrail using the normal trace format, so you have full context in the timeline.
  </Step>

  <Step title="Raises a typed exception">
    Raises `AgentDbgLoopAbort` (for loop detection) or `AgentDbgGuardrailExceeded` (for count and duration limits).
  </Step>

  <Step title="Finalizes the run">
    Records an `ERROR` event, then writes `RUN_END` with `status="error"` so the run is closed cleanly.
  </Step>
</Steps>

The `ERROR` payload includes `guardrail`, `threshold`, `actual`, `error_type`, and `message` — so the UI and raw trace both tell you not just that the run failed, but which guardrail fired and what the actual value was.

***

## Available guardrails

| Parameter                      | Type            | Default | What it does                                                        |
| ------------------------------ | --------------- | ------- | ------------------------------------------------------------------- |
| `stop_on_loop`                 | `bool`          | `False` | Abort when loop detection emits `LOOP_WARNING`                      |
| `stop_on_loop_min_repetitions` | `int`           | `3`     | Minimum repeated pattern count required before `stop_on_loop` fires |
| `max_llm_calls`                | `int \| None`   | `None`  | Abort after more than N LLM calls                                   |
| `max_tool_calls`               | `int \| None`   | `None`  | Abort after more than N tool calls                                  |
| `max_events`                   | `int \| None`   | `None`  | Abort after more than N total events                                |
| `max_duration_s`               | `float \| None` | `None`  | Abort when elapsed run time reaches this limit                      |

<Note>
  Count-based guardrails (`max_llm_calls`, `max_tool_calls`, `max_events`) trigger at N+1, not N — the event that crosses the limit is recorded before the run is aborted. `max_duration_s` triggers when elapsed time is greater than or equal to the configured value.
</Note>

***

## Exceptions

### `AgentDbgLoopAbort`

Raised when `stop_on_loop=True` and loop detection fires above the `stop_on_loop_min_repetitions` threshold. This is a subclass of `AgentDbgGuardrailExceeded`, so you can catch either.

```python theme={null}
from agentdbg import AgentDbgLoopAbort, trace

@trace(stop_on_loop=True)
def run_agent():
    ...

try:
    run_agent()
except AgentDbgLoopAbort as exc:
    print(f"Stopped because of a loop: {exc}")
    print(f"  guardrail: {exc.guardrail}")  # "stop_on_loop"
    print(f"  threshold: {exc.threshold}")  # minimum repetitions
    print(f"  actual:    {exc.actual}")     # observed repetition count
```

### `AgentDbgGuardrailExceeded`

Raised for all count and duration guardrails. Exposes the following attributes:

| Attribute   | Type           | Description                                   |
| ----------- | -------------- | --------------------------------------------- |
| `guardrail` | `str`          | Which guardrail fired, e.g. `"max_llm_calls"` |
| `threshold` | `int \| float` | The configured limit                          |
| `actual`    | `int \| float` | The value that exceeded the limit             |
| `message`   | `str`          | Human-readable description                    |

```python theme={null}
from agentdbg import AgentDbgGuardrailExceeded, traced_run

try:
    with traced_run(max_llm_calls=8, max_tool_calls=12):
        ...
except AgentDbgGuardrailExceeded as exc:
    print(exc.guardrail, exc.threshold, exc.actual)
```

***

## How guardrails appear in traces

### Loop guardrail

When `stop_on_loop=True` and the loop detector fires:

1. AgentDbg writes `LOOP_WARNING`
2. Raises `AgentDbgLoopAbort`
3. Writes `ERROR`
4. Writes `RUN_END(status="error")`

### Count and duration guardrails

For `max_llm_calls`, `max_tool_calls`, `max_events`, and `max_duration_s`:

1. AgentDbg writes the event that crossed the limit (e.g. the LLM call that put you at N+1)
2. Raises `AgentDbgGuardrailExceeded`
3. Writes `ERROR`
4. Writes `RUN_END(status="error")`

This means you always have full evidence of the specific step that tripped the guardrail when you open the timeline.

***

## Configuration

Guardrails can be configured in four places. The highest-precedence source wins:

1. Arguments passed directly to `@trace(...)` or `traced_run(...)`
2. Environment variables
3. Project YAML: `.agentdbg/config.yaml`
4. User YAML: `~/.agentdbg/config.yaml`

### Decorator and context manager

```python theme={null}
from agentdbg import trace, traced_run

@trace(stop_on_loop=True, max_llm_calls=50)
def guarded_fn():
    ...

with traced_run(stop_on_loop=True, max_llm_calls=50):
    ...
```

### Environment variables

```bash theme={null}
export AGENTDBG_STOP_ON_LOOP=1
export AGENTDBG_STOP_ON_LOOP_MIN_REPETITIONS=3
export AGENTDBG_MAX_LLM_CALLS=50
export AGENTDBG_MAX_TOOL_CALLS=50
export AGENTDBG_MAX_EVENTS=200
export AGENTDBG_MAX_DURATION_S=60
```

### YAML config

```yaml theme={null}
# .agentdbg/config.yaml or ~/.agentdbg/config.yaml
guardrails:
  stop_on_loop: true
  stop_on_loop_min_repetitions: 3
  max_llm_calls: 50
  max_tool_calls: 50
  max_events: 200
  max_duration_s: 60
```

***

## Examples

### Stop a looping agent immediately

```python theme={null}
from agentdbg import AgentDbgLoopAbort, record_llm_call, record_tool_call, trace


@trace(stop_on_loop=True)
def run_agent():
    for _ in range(10):
        record_tool_call("search_db", args={"q": "pricing"}, result={"hits": 3})
        record_llm_call(model="gpt-4.1", prompt="Summarize", response="Retrying...")


try:
    run_agent()
except AgentDbgLoopAbort as exc:
    print(f"Stopped because of a loop: {exc}")
```

### Cap LLM and tool usage during development

```python theme={null}
from agentdbg import AgentDbgGuardrailExceeded, record_llm_call, record_tool_call, traced_run


try:
    with traced_run(
        name="react_debug",
        max_llm_calls=8,
        max_tool_calls=12,
        max_events=40,
        max_duration_s=30,
    ):
        # ... your agent loop ...
        record_llm_call(model="gpt-4.1", prompt="...", response="...")
        record_tool_call(name="search", args={"q": "docs"}, result={"hits": 2})
except AgentDbgGuardrailExceeded as exc:
    print(exc.guardrail, exc.threshold, exc.actual)
```

<Tip>
  Practical starting points for local development:

  * `stop_on_loop=True` for any ReAct-style or planner/executor loop
  * `max_llm_calls=10` to `30` when iterating on prompts
  * `max_tool_calls=10` to `25` for tool-heavy debugging sessions
  * `max_events=50` to `200` for a hard ceiling on trace file size
  * `max_duration_s=15` to `60` for runs that should finish quickly

  Use tighter limits in tests and demos, and looser limits when you are intentionally exploring a larger workflow.
</Tip>
