Skip to content

Traces and sessions

Before you start

  • A project that has received at least one trace (see the Quickstart)
  • Any role in the project can read traces. Exporting, adding to a dataset and adding to a review queue need the member role or above.

The Traces page lists every trace your app sent. A trace is one run of your code, made of spans, and a session groups the traces of one conversation. This guide covers finding a trace, reading it, and following a conversation. The data model explains what traces, spans and sessions are.

The controls sit at the top right of the page, or in a row above the page on a phone.

  • Time range. Pick 1h, 24h, 7d or 30d. The calendar button sets a custom range of up to 90 days, in your local time. The default is the last 24 hours.
  • Environment. Pick one environment, or All environments. The list holds the environments seen in the last 30 days.

The Overview, Traces, Sessions and Releases pages show these controls and share their values. They are kept in the page address, so a link you copy opens on the same window. The window applies to when a trace started.

The page header counts the traces in the window, for example “12,904 traces in the last 24 hours”. Below the list, the footer says how many you have loaded.

The list shows the newest traces first, 50 at a time. Choose Load more under the list to get the next 50.

The search box matches the trace name (any part, ignoring case), a full trace ID, or what a span sent and got back: with three or more characters it also searches the end of each span’s input (where the latest chat message is) and the start of its output, up to 2,000 characters of each. Only spans that started inside the selected time range are searched. On a server whose database migrations do not run as a Postgres superuser, the content search covers the last hour of the range only (see Upgrades). It applies about 0.3 seconds after you stop typing. Press Enter to apply it at once and Esc to clear it.

To jump straight to one trace, paste its 32-character ID into the command palette instead (see below).

Each filter is a pill above the list. Choosing a value adds it to the Active filters row, where × removes it and Clear all removes them all. Filters combine, and a trace has to match every one.

Filter What it keeps
Status Traces with no failed span (OK), or with at least one (Error).
Error class Traces with a failed span of this class: Rate limited, Provider error, Timed out, Network, Authentication, Context too long, Content filtered, Client error, Gateway (Spanlight’s gateway refused or failed the call itself) or Unknown, in the same order as on Provider health and in alert rules.
Retries Has retries: traces where the same request was sent more than once. A trace stored before 2026-10-15, whose retries were never counted, is left out.
Model Traces with a span that called this model.
Release Traces tagged with this release.
Tag Traces carrying this tag.
Score Traces whose latest score of a given name matches a condition. Pick the name, then at least, at most or equal to a value (or a category).
Has scores Traces that have any score.
Add filter Traces from one user ID or one session ID, matched exactly, or with one Metadata key = value. Pick the key (promoted keys first, then the keys seen in the last 7 days) and type the value, matched exactly; leave the value empty to keep the traces without the key. The chip reads customer_id = acme or No customer_id.

The Sort pill orders the list by Newest (the default) or by Retry cost ↓: the costliest retries first, traces whose retry cost is unknown last.

The model, release, tag and score-name lists come from the last 30 days, at most 100 values each. A list with no values is disabled, and a tooltip says so.

Three filters have no pill. The Environment from the top bar appears as an environment chip in the active filters, a Prompt version chip appears when you arrive from a prompt version’s usage card, which links to the traces that used it, and a Trace name chip (an exact name, unlike the search) appears when you arrive from a row of the Attribution page.

When nothing matches, the list says “No traces match these filters” and offers Clear filters. When the window is empty, it offers Connect your app.

On a wide screen each row shows these columns. As the card narrows, columns drop away: Tokens goes first, then User / session, then Scores and Tags. Status, name, started, duration, models and cost always stay. Phones show each trace as a tile.

