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

> Every Nanny execution emits a structured NDJSON event log. Here's what's in it.

## Format

The event log is **NDJSON** — one JSON object per line, emitted in chronological order.
Every object has an `"event"` field identifying its type and a `"ts"` field with a Unix
timestamp in milliseconds.

## Output destination

By default, events are written to **stdout**, interleaved with your agent's own output.
To separate them, configure file output:

```toml theme={null}
[observability]
log      = "file"
log_file = "nanny.log"
```

Or pipe stdout to a file at the shell level:

```bash theme={null}
nanny run > nanny.log
```

## Guaranteed events

Every execution emits exactly these two events, in this order:

### ExecutionStarted

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

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

### ExecutionStopped

Always the last event. Emitted on every exit path — clean exit, timeout, error, or signal.

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

If the process was killed, `reason` will be one of the stop reasons listed in
[Limits & Enforcement](/v0.4/concepts/limits).

## SDK events

When the Rust SDK macros or Python SDK decorators are active, additional events are emitted for each tool call. These appear between `ExecutionStarted` and `ExecutionStopped`:

| Event               | When emitted                                                                 |
| ------------------- | ---------------------------------------------------------------------------- |
| `StepCompleted`     | After each agent step                                                        |
| `ToolAllowed`       | When Nanny permits a tool call                                               |
| `ToolDenied`        | When a tool call is blocked because the tool is not in the allowlist         |
| `RuleDenied`        | When a tool call is blocked by a custom rule or a per-tool `max_calls` limit |
| `ToolFailed`        | When a permitted tool fails at runtime (network error, bad args, etc.)       |
| `AgentScopeEntered` | When a `#[nanny::agent("name")]` function is entered                         |
| `AgentScopeExited`  | When a `#[nanny::agent("name")]` function returns                            |

`ToolDenied` and `RuleDenied` are distinct denial events — `ToolDenied` means the tool was not permitted at all; `RuleDenied` means the tool was permitted but a rule blocked this specific call.
`ToolFailed` is different from both — the tool was allowed and called, but encountered a runtime error. No tokens are charged on failure.

## Using the log

The event log is designed to be piped into standard tools:

```bash theme={null}
# Count total steps
cat nanny.log | grep StepCompleted | wc -l

# Find all denied tool calls (allowlist blocks and rule denials)
cat nanny.log | jq 'select(.event == "ToolDenied" or .event == "RuleDenied")'

# Check why a run stopped
cat nanny.log | jq 'select(.event == "ExecutionStopped") | .reason'
```
