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

# Event Schema

> Every event type emitted by Nanny, with full field definitions.

## Format

All events are JSON objects emitted one per line (NDJSON). Every event has:

| Field   | Type    | Description                       |
| ------- | ------- | --------------------------------- |
| `event` | string  | Event type identifier (see below) |
| `ts`    | integer | Unix timestamp in milliseconds    |

***

## ExecutionStarted

Emitted immediately before the child process is spawned.
Always the first event in any log.

```json theme={null}
{
  "event": "ExecutionStarted",
  "ts": 1711234567000,
  "limits_set": "[limits]",
  "command": "python agent.py",
  "limits": {
    "steps": 100,
    "tokens": 1000,
    "timeout": 30000
  }
}
```

| Field            | Type    | Description                                              |
| ---------------- | ------- | -------------------------------------------------------- |
| `limits_set`     | string  | The active limit set name. `"[limits]"` = base defaults. |
| `command`        | string  | The full command string as passed to `nanny run`.        |
| `limits.steps`   | integer | Active step limit.                                       |
| `limits.tokens`  | integer | Active token limit.                                      |
| `limits.timeout` | integer | Active timeout in milliseconds.                          |

***

## ExecutionStopped

Emitted on every exit path — clean completion, limit breach, spawn failure, or internal error.
Always the last event in any log.

```json theme={null}
{
  "event": "ExecutionStopped",
  "ts": 1711234572000,
  "reason": "AgentCompleted",
  "steps": 42,
  "tokens_spent": 380,
  "elapsed_ms": 4823
}
```

| Field          | Type    | Description                               |
| -------------- | ------- | ----------------------------------------- |
| `reason`       | string  | Why execution stopped. See reasons below. |
| `steps`        | integer | Total steps completed.                    |
| `tokens_spent` | integer | Total tokens spent.                       |
| `elapsed_ms`   | integer | Total wall-clock time in milliseconds.    |

### Stop reasons

| Reason              | Description                                                                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AgentCompleted`    | Process exited cleanly on its own.                                                                                                                                        |
| `TimeoutExpired`    | Wall-clock timeout was reached. Process was killed.                                                                                                                       |
| `MaxStepsReached`   | Step limit was reached. Process was killed.                                                                                                                               |
| `BudgetExhausted`   | Token budget was exhausted. Process was killed.                                                                                                                           |
| `ToolDenied`        | A tool call was blocked because the tool is not in the allowlist.                                                                                                         |
| `RuleDenied`        | A tool call was blocked by a custom rule or a per-tool `max_calls` limit.                                                                                                 |
| `ManualStop`        | Execution was stopped programmatically.                                                                                                                                   |
| `ProcessCrashed`    | The child process exited with a non-zero code unexpectedly.                                                                                                               |
| `BridgeUnavailable` | Enforcement was active but became unreachable during rule evaluation or a tool call. Nanny fails closed — silently continuing with ungoverned execution is never allowed. |

***

## AgentScopeEntered

Emitted when a function annotated with `#[nanny::agent("name")]` is entered. Records the limits active for that scope.

```json theme={null}
{
  "event": "AgentScopeEntered",
  "ts": 1711234567200,
  "name": "researcher",
  "limits": {
    "steps": 200,
    "tokens": 5000,
    "timeout": 120000
  }
}
```

| Field            | Type    | Description                                                              |
| ---------------- | ------- | ------------------------------------------------------------------------ |
| `name`           | string  | The name of the agent scope (matches `[limits.<name>]` in `nanny.toml`). |
| `limits.steps`   | integer | Step limit active for this scope.                                        |
| `limits.tokens`  | integer | Token limit active for this scope.                                       |
| `limits.timeout` | integer | Timeout active for this scope in milliseconds.                           |

***

## AgentScopeExited

Emitted when a function annotated with `#[nanny::agent("name")]` returns. Records usage during that scope.

```json theme={null}
{
  "event": "AgentScopeExited",
  "ts": 1711234571800,
  "name": "researcher",
  "steps_used": 12,
  "tokens_used": 240
}
```

| Field         | Type    | Description                        |
| ------------- | ------- | ---------------------------------- |
| `name`        | string  | The name of the agent scope.       |
| `steps_used`  | integer | Steps consumed during this scope.  |
| `tokens_used` | integer | Tokens consumed during this scope. |

***

## StepCompleted