Column Shows
Status dot Green for OK, red when a span failed. Hover or focus the red dot for the first error message.
Name The trace name in bold (or “Unnamed trace”), with the trace ID under it. A failed trace shows the error class and first error message instead of the ID.
Started How long ago the trace started.
Duration End to end.
Models The first model, with +N for more. “No LLM calls” if the trace made none.
Tokens Input then output tokens.
Cost The cost, or — if it is unknown. A warning mark means some spans had no price, so the total is a lower bound.
Metadata Shown on a wide screen when the project has promoted keys, except while Has retries or the Retry cost ↓ sort is on; phones show the chips on each tile. The trace’s value of the first promoted key it carries, such as customer_id acme, with +N for the others. A chip opens the list filtered to that pair. A value redaction replaced shows a redacted badge.
Retries How many calls in the trace were earlier attempts of the same request (same request hash), the last one not counted. Hover the count for the retry cost. 0 is a known zero. — means the trace was stored before 2026-10-15, when retries were not counted yet. Shown from a medium-wide screen up.
Retry cost What those earlier attempts cost. Shown only while Has retries or the Retry cost ↓ sort is on. — after a count means a retried call has no known price.
Scores The most recent score, with +N for more.
Tags The first two tags, with +N for more.
User / session The user ID and session ID, if the trace has them.

A — in the cost column is never a zero. See how cost is calculated.

Click a row to open the trace. When you come back with the Traces link, you return to the same filters and the row you opened is highlighted. Reloading the page starts from a clean list.

From the medium screen width up, each row has a checkbox, and the header checkbox selects every trace loaded. A bar appears at the bottom of the page with the count and these actions:

  • Add to dataset copies the selected traces into a dataset. See datasets.
  • Add to queue sends them to a review queue. See scores and review queues.
  • Compare appears when exactly two traces are selected and opens them side by side. See Compare two traces.
  • Clear drops the selection.

The two add buttons show only if your role allows them (member and above). The selection covers loaded rows only, and a trace that leaves the list when you change a filter is deselected.

Export at the top right of the page exports the traces that match the current time range and filters. It is disabled for viewers, with the reason in a tooltip.

  1. Choose a format. JSONL is one trace per line with its spans nested. CSV is one row per trace with no span payloads.
  2. Check What’s exported: the time range, the filters and the row count. The count is shown only when no filter is on, because a filtered count would be wrong.
  3. Choose Start export.

The file is built in the background. It appears under Settings, Exports and expires seven days later. A range longer than 90 days cannot be exported, and the dialog tells you to narrow it. The score filter, Has scores and the prompt version filter are not applied to exports yet, so the file can include traces those filters hide. The dialog warns you when that is the case.

If the server has no object storage, Start export answers “Not available on this server”. The Exports guide has the limits and the API.

The trace page has a header, a waterfall of spans on the left, and the selected span on the right. Below 1280 px wide the two columns stack.

  • Name and status. The trace name, an OK or error badge, and one badge per open Doctor finding on this trace. A finding badge reads like “Critical · Retry storm” and opens the finding. Resolved and muted findings are not shown. See the Doctor.
  • Add to dataset and Add to queue act on the span selected in the waterfall. They appear only if your role allows them.
  • Trace ID with a copy button.
  • Retries, for a trace with retried calls: a line such as “3 retries · $0.0412 retry cost”, with an info button that explains what counts. A trace stored before 2026-10-15 reads “Retries — · retry cost —”. A trace without retries has no line. The gateway’s own retries inside one call are not counted here; they are in the span’s attempt log.
  • Summary strip. Started, Duration, Spans (with a count of failed ones), Tokens (input to output), Cost and Environment. A value the SDK did not send shows — with a tooltip.
  • Chips for Release, User, Session, Tags and the prompt versions used, left out when not sent. The user chip opens the traces list filtered to that user. The session chip opens the session. A prompt chip opens the prompt version. A version that was deleted since reads as unknown.
  • Metadata. Every key and value of the trace’s metadata, with a count of keys. Each value opens the trace list filtered to that pair. A trace without metadata says how to set it with update_trace(metadata=…).

The Spans card shows the call tree. Each row has the span name, a bar placed on a shared time axis, and the duration.

  • Bar colour shows the span kind: LLM, Tool, Retrieval, Chain, Agent, HTTP or Other. Agent spans come from agent frameworks (see span kinds); spans stored by earlier Spanlight versions show them as Chain. A legend lists the kinds in this trace. Failed spans are red.
  • A lime marker on an LLM bar shows the time to first token, for streamed calls.
  • Click a row to select it. The chevron collapses a branch, and Expand all and Collapse all act on the whole tree.
  • The selected span is kept in the address as span=, so a link opens on the same span. With none chosen, the first root span is selected.
  • On a phone, the waterfall scrolls sideways.

With the keyboard, use the arrow keys to move between rows, Home and End to jump to the first and last, Right to expand or step into a branch, and Left to collapse or step out to the parent.

