Trace Explorer¶
trace-explorer/
is a React SPA backed by FastAPI (trace-explorer service, port 8060).
It has two views, switchable from the header:
Sessions¶
- Left sidebar with session list, sortable and searchable, with time-range filter (
1h,6h,24h,all) - Stat cards: LLM calls, tool calls, total cost, total tokens, duration
- Nested waterfall with depth-first span tree (Jaeger-style indentation)
- Subagent linking —
tasktool spans link to child session IDs viaparent_session_id - Right panel with per-span detail: collapsible JSON tree for attributes, formatted cost/token/duration values
- Live updates for in-progress sessions via SSE (
GET /api/sessions/{session_id}/spans/stream):spans— full span list; replace client state with this payload. Emitted when any(span_id, duration_ms)pair changes — covers both new spans arriving and the synthetic placeholder being replaced by the real root span on session close.heartbeat— emitted each poll cycle when nothing has changed (keeps the connection alive through proxies)done— emitted once the session closes; client should close theEventSourceerror— emitted on a Tempo fetch failure; includes adetailfield
Open-session placeholder¶
While a session is running, its child spans (LLM calls, tool calls, agent spans) reference a root
span that OpenCode has not yet flushed to Tempo. By default the @devtheops/opencode-plugin-otel
plugin closes the root opencode.session span at each session.idle (one root span per turn); with
the opt-in OPENCODE_LONG_RUNNING_SESSION_SPANS=1 flag (PR #98,
not yet merged upstream) a single root span stays open for the session's whole lifetime and is
flushed only when it ends. Either way, the root span is absent from Tempo while its children are
still arriving.
The backend detects these orphaned children and synthesises a placeholder root span so the waterfall renders a properly nested tree instead of disconnected top-level entries:
| Placeholder field | Value |
|---|---|
span_id |
The actual parentSpanId referenced by the orphaned children — identical to the real root span's ID |
start_ns |
Earliest start time among orphaned children |
duration_ms |
From start_ns to the latest child end time; grows as new child spans arrive |
session.is_open |
true — signals the session is still running |
Because the placeholder and the real root share the same span ID, the placeholder is
transparently replaced when the real root arrives: it is no longer an orphan (its
parent ID is now in Tempo), so synthesis produces nothing and the real span takes its place.
The SSE fingerprints (span_id, duration_ms) — not just span IDs — so this replacement
always triggers a spans event and the client receives the correct final duration before
the done event fires.
The frontend renders open placeholders with a pulsing bar animation and a Live badge.

Overview¶
A dashboard-style view aggregating cost, token, model, agent, and tool usage across all sessions
for the selected time range (1h, 6h, 24h, all) — computed directly from Tempo span
attributes:
- Summary cards: sessions, total cost, total tokens, LLM calls, tool calls
- Time series charts: cost by model, token usage by model, tool calls by tool
- Model usage table and cost-by-model bar chart
- Agent activity table and token-usage-by-agent bar chart
- Tool usage table and tool success-rate bar chart
- LLM calls table (prompt -> model -> outcome) and tool calls table (tool -> parameters -> result), each linking back to the originating session in the Sessions view

Connects to Tempo (TEMPO_URL, default http://tempo:3200).
Configuration¶
| Variable | Default | Description |
|---|---|---|
TEMPO_URL |
http://tempo:3200 |
Backend: base URL of the Tempo HTTP API. |
CACHE_TTL_SECONDS |
20 (set to 2 in docker-compose.yml) |
Backend: how long fetched trace data is cached before re-querying Tempo. Lower values make the UI feel more "live" at the cost of more frequent Tempo queries. |
STREAM_POLL_SECONDS |
0.5 |
Backend: how often (seconds) the SSE stream polls Tempo for span changes. |
VITE_SESSIONS_POLL_MS |
5000 (set to 2000) |
Frontend: poll interval for the session list. |
VITE_OVERVIEW_POLL_MS |
10000 (set to 3000) |
Frontend: poll interval for the overview dashboard. |
The VITE_* variables are read at build time. They're set in trace-explorer/frontend/.env for local dev/build and as Docker build args in docker-compose.yml for the containerized build.