Telemetry · Traces

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.

AttributeDescriptionCaptured atPlatforms
gen_ai.operation.nameConstant invoke_agentturn closeall
gen_ai.provider.nameConstant nioturn closeall
gen_ai.conversation.idHost session IDturn closeall
gen_ai.agent.namePlatform name acting as agent identifierturn closeall
session.idMirror of gen_ai.conversation.id for OTel base-spec consumersturn closeall
gen_ai.usage.input_tokensInput tokens consumed across the turnStop · SubagentStop · SessionEndall
gen_ai.usage.output_tokensOutput tokens generated across the turnStop · SubagentStop · SessionEndall
gen_ai.usage.cache_creation.input_tokensCache-creation input tokensStop · SubagentStop · SessionEndall
gen_ai.usage.cache_read.input_tokensCache-read input tokensStop · SubagentStop · SessionEndall
nio.platformSource platform — claude-code / hermes / openclawturn closeall
nio.turn_numberPer-session counter, starts at 1turn closeall
nio.cwdWorking dir at turn startturn close (when set)all
nio.turn.user_promptFirst user message of the turn, redacted, ≤2 KBUserPromptSubmitall
nio.turn.assistant_replyFirst assistant reply of the turn, redacted, ≤2 KBllm_output (OpenClaw-native)OpenClaw only
nio.turn.cache_hit_ratecache_read / (input + cache_creation + cache_read), 0–1turn closeall

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).

AttributeDescriptionCaptured atPlatforms
gen_ai.operation.nameConstant execute_toolPostToolUseall
gen_ai.tool.nameHost tool name (Bash, WebFetch, …)PreToolUse · PostToolUseall
gen_ai.tool.typeTool type, when knownPostToolUseall
gen_ai.tool.call.idHost tool-call id (tool_use_id on Claude Code, toolCallId on OpenClaw)PreToolUse · PostToolUseall
gen_ai.tool.call.argumentsTool input, redacted, ≤2 KBPreToolUseall
gen_ai.tool.call.resultTool output, redacted, ≤2 KBPostToolUseall
nio.tool.errorError message when the tool failedPostToolUseall
nio.tool.duration_msWall-clock tool execution time (ms)PostToolUseOpenClaw only
nio.tool.run_idOpenClaw-internal run identifierPreToolUseOpenClaw only
nio.tool_summaryOne-line summary derived from tool inputPostToolUseall
nio.platformSource platform — claude-code / hermes / openclawPostToolUseall
nio.turn_numberParent turn's numberPostToolUseall
nio.cwdWorking dir at hook firePostToolUse (when set)all
nio.guard.decisionGuard verdict — allow / deny / confirm_allowed / confirm_deniedPreToolUseOpenClaw only
nio.guard.risk_levelGuard risk level — low / medium / high / critical / unknownPreToolUseOpenClaw only
nio.guard.risk_scoreGuard risk score, 0–1PreToolUseOpenClaw only
nio.guard.risk_tagsComma-joined rule IDs that firedPreToolUseOpenClaw 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.

AttributeDescriptionCaptured atPlatforms
nio.task_idTask id from the dispatch eventTaskCreatedClaude Code + OpenClaw
nio.task_summaryDerived from task input (Claude Code: task_input.prompt; OpenClaw: empty)TaskCreatedClaude Code + OpenClaw
nio.platformSource platform — claude-code / openclawTaskCompletedClaude Code + OpenClaw
nio.session_idHost session idTaskCompletedClaude Code + OpenClaw
nio.turn_numberParent turn's numberTaskCompletedClaude Code + OpenClaw
nio.cwdWorking dir at task startTaskCompletedClaude Code + OpenClaw
Known gap · not yet GenAI-aligned

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.