CClawMetryDocs

Data & storage

Event schema#

Twenty-seven runtimes, one event shape. Everything downstream — the dashboard, the rollups, the detectors, the MCP tools, the cloud snapshot — reads this and nothing else.

An event#

json
{
  "id":         "…",
  "session_id": "claude_code:1f2e3d…",
  "agent_id":   "…",
  "ts":         1788745200.123,
  "event_type": "tool_call",
  "role":       "assistant",
  "tool_name":  "Bash",
  "model":      "claude-sonnet-5",
  "cost_usd":   0.0041,
  "data":       { "…": "runtime-specific detail" }
}
FieldNotes
idDeterministic. Re-ingesting the same source is a primary-key no-op, never a duplicate.
session_idNamespaced by runtime — see below
agent_idWhich agent, where the runtime distinguishes them
tsEpoch seconds, float
event_typeSee the table below
roleuser, assistant, system, tool
tool_namePresent on tool events
modelPresent on model calls. This is what makes cost derivable.
cost_usdDerived at ingest, or the vendor's figure. null when genuinely unknown.
dataThe runtime's own detail, preserved rather than flattened away

Event types#

TypeMeaning
session.startedA session began
messageA user or assistant turn
prompt.submittedA user prompt was submitted
model.completedA model call finished, with usage
tool_callA tool was invoked
tool_resultA tool returned, success or failure
errorThe runtime recorded a failure
thinkingA reasoning block, where persisted
compactionContext was compacted
session.endedA session ended

Runtimes emit richer vocabularies of their own; those are preserved in data and mapped onto these for anything cross-runtime. A detector that asks "was this a tool call" gets a consistent answer regardless of whether the runtime called it function_call, toolRequest, ToolCall or tool.execution_start.

data is not flattened#

The runtime's own payload is kept. That is what makes the Runtime extras tab possible — Grok's upload manifest, Cline's rejected tool calls, Kimi's context-window occupancy — none of which fits a unified schema and all of which is worth keeping.

The daemon adds data.extra.runtime on ingest, so an event always knows which runtime produced it even after it has been through a rollup.

Session identity#

Session ids are namespaced by runtime:

text
claude_code:1f2e3d4c-…
codex:019e28c2-…
cursor:composer-…
pi-<uuid>
n8n-<execution_id>

Three consequences worth knowing:

  1. Filtering by runtime genuinely scopes a query rather than filtering rows

client-side, which is what keeps the runtime filter cheap on a large store.

  1. Ids do not collide across runtimes even when the underlying values would.
  2. Synthesised child sessions carry a composite id

<parent>::<tool_call_id> for a PicoClaw delegation, or <runtime>:vmusage:<session> for a hosted-VM usage row — so a synthetic session is identifiable as one.

Spans#

OpenTelemetry-shaped spans are stored alongside events, for tracing and the agent graph. The mapping from a Claude Code transcript is the clearest example:

SourceSpan
Assistant turnllm.call
tool_use blocktool.<name>, child
tool_use named Taskagent.spawn, child
thinking blockthinking, kind INTERNAL

Spans also arrive directly over OTLP.

Timestamps#

Every runtime is normalised to epoch seconds as a float. The sources vary enormously — ISO 8601 with and without a zone, epoch seconds, epoch milliseconds, epoch microseconds, local time with no zone at all, Go-trimmed RFC 3339 fractions — and each adapter converts.

Where a record genuinely carries no timestamp, the adapter falls back to file mtime and offsets successive messages slightly so stream order is preserved.

A session with no timestamp

A NULL timestamp renders as "just now", forever. Adapters are written to avoid producing one; if you see a session that is perpetually current, that is the shape of the bug.

Cost fields#

FieldMeaning
cost_usdThe figure, or null
cost_statusexact, estimated, partial, tokens_only, unavailable
spend_basisWhat produced the figure

cost_usd: null with cost_status: "unavailable" means the data is not there. It does not mean zero. → How cost is computed

Adding your own events#

The ingest API accepts this shape directly, so anything you push participates in the same rollups, detectors and views as a first-class runtime.

Cookie preferences