Skip to main content
The Rust SDK brings Nanny’s enforcement boundary into your code. Instead of relying only on process-level limits, you mark individual functions as tools and rules — Nanny governs each call before it executes.

Installation

The SDK ships inside the same crate as the CLI binary. Add it to your project:
cargo add nannyd
Then import what you need:
use nanny::{tool, rule, agent, PolicyContext};

Passthrough mode

If your agent runs without nanny run, every macro is a no-op. The function executes normally with no enforcement overhead:
# Governed — enforcement active (reads [start].cmd from nanny.toml)
nanny run

# Not governed — macros silent, agent runs normally
cargo run
This is safe to ship to production. The instrumentation only activates when nanny run is present.

#[tool] — declare a governed tool

Mark a function as a tool that Nanny should track and charge against the budget:
use nanny::tool;

#[tool(cost = 10)]
fn fetch_page(url: &str) -> String {
    // HTTP call, file read, LLM call, or any side-effecting operation
    reqwest::blocking::get(url).unwrap().text().unwrap()
}
When the agent calls fetch_page:
  1. Nanny checks: is fetch_page in the [tools] allowed list?
  2. Nanny checks: has fetch_page exceeded [tools.fetch_page] max_calls?
  3. Nanny charges 10 cost units against the budget.
  4. If any check fails, execution stops immediately — the function body never runs.

Cost

The cost argument is required. It is the number of cost units charged per call. Set it to 0 for tools you want tracked but not charged:
#[tool(cost = 0)]
fn log_step(msg: &str) { ... }

Matching the tool allowlist

The tool name used for allowlist checks is the function name as declared in Rust:
# nanny.toml
[tools]
allowed = ["fetch_page", "read_file"]

[tools.fetch_page]
max_calls     = 20
cost_per_call = 10   # nanny.toml cost overrides the macro default

nanny::http_get — built-in HTTP tool

nanny::http_get is a built-in governed HTTP GET function. It requires no #[tool] annotation — Nanny applies allowlist, call-count, and cost enforcement automatically.
use nanny::http_get;

let html = http_get("https://example.com")?;
Governance applied automatically:
  • Checked against the [tools] allowed list (tool name: "http_get")
  • Subject to [tools.http_get] max_calls limit
  • Costs 10 units per successful call (configurable via [tools.http_get] cost_per_call)
# nanny.toml
[tools]
allowed = ["http_get"]

[tools.http_get]
max_calls     = 15
cost_per_call = 10
In passthrough mode (no nanny run), nanny::http_get makes the request directly with no enforcement overhead.

#[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:
use nanny::{rule, PolicyContext};

#[rule("no_spiral")]
fn check_spiral(ctx: &PolicyContext) -> bool {
    let h = &ctx.tool_call_history;
    // Deny if the last three tool calls were all the same URL
    !(h.len() >= 3 && h[h.len()-3..].iter().all(|u| u == &h[h.len()-1]))
}
Rules are evaluated on every tool call. When a rule returns false, Nanny stops execution with:
StopReason::RuleDenied { rule_name: "no_spiral" }

PolicyContext fields

The ctx parameter gives you a snapshot of the current execution state:
FieldTypeDescription
step_countu32Number of steps completed
elapsed_msu64Wall-clock time elapsed
cost_units_spentu64Total cost units spent so far
next_tool_costu64Declared cost of the tool currently being evaluated. 0 when no tool call is in flight.
tool_call_countsHashMap<String, u32>Per-tool call counts
tool_call_historyVec<String>Ordered log of tool names called
requested_toolOption<String>The tool being evaluated right now
last_tool_argsHashMap<String, String>Arguments of the tool call currently being evaluated
Rules are evaluated before the tool runs — requested_tool is set to the tool name being checked.

#[agent] — activate named limits for a scope

Mark a function to run under a named limit set from nanny.toml. When the function is entered, Nanny switches to those limits; when it exits, limits revert:
use nanny::agent;

#[agent("researcher")]
fn run_research(topic: &str) {
    // This runs under [limits.researcher] from nanny.toml
    let page = fetch_page(&format!("https://en.wikipedia.org/wiki/{topic}"));
    // ...
}
# nanny.toml
[limits.researcher]
steps   = 200
cost    = 5000
timeout = 120000
The named set inherits from [limits] and overrides only the declared fields. Nesting #[agent] functions is supported — limits revert to the caller’s set on exit.
webdingo running under nanny run — planner, researcher, and synthesizer agent scopes entering and exiting with structured NDJSON events

Complete example

use nanny::{tool, rule, agent, PolicyContext};
use std::collections::HashMap;

#[tool(cost = 10)]
fn fetch_page(url: &str) -> String {
    reqwest::blocking::get(url).unwrap().text().unwrap()
}

#[rule("no_spiral")]
fn check_spiral(ctx: &PolicyContext) -> bool {
    let h = &ctx.tool_call_history;
    !(h.len() >= 3 && h[h.len()-3..].iter().all(|u| u == &h[h.len()-1]))
}

#[agent("researcher")]
fn research(topic: &str) -> Vec<String> {
    let mut pages = Vec::new();
    let mut url = format!("https://en.wikipedia.org/wiki/{topic}");

    loop {
        let content = fetch_page(&url);
        pages.push(content.clone());
        // extract next URL from content ...
        break;
    }

    pages
}

fn main() {
    let results = research("Alan Turing");
    println!("Collected {} pages", results.len());
}
Run it under Nanny:
nanny run
Run it without Nanny (macros silent, agent runs normally):
cargo run

What happens on stop

When Nanny stops execution inside an instrumented function, the macro propagates the stop signal by panicking with a structured message. The panic is caught by the Nanny runtime — your agent process exits cleanly with a non-zero code and an ExecutionStopped event in the log. You do not need to handle stop reasons in your agent code. Nanny handles the exit path.