← back to docs

what we measure

Every dashboard panel, the data source behind it, and what writes the rows.

Every panel maps to one of three storage layers: live span ingest into D1, 1-minute pre-aggregates in Workers Analytics Engine (WAE), or the daily roll-up tables the cron worker produces every 15 minutes.

PanelSource tableUpdated by
Cost per user · 7drollups_metrics_dailycron */15
Cost per repo · 7drollups_metrics_dailycron */15
Cost trend · 30d (sparkline + run-rate)rollups_metrics_dailycron */15
Cache hit % per user · 7drollups_metrics_daily.cache_*cron */15
Tool approval rate · 7drollups_decisions_dailycron */15
Active developers (DAU/WAU/MAU) · 30drollups_metrics_daily distinct userscron */15
Top cost prompts · 7dspans (group by prompt_id, cost computed live from tokens × pricing)live ingest
Subagent fanout / depth per repo · 7dspans (BFS at query time, grouped by repo)live ingest
Subagent tree (by prompt_id)spans (parent / child walk)live ingest

Every panel honors the source filter chip at the top of the page (Claude Code · Agent SDK · Cowork · All). The source is detected at ingest from service.name resource attribute and persisted on every row.

Cowork events

Cowork emits org-level OTLP from its admin portal alongside Claude Code’s per-developer telemetry. Both streams hit the same /v1/{traces,metrics,logs} endpoint with the same shared org bearer, share prompt.id across the user turn, and land in the dashboards below.

Cowork event categoryWhere it landsUsed by
api_requestD1.events + per-prompt aggregate from spansCost trend · Top cost prompts
tool_resultD1.events, joined with claude_code.tool spansSubagent tree viewer
File accessD1.eventsInvestigate (per-prompt drill-down)
Skills / plugins invocationD1.eventsInvestigate (per-prompt drill-down)
Human approval (approve / deny)D1.tool_decisionsrollups_decisions_dailyTool approval rate

Approval events are the differentiator: generic backends ingest them as opaque log lines. We split approve vs deny per user, correlate them with the prompt.id they gated, and surface the rate in the Quality tab.

what we don’t store

  • Raw prompt text. The claude_code.interaction span carries the prompt id, not the body.
  • Raw response bodies. Forensics tier (Scale) renders these via signed URLs from your own R2/S3 bucket — we never custody them.
  • Per-token detail beyond aggregate input / output / cache_read / cache_creation counts.
  • Persisted dollar amounts. Cost is computed at read time from tokens × the pricing table in worker/src/pricing.ts. When Anthropic adjusts pricing, no historical row silently rewrites itself with stale dollars.
  • Trace data older than the tier retention window. 7d on Free, 30d on Pro, 90d on Scale.

why pre-aggregate

A 12-hour Claude Code session emits ~50,000 spans. Indexing every span at full fidelity is the cost driver behind generic OTel backends — they store everything, you pay for it. We collapse to ~200 WAE rows keyed by (org, user, repo, model, tool, status, metric_name, source) per 1-minute bucket. The dashboards you actually look at weekly only need that aggregate.

The 13 D1 tables are deliberately small: thin span metadata for the subagent-tree viewer, daily rollups (per source) for fast dashboard reads, sessions / OTP challenges for auth, members / org-level subscription state for billing. Everything else stays in WAE or is computed at read time.