Logs — audit-log entries, dual-written
Audit entries are dual-written: OTEL Logs export to <endpoint>/v1/logs AND a local JSONL file at collector.logs.path (default ~/.nio/audit.jsonl). The JSONL line is the entry verbatim; the OTEL LogRecord uses body = JSON.stringify(entry) plus a flat attribute set for indexing. For Guard evaluations, both carry the same evaluation payload; remote export uses the existing nio-audit logger and collector endpoint. Local writing continues without an endpoint, with remote logs disabled, or after export failure unless collector.logs.local=false.
Entry types
Discriminated by the event field. Five shapes — four named entry types plus the catch-all "hook event" record.
Each evaluation has its own ID and records every gate, including skips and failures. No session, trace, or span identity is required. See the canonical schema reference and receiver JSON Schema.
| Field | Type | Notes |
|---|---|---|
event | "guard" | discriminator |
evaluation_id | string | Evaluation UUID; independent of session, trace, and span identity |
timestamp / duration_ms | string / number | Evaluation start (ISO-8601) and elapsed milliseconds |
platform / service_name / agent_name | string | Required host, service, and agent identity |
session_id | string? | Optional host session id |
tool_name / tool_input_summary | string | Host tool name and existing redacted input summary |
decision | string | allow / deny / confirm / error |
risk_score / risk_level | number | null / string | null | Final risk; unknown values are null |
risk_tags | string[] | Deduplicated rule IDs |
phases | GuardPhase[] | All seven gates in order: tool_gate, allowlist, runtime, static, behavioural, llm, external |
findings | GuardFinding[] | All findings with unique finding_id, producing phase_id, and rule metadata |
termination | GuardTermination | kind, blocked_gate, trigger_finding_ids, and reason |
explanation | string | Human-readable result |
error | {kind, message}? | Actual engine error details |
scores / top_findings / phase_stopped / max_finding_severity | optional legacy fields | Retained summaries; top_findings remains limited to five |
cwd / action_type / initiating_skill / event_type | optional context | Existing local and hook context |
Each gate records status (completed, skipped, failed), reason, duration, nullable score, finding IDs/count, and whether it triggered a block. Skips explain why and have null timing/score; failures retain errors and a null score. External results retain each endpoint outcome. Termination identifies the actual blocking gate or an aggregate block and references the triggering findings.
| Field | Type | Notes |
|---|---|---|
timestamp | string | ISO-8601 |
platform | string | host |
agent_name | string? | User-configured agent_name; omitted when unset |
session_id | string? | host session id |
skill_name | string | scanned skill / dir |
risk_level | string | aggregated severity |
risk_tags | string[] | rule IDs hit |
finding_count | number? | total findings |
| Field | Type | Notes |
|---|---|---|
timestamp | string | ISO-8601 |
platform | string | host |
agent_name | string? | User-configured agent_name; omitted when unset |
session_id | string? | host session id |
lifecycle_type | string | subagent_spawning / subagent_ended / agent_end / session_start / session_end |
details | Record<string, unknown>? | platform-specific (e.g. OpenClaw: {subagent_id, run_id}) |
| Field | Type | Notes |
|---|---|---|
timestamp | string | ISO-8601 |
config_path | string | path that failed to load |
error_message | string | parser / IO error |
Discriminator is the canonical hook event name itself: UserPromptSubmit · PreToolUse · PostToolUse · TaskCreated · TaskCompleted · Stop · SubagentStop · SessionStart · SessionEnd.
| Field | Type | Notes |
|---|---|---|
timestamp | string | ISO-8601 |
platform | string | host |
agent_name | string? | User-configured agent_name; omitted when unset |
session_id | string? | host session id |
cwd | string | null | working dir |
transcript_path | string? | Claude Code-only — path to session transcript JSONL |
tool_name | string? | for PreToolUse / PostToolUse |
tool_use_id | string? | for PreToolUse / PostToolUse |
tool_summary | string? | for PreToolUse / PostToolUse |
task_id | string? | for TaskCreated / TaskCompleted |
task_summary | string? | for TaskCreated |
OTEL LogRecord projection
The flat attribute set used for OTEL Logs indexing. Same key names as the matching trace span attributes wherever a concept overlaps (tool name, conversation id, guard decision, …) — same query keys work across logs and traces.
body= JSON-stringified entry (full content of the JSONL line)severityNumber/severityTextderived fromrisk_level:low→INFO,medium→WARN,high→ERROR,critical→FATAL; INFO when norisk_level
| Attribute | Description | Captured at | Platforms |
|---|---|---|---|
gen_ai.tool.name | Host tool name; same key as the tool-span attribute | PreToolUse · PostToolUse · guard decision | all |
gen_ai.tool.call.id | Host tool-call id; same key as the tool-span attribute | PreToolUse · PostToolUse | all |
gen_ai.conversation.id | Host session id; same key as the turn-span attribute | every audit entry with a session | all |
session.id | Mirror of gen_ai.conversation.id for OTel base-spec consumers | every audit entry with a session | all |
nio.guard.decision | Guard verdict — allow / deny / confirm / error | guard decision | all |
nio.guard.evaluation_id | Independent evaluation UUID | structured guard evaluation | all |
nio.guard.termination / nio.guard.blocked_gate | Termination kind; blocking gate when applicable | structured guard evaluation | all |
nio.guard.risk_level | Guard risk level — low / medium / high / critical | guard decision | all |
nio.guard.risk_score | Guard risk score, 0–1 | guard decision | all |
nio.guard.risk_tags | Comma-joined rule IDs that fired | guard decision | all |
nio.tool_summary | One-line summary derived from tool input | PreToolUse · PostToolUse | all |
nio.task_id | Task id from the dispatch event | TaskCreated · TaskCompleted | Claude Code + OpenClaw |
nio.task_summary | Derived from task input | TaskCreated | Claude Code + OpenClaw |
nio.platform | Source platform — claude-code / hermes / openclaw | every audit entry | all |
gen_ai.agent.name | User-configured agent_name; only emitted when set | every audit entry (when configured) | all |
nio.cwd | Working dir at hook fire | every audit entry with cwd | all |
nio.event | Discriminator — hook event name vs guard / lifecycle / scan / config_error | every audit entry | all |
nio.event_type | pre / post for guard entries | guard decision | all |
nio.action_type | exec_command / write_file / network_request / read_file | guard decision | all |
nio.max_finding_severity | Highest finding severity surfaced this run | guard decision | all |
nio.phase_stopped | Which Phase 0–6 produced the decision | guard decision | all |
nio.explanation | Human-readable reason for the verdict | guard decision | all |
nio.transcript_path | Claude Code-only — path to session transcript JSONL | hook events with transcript | Claude Code only |
nio.phases.{name}.score | Per-phase score (array index 0–6 for structured evaluations); null values omitted | guard decision | all |
nio.phases.{name}.finding_count | Per-phase finding count | guard decision | all |
nio.phases.{name}.duration_ms | Per-phase wall-clock cost (ms) | guard decision | all |
Local JSONL path: collector.logs.path (default ~/.nio/audit.jsonl). Rotation kicks in at collector.logs.max_size_mb (default 100 MB) — the live file is renamed to <path>.1.