Span, trace, session
Span
One recorded operation — a single LLM call, tool call, or agent step. A span
carries
model, tokens, cost, latency, status, and any guardrail verdicts.Trace
One agent run — a root span plus its child spans, linked by
parent_span_id. /traces shows one row per trace with rolled-up cost,
tokens, duration, and error count.Session
A multi-turn conversation — several traces grouped by
gen_ai.conversation_id. /sessions shows per-session turns, cost, and tokens.POST /v1/chat/completions — the response your application received, kept for as
long as the trace. What is recorded, the limits, and the audit record of the setting
are on Security → Prompt content.
A trace is not a span. A single agent run that makes 6 LLM calls and 3 tool
calls is one trace containing ~10 spans. The list views count traces; the
detail view (the transcript spine) renders the spans inside one trace.
How a session gets its id
A trace joins a session only if the call carries a conversation id. Tracelane never guesses one — two traces are the same conversation because you said so. There are exactly two ways to say it, and they end up in the same place:
Who is a separate question from which conversation, and a separate field.
Same shape, same rule that Tracelane never invents one:
If you already run OpenInference or Langfuse instrumentation,
user.id is what
they emit and nothing needs changing. Use an opaque id: the value is stored, and
an email address is scrubbed to [REDACTED:email] by PII redaction on the way
in.
Both SDKs set it for you. Wrap the turn, and every call inside the scope joins
the session:
instrumentOpenAI, instrumentAnthropic, instrumentLiteLLM,
instrumentOpenRouter in TypeScript; instrument_openai,
instrument_anthropic, instrument_openrouter in Python. For any other client
— including the no-SDK gateway path in the Quickstart — pass the
header yourself with sessionHeaders(id) (TypeScript) or session_headers(id)
(Python).
Ids are validated, not silently dropped. A session id must be non-empty
visible ASCII, at most 256 characters. Anything outside that throws at the call
you wrote — the gateway would otherwise drop the header and the session would
vanish with no error. An over-long id is rejected, never truncated, because a
truncated id would split one conversation into two.
Organization and environment
- Organization — your workspace. An org maps 1:1 to a WorkOS Organization and
to an internal tenant UUID. All isolation is per-tenant: every ClickHouse
query runs
WHERE tenant_id = ?, with the tenant taken from your validated JWT, never a request body. (The WorkOSorg_idis resolved to the internal tenant UUID before any query — see Tenant isolation.) - Environment — a promotion stage for prompt versions (
staging→production). Environments apply to prompt promotion, not to trace storage.
How usage is metered
Billing is measured across six usage meters — ingest, hot indexed window, series, query compute, cold archive and evaluations — not a single trace count. Each plan includes a monthly allowance on every meter (Builder, for example, includes 20 GB of ingest); usage above the included allowance bills continuously per unit, with no rollover. Ingest is never blocked by billing state — sending traces never stops because of usage or an unpaid invoice. See Pricing for the exact per-tier numbers (they are the source of truth). Tenants with no billing account — OSS self-host and unbilled workspaces — are never metered.What this page does not settle yet. The exact accounting rules for
partial-trace and failed-request metering are defined by the pricing configuration
in Polar, not by this page — always reconcile any usage number against
Pricing and your Polar dashboard.
Related
- Quickstart — send your first traced call
- Pricing — the six usage meters, included allowances and windows
- Architecture — how traces are captured and stored
- Audit ledger — how each ledger event is made verifiable