Traces — one trace per conversation turn
Span hierarchy follows the OTel GenAI semantic conventions wherever a spec equivalent exists; Nio-specific extensions use the nio.* prefix. Three span shapes — turn root, tool call, subagent task — covered below with their full attribute schemas.
Span hierarchy
Trace: invoke_agent UserPromptSubmit (root, opens at 1st PreToolUse, ends at Stop / SubagentStop)
├─ Span: execute_tool <name> (PreToolUse → PostToolUse)
├─ Span: execute_tool <name> (...)
└─ Span: task:execute (TaskCreated → TaskCompleted, or OpenClaw subagent_spawning → subagent_ended)
Span: invoke_agent UserPromptSubmit — turn root
One per conversation turn. Carries the turn-level metadata: conversation id, accumulated token usage, agent identity, and the redacted user-prompt / assistant-reply previews.
| Attribute | Description | Captured at | Platforms |
|---|---|---|---|
gen_ai.operation.name | Constant invoke_agent | turn close | all |
gen_ai.provider.name | Constant nio | turn close | all |
gen_ai.conversation.id | Host session ID | turn close | all |
gen_ai.agent.name | Platform name acting as agent identifier | turn close | all |
session.id | Mirror of gen_ai.conversation.id for OTel base-spec consumers | turn close | all |
gen_ai.usage.input_tokens | Input tokens consumed across the turn | Stop · SubagentStop · SessionEnd | all |
gen_ai.usage.output_tokens | Output tokens generated across the turn | Stop · SubagentStop · SessionEnd | all |
gen_ai.usage.cache_creation.input_tokens | Cache-creation input tokens | Stop · SubagentStop · SessionEnd | all |
gen_ai.usage.cache_read.input_tokens | Cache-read input tokens | Stop · SubagentStop · SessionEnd | all |
nio.platform | Source platform — claude-code / hermes / openclaw | turn close | all |
nio.turn_number | Per-session counter, starts at 1 | turn close | all |
nio.cwd | Working dir at turn start | turn close (when set) | all |
nio.turn.user_prompt | First user message of the turn, redacted, ≤2 KB | UserPromptSubmit | all |
nio.turn.assistant_reply | First assistant reply of the turn, redacted, ≤2 KB | llm_output (OpenClaw-native) | OpenClaw only |
nio.turn.cache_hit_rate | cache_read / (input + cache_creation + cache_read), 0–1 | turn close | all |
Token usage source differs by platform. Claude Code: Stop reads the transcript JSONL and sums message.usage from all assistant entries since turn start. Hermes: same code path as Claude Code if the transcript path is included in the post_llm_call payload; otherwise empty. OpenClaw: llm_output event payload carries usage directly; accumulated incrementally.
Span: execute_tool <name> — tool span
One per tool invocation. Span name is literally execute_tool ${toolName || 'unknown'}. Pre-event opens the span; post-event closes it (with retroactive start time on Claude Code/Hermes since the pre-side process is gone).
| Attribute | Description | Captured at | Platforms |
|---|---|---|---|
gen_ai.operation.name | Constant execute_tool | PostToolUse | all |
gen_ai.tool.name | Host tool name (Bash, WebFetch, …) | PreToolUse · PostToolUse | all |
gen_ai.tool.type | Tool type, when known | PostToolUse | all |
gen_ai.tool.call.id | Host tool-call id (tool_use_id on Claude Code, toolCallId on OpenClaw) | PreToolUse · PostToolUse | all |
gen_ai.tool.call.arguments | Tool input, redacted, ≤2 KB | PreToolUse | all |
gen_ai.tool.call.result | Tool output, redacted, ≤2 KB | PostToolUse | all |
nio.tool.error | Error message when the tool failed | PostToolUse | all |
nio.tool.duration_ms | Wall-clock tool execution time (ms) | PostToolUse | OpenClaw only |
nio.tool.run_id | OpenClaw-internal run identifier | PreToolUse | OpenClaw only |
nio.tool_summary | One-line summary derived from tool input | PostToolUse | all |
nio.platform | Source platform — claude-code / hermes / openclaw | PostToolUse | all |
nio.turn_number | Parent turn's number | PostToolUse | all |
nio.cwd | Working dir at hook fire | PostToolUse (when set) | all |
nio.guard.decision | Guard verdict — allow / deny / confirm_allowed / confirm_denied | PreToolUse | OpenClaw only |
nio.guard.risk_level | Guard risk level — low / medium / high / critical / unknown | PreToolUse | OpenClaw only |
nio.guard.risk_score | Guard risk score, 0–1 | PreToolUse | OpenClaw only |
nio.guard.risk_tags | Comma-joined rule IDs that fired | PreToolUse | OpenClaw only |
Span status: ERROR (with recordException(error)) when the tool failed or the guard denied / confirm-denied; OK otherwise. nio.guard.* on Claude Code? Not yet — Claude Code's guard decisions go to the audit log only; symmetric trace-span adoption is queued as a follow-up.
Span: task:execute — task span
One per subagent dispatch. Opens at TaskCreated (Claude Code, Teammates / cloud-agent flows) or subagent_spawning (OpenClaw); closes at the matching completion event.
| Attribute | Description | Captured at | Platforms |
|---|---|---|---|
nio.task_id | Task id from the dispatch event | TaskCreated | Claude Code + OpenClaw |
nio.task_summary | Derived from task input (Claude Code: task_input.prompt; OpenClaw: empty) | TaskCreated | Claude Code + OpenClaw |
nio.platform | Source platform — claude-code / openclaw | TaskCompleted | Claude Code + OpenClaw |
nio.session_id | Host session id | TaskCompleted | Claude Code + OpenClaw |
nio.turn_number | Parent turn's number | TaskCompleted | Claude Code + OpenClaw |
nio.cwd | Working dir at task start | TaskCompleted | Claude Code + OpenClaw |
Span name is the literal task:execute (not execute_tool task); session id uses nio.session_id instead of gen_ai.conversation.id + session.id. The other two spans use GenAI semantic conventions; the task span is intentionally on the legacy schema until Claude Code and OpenClaw can migrate in lockstep.
Trace state lifecycle
Claude Code and Hermes spawn a fresh node process per hook event, so a PreToolUse in process A and the matching PostToolUse in process B can't share an OTEL Span object. Both platforms bridge state via an on-disk cache keyed by session id; pending spans land there at pre-event time and get materialised retroactively at post-event time with the original start timestamp. OpenClaw runs as a single daemon, so the equivalent state lives in an in-memory Map<sessionId, State> instead. All three platforms route through the same trace-collector helper functions — span names and attribute keys are identical regardless of where the state was kept.