Emitted after each agent step when using the Rust SDK or Python SDK.

```json theme={null}
{
  "event": "StepCompleted",
  "ts": 1711234568000,
  "step": 1
}
```

| Field  | Type    | Description                       |
| ------ | ------- | --------------------------------- |
| `step` | integer | The step number, starting from 1. |

***

## ToolAllowed

Emitted when Nanny permits a tool call to proceed.

```json theme={null}
{
  "event": "ToolAllowed",
  "ts": 1711234568101,
  "tool": "http_get"
}
```

| Field  | Type   | Description                            |
| ------ | ------ | -------------------------------------- |
| `tool` | string | The name of the tool that was allowed. |

***

## ToolDenied

Emitted when Nanny blocks a tool call because the tool is not in the `[tools] allowed` list in `nanny.toml`.

```json theme={null}
{
  "event": "ToolDenied",
  "ts": 1711234568101,
  "tool": "write_file"
}
```

| Field  | Type   | Description                            |
| ------ | ------ | -------------------------------------- |
| `tool` | string | The name of the tool that was blocked. |

***

## RuleDenied

Emitted when a custom rule or a per-tool `max_calls` limit blocks a tool call. The tool was on the allowlist but a rule returned a denial before the call executed.

```json theme={null}
{
  "event": "RuleDenied",
  "ts": 1711234568101,
  "tool": "http_get",
  "rule_name": "no_loop"
}
```

| Field       | Type   | Description                                                                                                          |
| ----------- | ------ | -------------------------------------------------------------------------------------------------------------------- |
| `tool`      | string | The name of the tool that triggered the rule.                                                                        |
| `rule_name` | string | The name of the rule that fired (e.g. `"no_loop"`) or the per-tool limit that was hit (e.g. `"http_get.max_calls"`). |

***

## ToolFailed

Emitted when a permitted tool fails at runtime. Distinct from `ToolDenied` — the tool was allowed but encountered an error (network failure, bad arguments, timeout). No tokens are charged on failure.

```json theme={null}
{
  "event": "ToolFailed",
  "ts": 1711234568200,
  "tool": "http_get",
  "error": "connection refused"
}
```

| Field   | Type   | Description                       |
| ------- | ------ | --------------------------------- |
| `tool`  | string | The name of the tool that failed. |
| `error` | string | A description of the error.       |

***

## LlmUsageRecorded

Emitted when LLM token usage is reported to Nanny — via the Python SDK's `instrument()` or the Rust SDK's `report_usage()`. Records the measured input and output tokens debited from the budget, plus optional model, provider, and cache-usage labels.

```json theme={null}
{
  "event": "LlmUsageRecorded",
  "ts": 1711234568150,
  "input": 1200,
  "output": 340,
  "model": "gpt-4o",
  "provider": "openai",
  "cache_read": 900
}
```

| Field         | Type    | Description                                                                                                                                                                                                                            |
| ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input`       | integer | Prompt / input tokens consumed by the LLM call.                                                                                                                                                                                        |
| `output`      | integer | Completion / output tokens produced by the LLM call.                                                                                                                                                                                   |
| `model`       | string  | Optional. Model identifier reported by the SDK (e.g. `"gpt-4o"`). Omitted when not provided.                                                                                                                                           |
| `provider`    | string  | Optional. Provider identifier reported by the SDK (e.g. `"openai"`). Omitted when not provided.                                                                                                                                        |
| `cache_read`  | integer | Optional. A finer split of `input` — never additional tokens beyond it — for providers that report prompt-cache reads (OpenAI, Anthropic, DeepSeek, Gemini). Omitted, not zero, for providers/responses that don't report cache usage. |
| `cache_write` | integer | Optional. Same as `cache_read`, but for tokens written to cache (a real, separate cost for providers like Anthropic; most providers have no write-cost concept and never set this).                                                    |

`cache_read`/`cache_write` are reporting-only, same as `model`/`provider`: enforcement always debits `input + output`, unaffected by whether either is present. They exist so a downstream cost calculator can price cache-hit tokens at their real, much cheaper rate instead of treating all input as one undifferentiated price. Every provider uses its own field name and shape for cache usage — there's no shared convention the way there is for input/output tokens — so the SDK normalizes each provider's own vocabulary into these two generic fields; see `nanny_sdk.instrument`'s module docs for the per-provider mapping.
