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

# Limits & Enforcement

> How steps, tokens, and timeout work — and how to configure them.

## The three limit types

Every Nanny execution is governed by three independent limits. Any one of them can stop a run.

### Timeout

The wall-clock time limit in milliseconds. The moment the child process has been running
for `timeout` ms, Nanny kills it — regardless of what it's doing.

```toml theme={null}
[limits]
timeout = 30000   # 30 seconds
```

Timeout enforcement requires no instrumentation — it works for any process in any language.

### Steps

The maximum number of agent steps allowed. Requires `#[nanny::tool]` (Rust) or `@tool` (Python) to report tool calls.

```toml theme={null}
[limits]
steps = 100
```

### Tokens

The maximum number of tokens the agent may spend. Each tool declares its token cost per call;
Nanny tracks the running total and stops the moment the budget is exhausted.

```toml theme={null}
[limits]
tokens = 1000
```

## Named limit sets

In a multi-agent system, each agent has a different risk profile. The analysis agent makes expensive API calls — it deserves a tight token ceiling. The reporter just writes a file — it barely needs a budget at all. Named limit sets let each role get exactly the ceiling it deserves, configured once in `nanny.toml`.

```toml theme={null}
[limits]
# Global ceiling — applies to any run not using a named set
steps   = 200
tokens  = 500
timeout = 120000

[limits.ingestion]
steps   = 20
tokens  = 50
timeout = 30000

[limits.analysis]
steps   = 60
tokens  = 200    # tighter — this agent makes expensive calls
timeout = 60000

[limits.visualization]
steps   = 20
tokens  = 100
timeout = 30000

[limits.reporter]
steps   = 20
tokens  = 50     # loose — this agent just writes a file
timeout = 30000
```

Named sets **inherit** from `[limits]` and override only the fields you declare. In the example above, `[limits.ingestion]` inherits from the global `[limits]` and overrides all three fields. A set that only declares `timeout` would inherit `steps` and `tokens` from the base.

Each agent activates its own set via the `@agent("role")` decorator:

```python theme={null}
@agent("analysis")
def run_analysis(path: str): ...    # governed by [limits.analysis]

@agent("reporter")
def run_reporter(output: str): ...  # governed by [limits.reporter]
```

Or activate a named set from the CLI for the entire run:

```bash theme={null}
nanny run --limits=analysis
```

### Named sets share one counter — they are not separate budgets

This is easy to misread, so it's worth being explicit: entering `[limits.analysis]`
does **not** give the analysis role its own fresh 200-token pool. There is exactly
one running total for tokens spent (and steps taken) in a given run, for its entire
lifetime. A named set only changes which ceiling that *same* total is compared
against while the set is active.

Concretely: if `[limits.ingestion]` already spent 40 of its own 50-token ceiling
before handing off to `[limits.analysis]` (tokens = 200), the analysis phase starts
with only 160 tokens of real headroom left, not a fresh 200. If the run had already
spent close to 200 tokens by the time `[limits.reporter]` (tokens = 50) becomes
active, the very next governed call in that phase can fail immediately, even though
the reporter itself hasn't done anything yet.

Size named-set ceilings with this in mind: later scopes in a pipeline need ceilings
large enough to absorb everything earlier scopes may have already spent, not just
their own expected usage in isolation.

If your process runs multiple, logically independent phases and you want each one
to start from a genuinely clean budget, unrelated to what came before, that's a
different thing: a new **run**, not a new named set. See `fresh_run()` in the
[Python](/v0.5/guides/python-sdk#fresh-run-starting-a-fresh-run-mid-process) or
[Rust](/v0.5/guides/rust-sdk#nanny-fresh-run-starting-a-fresh-run-mid-process) SDK
guide for how to do that.

## What happens when a limit is hit

1. Nanny kills the child process immediately — no grace period, no way for the agent to catch or delay the stop.
2. An `ExecutionStopped` event is emitted with the reason.
3. A human-readable message is printed to stderr: `nanny: stopped — TimeoutExpired`.
4. Nanny exits with code `1`.

The stop reasons are:

| Reason              | Trigger                                                                                            |
| ------------------- | -------------------------------------------------------------------------------------------------- |
| `AgentCompleted`    | Process exited cleanly on its own                                                                  |
| `TimeoutExpired`    | Wall-clock timeout exceeded                                                                        |
| `MaxStepsReached`   | Step limit hit                                                                                     |
| `BudgetExhausted`   | Token budget exhausted                                                                             |
| `ToolDenied`        | Tool not in allowlist                                                                              |
| `RuleDenied`        | Custom rule returned denial                                                                        |
| `ManualStop`        | Stopped programmatically                                                                           |
| `ProcessCrashed`    | Child process exited with non-zero code unexpectedly                                               |
| `BridgeUnavailable` | Enforcement was active but became unreachable — Nanny fails closed rather than continue ungoverned |
