Skip to main content

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:
[observability]
log      = "file"
log_file = "nanny.log"
Or pipe stdout to a file at the shell level:
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.
{
  "event": "ExecutionStarted",
  "ts": 1711234567000,
  "limits_set": "[limits]",
  "command": "python agent.py",
  "limits": {
    "steps": 100,
    "cost": 1000,
    "timeout": 30000
  }
}

ExecutionStopped

Always the last event. Emitted on every exit path — clean exit, timeout, error, or signal.
{
  "event": "ExecutionStopped",
  "ts": 1711234572000,
  "reason": "AgentCompleted",
  "steps": 7,
  "cost_spent": 70,
  "elapsed_ms": 4823
}
If the process was killed, reason will be one of the stop reasons listed in Limits & Enforcement.

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:
EventWhen emitted
StepCompletedAfter each agent step
ToolAllowedWhen Nanny permits a tool call
ToolDeniedWhen Nanny blocks a tool call (not in allowlist, or rule fired)
ToolFailedWhen a permitted tool fails at runtime (network error, bad args, etc.)
AgentScopeEnteredWhen a #[nanny::agent("name")] function is entered
AgentScopeExitedWhen a #[nanny::agent("name")] function returns
ToolFailed is distinct from ToolDenied — the tool was allowed but encountered an error. No cost is charged on tool failure.

Using the log

The event log is designed to be piped into standard tools:
# Count total steps
cat nanny.log | grep StepCompleted | wc -l

# Find all denied tools
cat nanny.log | jq 'select(.event == "ToolDenied")'

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