@tool, @rule, and @agent decorators that enforce limits per function call.
Passthrough mode
When running outsidenanny run, every decorator is a no-op. The function executes normally with no enforcement overhead:
@tool — declare a governed tool
Mark a function as a tool that Nanny should track and charge against the budget:
fetch_page:
- Nanny checks: is
fetch_pagein the[tools] allowedlist? - Nanny checks: has
fetch_pageexceeded[tools.fetch_page] max_calls? - Nanny charges 10 tokens against the budget.
- If any check fails, a
NannyStopexception is raised — the function body never runs.
Tokens
Thetokens argument is required. Set it to 0 for tools you want tracked but not charged:
Matching the tool allowlist
The tool name used for allowlist checks is the function name as declared in Python:
instrument — automatic LLM token tracking
Call nanny_sdk.instrument(client) once at agent startup to automatically report LLM token usage to Nanny’s budget. Every completion response is intercepted and its token counts are debited from the same ledger that @tool charges against.
- OpenAI, Groq, Together AI, Azure OpenAI, LiteLLM — any client with a
chat.completions.createmethod - Anthropic —
client.messages.create - Mistral —
client.chat.complete - Google Gemini (
google-genaiSDK) —client.models.generate_content - Cohere v2 —
cohere.ClientV2
instrument returns the same client unchanged — it patches the method in-place. In passthrough mode (outside nanny run), it is a no-op: no wrapping, no overhead.
For providers that report prompt-caching usage (OpenAI, Anthropic, DeepSeek, Gemini), instrument also captures it and reports it as cache_read/cache_write — a finer split of input, never additional tokens beyond it, and never used for enforcement (Nanny still debits input + output exactly as before). Every provider names and shapes this data differently, so instrument normalizes each one’s own vocabulary into these two generic fields; absent, not zero, for a provider/response that doesn’t report cache usage at all. Exists purely so a downstream cost calculator can price cache-hit tokens at their real, much cheaper rate — see LlmUsageRecorded for the full field reference.
@rule — declare an enforcement rule
A rule is a function that returns a verdict on whether execution should continue.
Return True to allow, False to deny:
False, Nanny raises:
PolicyContext fields
Thectx parameter gives you a snapshot of the current execution state:
Rules are evaluated before the tool runs —
requested_tool is set to the tool name being checked. Use last_tool_args for content-based enforcement:
step_count, tokens_spent, tool_call_counts, and tool_call_history reflect the actual execution state at the moment your rule runs.

@agent — activate named limits for a scope
In a multi-agent system, each agent has a different role and a different risk profile. The analysis agent makes expensive API calls and deserves a tight token ceiling. The reporter just writes a file and barely needs a budget at all. @agent activates the right named limit set when each role runs, then reverts automatically when it’s done.
[limits] and overrides only the declared fields.
Works identically for async functions. Limits revert on exit whether the function returns normally or raises.

fresh_run — starting a fresh run mid-process
@agent changes which ceiling the run’s one running total is checked
against; it does not give a role its own budget (see
Named sets share one counter).
If your process genuinely runs multiple, independent phases back to back,
say, a research phase that hands off to a drafting phase, or a long-lived
server that should give each incoming request its own clean slate, and you
want each one to start from zero rather than inherit whatever an earlier
phase already spent, that’s a new run, Nanny’s real unit of governance:
one cumulative counter, one stop state, “a stop is final.”
nanny run --serve
/ --join): the server keys independent state per run, so a stop in the run
you just left has zero effect on the one you’re starting. Under local
nanny run (no --serve), one process is already always exactly one run,
so this is a safe no-op there, code that might run under either mode
doesn’t need to branch on which one it’s in.
NANNY_RUN_ID
environment variable directly, an internal detail the client happens to read
fresh on every call, never documented as something to rely on. fresh_run()
is that same mechanism, given a real, discoverable name.
What happens on stop
When Nanny stops execution, it raises aNannyStop exception. All stop reasons are distinct subclasses:
BridgeUnavailable is raised when Nanny’s enforcement layer is active but unreachable during rule evaluation or a tool call. Nanny fails closed — the agent does not continue ungoverned. Like all NannyStop subclasses it extends BaseException, so it propagates through broad except Exception handlers in agent frameworks without being swallowed.
Under plain nanny run (single process, no --serve), you do not need to handle stop reasons in most agent code. They propagate up the call stack and terminate the process, since nanny run is watching that process and exits it for you. Catching them there is useful mainly in test code and at the CLI entry point.
This is not true once you’re running under nanny run --serve / --join. When your process joins a governance server, the wrapping nanny run --join command does not watch your process for stops at all: no polling loop, no kill on budget exhaustion, nothing. That’s deliberate. The stop guarantee is that a stop ends one run, not your whole process, so a long-lived server (a web app, a Discord bot, anything handling more than one request per process) has to keep running after another request’s run gets stopped. Nothing does that for you. If a request handler lets NannyStop propagate uncaught, your framework’s own crash behavior decides what happens next, which for many servers means the whole process going down over one governed request. Catch it at the boundary of whatever “one governed unit of work” means for your app, typically each request handler, and turn it into a failure response for that one request, not an unhandled exception:
--serve/--join.

Complete example
Multi-agent pattern
The canonical use case: a pipeline where each agent has a specific role, a specific budget, and access to only the tools it needs. This is themetrics_crew pattern — four specialized agents, each governed independently.
- A ceiling tailored to each role: the analysis agent gets a tighter check than the reporter while it’s running, expressed once in
nanny.toml. Worth being precise about what this is not: tokens spent is one running total for the whole run, not a separate pool per role, so if analysis exhausts its own ceiling, the run stops there, the reporter never gets to run at all. See Named sets share one counter for the full explanation, andfresh_run()below if you actually want each phase to have its own independent budget. - Least-privilege tool access: each agent only receives the tools it needs; calling outside its role raises
ToolDeniedimmediately - Loop detection: the
@rulefires client-side before Nanny’s enforcement layer is contacted — the denied tool never runs and no tokens are charged - Full audit trail: every tool call, every limit activation, every stop reason logged to NDJSON

examples/python/metrics_crew for the complete working implementation of this pattern with four agents, Plotly chart generation, and a full incident report output.
Framework integration
LangChain
Stack@lc_tool (outer) and @nanny_tool (inner). LangChain registers the function for dispatch; Nanny intercepts every call regardless of which model or API style invoked it:
tool.run(args) → LangChain validates args → Nanny wrapper intercepts → enforcement check → if allowed, file is read.
CrewAI
Same stacking pattern. CrewAI’s@tool decorator and Nanny’s @tool decorator both wrap the function — Nanny’s wrapper fires on every tool.run() call inside the crew:
examples/python/dev_assist for a complete LangGraph integration and examples/python/metrics_crew for the canonical multi-agent governance example with four specialized agents, per-role limits, and per-role tool allowlists.