Skip to main content

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.
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.

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.

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.
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:
Or activate a named set from the CLI for the entire run:

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 or Rust 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: