Core0 / Nio
Core0
GitHub v2.4.4
Telemetry · Overview

# 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 alongside `gen_ai.conversation.id` for 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

Metrics OTLP · `<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.

Metrics →

Traces OTLP · `<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.

Traces →

Logs OTLP · `<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).

Logs →

## 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.endpoint` is empty, the corresponding provider factory returns `null` and 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.