Under the waterfall, Usage shows the selected span’s model, provider, input, output and cached tokens, cost, time to first token and pricing version. A span that reported prompt-cache writes also shows Cache writes, with the part written for 1 hour after it (· 1 h: N) when there is one. A value the SDK did not report shows — with a tooltip that says why. A span that made no model call says so; select an LLM span to see its numbers.

Below it, the Scores panel lists the trace’s scores. See scores.

The panel on the right has the span name with a copy button for its ID, then its kind, status, duration, model and prompt version. A failed span adds a red note with its error message.

A failed LLM span then shows a Provider error block with its error class and what the provider said. When Spanlight’s gateway made the failure itself (a budget, a key limit refusal, a blocked address, the gateway failing), the block is titled Gateway error instead and starts with a Gateway code row, the gateway’s own code such as BUDGET_EXCEEDED or RATE_LIMITED, so the failure is never read as the provider’s:

  • Type and Code: the provider’s error type and code, such as rate_limit_error or insufficient_quota.
  • Kind: for a capacity refusal, which limit refused the call (Token limit, Request limit, Spend cap, Ramp-up limit, Overloaded, Gateway key limit or Unknown); see the rate-limit kinds.
  • Retry-after: the wait the provider asked for, or for a gateway refusal the wait the gateway told your client.
  • Request id, with a copy button, to quote to the provider’s support.
  • Gateway answered: the status the gateway answered with, for a gateway call.
  • Rate-limit headers: the provider’s rate-limit headers as it sent them, shown as text.
  • Attempts, for a gateway call: one row per attempt with its number, credential, result (the status or the error, such as timeout), time and retry-after. A call with more than 10 attempts keeps the first 9 and the last, numbered with the true count. Whether a failed attempt was billed is not known.

A value the span did not carry shows — with a tooltip. Long codes and request ids wrap onto a second line instead of being cut. A gateway call that succeeded after a retry or a fallback shows its attempts alone, under Gateway attempts. The attributes the block shows are left out of the Metadata tab’s JSON.

The panel has four tabs:

  • Input / Output. Chat payloads from OpenAI Chat Completions, the OpenAI Responses API, Anthropic Messages and OpenTelemetry GenAI messages show as a conversation. System messages are centred, user messages sit on the right, and assistant and tool messages on the left. Tool calls and results show their arguments and content as JSON, and an image shows as a short description. Text longer than 1,200 characters is clipped until you choose Show all. Any other payload shows as text or a JSON tree. Each side has a copy button.
  • Metadata. Start and end time, duration, span ID, parent span ID, prompt version, and the span’s custom attributes as JSON.
  • Raw JSON. The whole span, with Copy span JSON.
  • Scores. The scores on this span.

Two notes explain a missing or cut payload:

  • No input captured means the SDK did not send one, or payload capture is off in the project settings.
  • Truncated marks a payload over 1 MB that was cut at ingestion.

Long texts and large JSON open at their start: a text shows its first 20,000 characters and an object or list its first 200 entries, with Show all for the rest. When a trace’s payloads add up to more than 8 MB, the spans past that point load their input and output when you open them.

Compare two runs of the same agent or pipeline, for example before and after a prompt change. Select exactly two traces in the list and choose Compare. The earlier trace is A, the baseline; the later one is B, the candidate. The page address holds both ids (a= and b=), so you can share it.

  • Totals. Duration, input and output tokens, cost and errors of the whole traces, A → B, with the change. Lower is better for each. The cost change shows “—” when either trace has a call without a price.
  • Steps. A span of A and a span of B are the same step when they have the same name and the same position among spans of that name (the second chat span of A pairs with the second chat span of B). Steps follow A’s order; a step only in B appears after the step before it that A also has.
  • State. Each step shows one of: Only in A, Only in B, Failed in A or Failed in B (the step failed on one side only), Output changed, Same output or Output unknown. Input changed shows when the step was asked something else, and the model is shown as A → B when it changed. Same input with a different output points at a nondeterministic step; a changed input points at a prompt or context change.
  • Values. Duration, tokens and cost of each step, A → B, with the change. “—” means the step did not report the value (a tool step has no tokens) or is missing on that side.
  • A and B open the step in its trace, with that span selected.
  • Changed steps only hides steps with the same output, input, model, tokens and cost, even when their duration differs.

