OpenClaw Telemetry v0.1.0
Rewrite against OpenClaw 2026.8+ and Latitude's current span contract. Lockstep release with @latitude-data/openclaw-telemetry-cli 0.1.0. Requires OpenClaw 2026.8.1 or newer.
Fixed
- The plugin emits again on OpenClaw 2026.8.1+. Upstream removed the
before_agent_starthook (replaced bybefore_model_resolve/before_prompt_build/agent_turn_prepare), and 0.0.9 opened its root span only there, so on current OpenClaw every later hook found no run in flight and nothing was ever exported; the gateway log only showedunknown typed hook "before_agent_start" ignored. The root now opens lazily on the first hook that carries a run id (llm_inputin practice), and the run's true start is back-dated fromagent_end.durationMs. - Tokens, cost, conversation and tools reach Latitude's rollups. Spans carry
gen_ai.operation.name(invoke_agent/chat/execute_tool/ memory operations). Since June, Latitude's trace and session rollups only count spans classified as generations or tool calls, so 0.0.9's unclassifiedagent/model_call/tool_call:*spans would have shown a trace with no tokens, no cost, no messages and no tools even once traces flowed. - Finalization no longer races
llm_output. OpenClaw firesagent_endbeforellm_outputwith awaits in between, so the microtask deferral in 0.0.7 was not enough. The run ships once both have arrived, or after a short grace period whenllm_outputnever comes. - Compaction and subagent hooks are keyed by session. Both stopped carrying the parent run id upstream; they now resolve the open run through the session key (
requesterSessionKeyfor subagents). - Diagnostics land in the gateway log. Output goes through OpenClaw's
api.loggerinstead of raw stderr, which the gateway never captured.
Added
- Per-call usage, cost and output. Each
llm_requestspan takes its tokens (input, output, cache read/write, reasoning), OpenClaw's own cost (gen_ai.usage.cost, catalog or provider-billed), response id, finish reason, output message and the exact input transcript from the assistant message it produced, matched by timestamp against themodel_call_started/model_call_endedwindow. Harnesses that never fire the per-call hooks getllm_requestspans synthesized from the transcript instead. - Time to first token (
gen_ai.server.time_to_first_token) frommodel_call_ended.timeToFirstByteMs, plusgen_ai.request.stream. - Tool definitions (
gen_ai.tool.definitions) fromllm_input.tools, the post-policy tool list offered to the model, so the Tools page knows what the agent is equipped with.config.toolDefinitions=falseopts out. - End-user identity.
user.idis the sender of a user-triggered turn; the display name and handle come frommessage_receivedor from the⟦openclaw:ctx⟧block channels prefix to the prompt, asopenclaw.sender.*metadata. OpenClaw's injected[openclaw.runtime-context]message is exported with rolesystemso a turn does not render as two user messages. - Memory. OpenClaw's built-in memory (
MEMORY.md,USER.md,memory/*.md) surfaces as OTEL GenAI memory operations under the storeopenclaw/<agentId>: the snapshot injected at session start is onesearch_memoryper session,memory_search/memory_getcalls are reads, andwrite/editcalls on a memory file areupsert_memory(ordelete_memorywhen the file is emptied) carrying the full new body. Switches:config.memory,config.memoryContent. - Cron runs are tagged
cron:<jobId>with the job id and name in metadata, derived from the isolated session key or the latestcron_changedstart for the agent. - Subagents nest under the
sessions_spawntool call that created them: asubagentspan spanning spawn to ended, with the child run's wholeinteractiontree underneath, in the parent's trace and session. Child spans carry the label asgen_ai.agent.nameand keep their own session id underopenclaw.session.id. - Compactions are model calls. A compaction span is a
chatspan whose input is the messages being compacted and whose output is the summary that replaced them (read from OpenClaw's transcript store, which persists it beforeafter_compactionfires), with the before/after message and token counts; the summarizer fires no per-call hooks, soopenclaw.usage.state=unreported. A compaction outside a run (sessions.compact) ships as its own trace whose root is namedcompaction, so it is filterable by name and never mistaken for a user turn. - Derived tags and metadata:
openclaw, the channel, the agent id,cron:<job>andsubagent:<agent>tags;openclaw.*metadata (run, session, channel, trigger, model, sender, plugin version). Operators add their own withconfig.tags/config.metadata, andconfig.serviceNamesets the OTLP service name. - Transport retries.
429,5xxand network errors retry with backoff honouringRetry-After(capped at 30 s, since exports are sequential); other4xxare final so no span is ever sent twice.gateway_stopflushes the queue. - Per-attribute content budget (
config.maxContentChars, default 256 KiB). Strings are truncated from the middle without splitting a surrogate pair; message lists, tool definitions and memory records stay valid JSON by truncating the strings inside them first and then dropping whole items from the middle (a message list gets asystemmarker saying how many were omitted). - Transcript dialect. The message normalizer understands OpenClaw's own transcript (
toolCallblocks,toolResultmessages,thinkingblocks, base64 images, custom runtime notes) and thetoolResultblocks the Codex harness nests inside tool results. - Codex harness support. OpenClaw's Codex app-server harness (ChatGPT OAuth models) passes plugins an empty history, a per-turn transcript, usage only on the turn's final message and no per-call hooks. The plugin rebuilds the session conversation from the turns it has seen and, on a cold start, from OpenClaw's transcript store (the per-agent
openclaw-agent.sqliteon 2026.9+, read throughnode:sqlite, or the oldersessions/<id>.jsonlfile), prepending it to every input so the trace and session views read as one conversation (openclaw.history.sourcesays where it came from); it synthesizesllm_requestspans from the transcript, puts the attempt aggregate on the last call when no message carried usage, takes time to first token from the first streamed delta of the agent event stream (openclaw.ttft.source=stream), prepends streamed reasoning to a call's output when its transcript message carries none (openclaw.reasoning.source=stream), records memory writes made through Codex'sapply_patch, and labels the result-announcement runsinteraction.kind=announce.
Changed
- Span names follow the Latitude harness family:
interaction,llm_request,tool_call:<name>,subagent,compaction, plus memory operation names. Tool and memory spans are OTLPCLIENTspans. - Usage lives only on
llm_requestspans; the root carries counts (openclaw.llm_calls,openclaw.tool_calls) and the turn outcome. - Minimum OpenClaw version is 2026.8.1.
Removed
- The
before_agent_startsubscription and theagent/model_callspan names.