Collector Signals — what Nio captures while an agent runs
Nio emits three OTEL signals — metrics, traces, and logs — covering every tool invocation, every guard decision, every conversation turn. Schemas are aligned across all three host platforms (Claude Code, Hermes, OpenClaw) so the same dashboards work everywhere. This page is the high-level overview; each signal has its own deep-dive page below.
Architecture
The three host platforms each have their own runtime model — Claude Code and Hermes spawn a node process per hook event, OpenClaw runs as a long-lived daemon — but they all converge on the same canonical hook event vocabulary, then on the same three collector modules that own the attribute schema. Schema consistency falls out of the architecture: every attribute key string is owned by exactly one module, no matter which platform produced the event.
┌─────────────┐ ┌────────────────┐ ┌───────────────┐
│ Claude Code │ │ Hermes │ │ OpenClaw │
│ │ │ │ │ │
│ per-hook │ │ per-hook spawn │ │ single daemon │
│ spawn │ │ (node hook-cli)│ │ process │
└──────┬──────┘ └────────┬───────┘ └───────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────┐ ┌──────────────┐
│ on-disk state cache │ │ in-memory │
│ bridges span lifecycle across │ │ Map<sessionId│
│ short-lived hook processes │ │ ,State> │
└──────────────────┬───────────────────┘ └──────┬───────┘
│ │
└──────────────┬───────────────┘
▼
┌──────────────────────────────────────┐
│ Canonical hook event vocabulary │
│ UserPromptSubmit · PreToolUse · │
│ PostToolUse · TaskCreated · │
│ TaskCompleted · Stop · Subagent │
│ Stop · SessionStart · SessionEnd │
└─────────────────┬────────────────────┘
▼
┌──────────────────────────────────────┐
│ Three collector modules unify │
│ the attribute schema: │
│ │
│ trace-collector │
│ metrics-collector │
│ logs-collector │
│ │
│ shared keys: gen_ai.* · nio.* │
│ shared values: span names, │
│ metric instruments │
└────────┬─────────┬───────────┬───────┘
│ │ │
▼ ▼ ▼
Metrics Traces Logs
(OTLP) (OTLP) (OTLP + local audit log)
Claude Code and Hermes have to bridge span lifecycle across short-lived hook processes — a PreToolUse in process A and the matching PostToolUse in process B share state via an on-disk cache. OpenClaw's daemon model holds the same state in memory. Both end up calling the same trace-collector helpers; the only difference is where the state lives between events.
Naming conventions
gen_ai.*— keys that follow the OTel GenAI semantic conventions. Used wherever there's a spec equivalent: tool name, conversation id, token usage, tool I/O.nio.*— vendor extensions for concepts the GenAI spec doesn't cover: guard decisions, per-phase scoring, platform tag, redacted prompt / reply previews, per-task subagent metadata.session.id— mirrored alongsidegen_ai.conversation.idfor OTel base-spec consumers that key off session id rather than conversation id. Same value.
Cross-signal: the same key name carries the same meaning across metrics, traces, and logs. gen_ai.tool.name on a metric label is the same string as the matching trace span attribute, which is the same string as the matching audit-log attribute. Joining signals in dashboards Just Works.
The three signals
<endpoint>/v1/metrics
Four instruments — three counters and one histogram — covering tool invocations, turn boundaries, guard decisions, and the risk-score distribution. No local backup; metrics drop on the floor when the OTLP endpoint is empty.
<endpoint>/v1/traces
One trace per conversation turn, with child spans per tool call and per subagent dispatch. Span names follow GenAI conventions (invoke_agent UserPromptSubmit, execute_tool <name>) where applicable. No local backup.
<endpoint>/v1/logs + local ~/.nio/audit.jsonl
An audit-log entry per guard decision, per skill scan, per session lifecycle event, and per dispatched hook event. Dual-written: OTEL Logs export and a local JSONL file (offline / air-gapped friendly).
Configuration
All three signals are gated by the collector section in ~/.nio/config.yaml. Full field-by-field reference: Configuration · collector.
YAML cheatsheet (click to expand)
collector:
endpoint: "" # OTLP base URL; empty = no OTLP export at all
api_key: "" # Bearer token
timeout: 5000 # milliseconds
protocol: http # http | grpc
metrics:
enabled: true # OTLP metrics export on/off
traces:
enabled: true # OTLP traces export on/off
logs:
enabled: true # OTLP logs export on/off
local: true # local JSONL backup on/off
path: "~/.nio/audit.jsonl" # audit log + (sibling) traces-state-store.json
max_size_mb: 100 # rotation threshold for the local file
- When
collector.endpointis empty, the corresponding provider factory returnsnulland the platform code skips emit. - The audit-log local JSONL still works (controlled by
collector.logs.local) even without an endpoint — handy for offline / air-gapped use.