Outputs and inputs are compared exactly, as stored. A payload cut at ingest because it was too large compares as unknown when the stored parts match. Two spans of the same name that start at the same instant may pair crosswise. At most 2,000 spans of each trace are compared step by step; the totals always cover every span.

The same comparison is available from the API: GET /api/v1/projects/{project_id}/traces/compare?a={trace_id}&b={trace_id}, with the same access as reading a trace (an API key needs the traces:read scope). A trace that is not in the project answers 404.

A session groups the traces that share a session_id. Set it with update_trace(session_id="chat_123") inside a trace. The Python SDK page has the details.

Sessions in the sidebar lists the sessions that had a trace in the time range, the most recently active first, 50 at a time. Both the time range and the environment filter apply. With an environment picked, a session appears only if it has a trace in that environment, and its tile counts only those traces: the trace count, cost, errors and times leave out the session’s traces from other environments. With All environments picked, a session that spans environments shows all its traces. Each tile shows:

  • the session ID, which is the link;
  • the first to last time and the duration;
  • the number of traces;
  • the total cost, or — if no model in the session has a price;
  • an error count, or “No errors”.

Click a tile, or the session chip on a trace. The page reads every trace of that ID from the last 90 days, whatever the time range and environment are, so it can show more turns than the tile you clicked. With an environment picked, a line under the header says how many turns are from other environments, for example 1 of 2 turns is outside "production".

  • Header. The session ID with a copy button, the user when every trace that names one agrees, and a line such as “12 turns · $0.214 · 1 error · Today 09:12 to 09:41 · 29m 33s”.
  • Conversation. One turn per trace, oldest first. A turn shows its status, number, trace name and start time, then the user message on the right and the assistant reply on the left, then the model, latency, cost and Open trace. A failed turn shows a red bubble with the error message. Messages longer than 2,000 characters are clipped until you choose Show full message.
  • Summary. Total cost with the average per turn, counts of turns, errors and tokens, the time span, the models used with the number of turns for each, an input and output token bar, a timeline of when each turn started with failed turns marked, and the start time, last turn and user.

The text of each turn comes from its spans. The user message is the last user message of the first LLM call. The assistant reply is the output of the last LLM call that produced one. If the trace has no chat-shaped LLM span, Spanlight falls back to plain-text input and output on the root span. When nothing was captured, the turn says so.

Turns appear ten at a time, with a button such as Show 10 more turns for the rest (fewer on the last batch). A session of more than 100 traces loads the newest 100 first. Until you choose Load earlier turns, the turns are not numbered, the turn count in the header line gets a trailing + (for example “100 turns+”), and the summary covers the loaded turns only.

Press Ctrl+K (⌘K on a Mac), or click the search pill at the top, to open the command palette. It lists pages with their shortcuts, API keys, Members, other projects to switch to, the light, dark and system themes, and Sign out. Paste a 32-character trace ID to get an Open trace entry.

Outside a text field, press G, then a second key within one second, to go to a page.

Keys Page Keys Page
G O Overview G N Datasets
G T Traces G Q Review
G S Sessions G E Evaluators
G D Doctor G X Experiments
G R Releases G G Gateway
G U Users G A Alerts
G P Prompts G B Budgets
G L Playground G , Settings

Open Traces after your app has sent a trace. The list has a row for it, with the trace name, a green status dot, its duration, models, tokens and cost. Select the row: the page shows the header with the trace ID and the span waterfall, and the span panel shows the Input / Output of the selected span.

  • The list says “No traces in this range”. Nothing arrived in the time range or environment you chose. Widen the range or pick another environment. If your app sent traces, see No trace arrives.
  • The list says “No traces match these filters”. A trace has to match every active filter. Select Clear filters.
  • A trace page says “No spans in this trace”. The trace was created but none of its spans have arrived. Spans usually arrive within a few seconds. Reload the page.
  • A trace page says “Trace not found”. The retention policy may have deleted it, or the ID is wrong.
  • Sessions says “No sessions in this range” or “No sessions in this environment”. No trace in the range, or none from the environment you picked, carries a session ID. Widen the range or pick another environment. To group traces, set one with update_trace(session_id="chat_123") inside a trace. See Python SDK.