Skip to main content
RunAgain organizes agent activity into traces and spans, following OpenTelemetry semantics with a gen_ai-aware data model.

Traces & spans

  • A trace is one end-to-end run (e.g. a single agent invocation, or a Claude Code session). Spans in a trace share a trace_id.
  • A span is a single operation within a trace — an LLM call, a tool call, a retrieval step. Spans carry timing, status, and attributes.

Observation types

Every span is classified so the UI can render it meaningfully: RunAgain promotes the common gen_ai attributes to typed columns so they’re fast to query and chart:
  • Model — request and response model
  • Tokens — input, output, cache-read, cache-write, reasoning, total
  • Cost — computed from tokens, or taken from a provider-supplied cost_usd
  • Identitysession_id, conversation_id, user_id, agent_name
  • Deploymentenvironment, version, release
  • Labelstags, plus a free-form metadata bag
  • Tooltool_name, status
Large input / output payloads are offloaded to blob storage above a size threshold and referenced by hash, keeping queries fast.

Reasoning & thinking

When a model emits extended thinking / reasoning, RunAgain captures it end to end: the reasoning token count lands in the reasoning column, and the reasoning text is parsed out of the message payload and rendered as a distinct ✳ thinking block in the transcript — kept separate from the assistant’s answer. The following shapes are recognized automatically in gen_ai.input.messages / gen_ai.output.messages (and the Langfuse / OpenInference / Vercel AI SDK equivalents):
  • Anthropic — content blocks of type thinking ({ thinking, signature }) and redacted_thinking (shown as [redacted thinking]).
  • Vercel AI SDKreasoning content parts ({ type: "reasoning", text }).
  • OpenAI — a reasoning output item with a summary, or a sibling reasoning_content field on the message.
Reasoning tokens are read from gen_ai.usage.reasoning.output_tokens (or the OpenInference llm.token_count.completion_details.reasoning alias). The @runagain/ai-sdk integration emits both the reasoning parts and the token count for you.

Custom metadata & identity

Set these as span attributes (or, for the deployment fields, resource attributes — a span attribute wins if both are present). RunAgain accepts the OTel/GenAI keys plus common vendor aliases, so most SDKs work with no mapping. First non-empty wins. Deployment fields set on the OTLP resource apply to every span from that process — the natural place for environment / version / release. Environment is a column on the trace list (click a value to filter); environment / version / release / user / tags / metadata all show on the trace.

Sessions

Spans that share a session_id (or, for Claude Code, session.id) are grouped into a session — a multi-turn conversation you can replay end to end, with every turn on one shared time axis. The hierarchy is strict:
A trace answers “what happened in this one run?” — the full tree of LLM calls, tool calls, and retrievals for a single turn. A session answers “what happened across this whole conversation?” — every turn stitched onto one timeline.

How this compares to other tools

The span → trace → session hierarchy is common across LLM observability tools; the main differences are what’s stored as the primitive and what the conversation grouping is called. Two RunAgain choices worth calling out:
  • Spans are the only stored primitive. Traces and sessions are rollups over spans — a trace by trace_id, a session by session_id at query time. Tools like Langfuse and LangSmith persist the trace as a first-class object at ingest instead. The rollup approach keeps writes cheap and makes session grouping a plain GROUP BY.
  • OTel-aligned identity. RunAgain reads session.id and gen_ai.conversation.id (see Custom metadata & identity), matching Phoenix and the OTel GenAI conventions — so an app instrumented for those is portable here with no remapping.

See it in the app

The Traces page renders the span waterfall, tokens, cost and status.