Skip to content
Judgment Labs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Trace conventions

How to structure spans so traces display correctly, judges read them well and cost adds up.

Judgment traces are standard OpenTelemetry traces. These conventions decide how a trace displays, how judges read it, and how cost adds up. For the wire rules, see Ingest contract. For every key, see Attribute keys and mappers.

Trace boundaries

Convention Rule
One root A trace has one root span: no parent span ID and a duration greater than zero. Every agent, model, and tool span for that run descends from it.
One autonomous run Start the root when the application starts work. End it before the application waits for new outside input, such as the user’s next message or a human approval.
Children first End child spans before the root, then flush. Judgment evaluates the trace once the finalized root arrives.
Sessions Set judgment.session_id on the root to group separate traces from one conversation, job, or workflow. Only the root’s value counts.
Waits start new traces A queue hand-off, checkpoint, retry that resumes later, or approval wait starts a new trace in the same session. One live request that crosses services stays one trace when its context is propagated.

A user’s request and the agent’s answer are one trace. The user’s reply to that answer is a second trace in the same session.

Span kinds

judgment.span_kind is a free-form string. The backend does not enumerate kinds. These are the values Judgment and the SDKs use:

Kind Use for Set by
agent The application root, or a step where an agent decides or delegates. observe(span_type="agent")
llm Exactly one model call, carrying its model, token, and cost attributes. wrap(), observe(span_type="llm"), ingest inference
tool One tool, function, or retrieval call. observe(span_type="tool"), ingest inference from gen_ai.tool.name
function Other application functions. observe(span_type="function")
span Default when nothing else is set. observe() with no span_type; ingest default

TypeScript uses observe(fn, { spanType: "agent" }) for the same values. Ingest infers llm for billable GenAI operations that carry usage, provider, or model attributes, so spans from OpenTelemetry instrumentation libraries usually need no kind.

Span names

Name the span for the work it performs, such as search_nearby_restaurants or estimate_order. Pass span_name / spanName explicitly in bundled TypeScript: production bundlers can strip function names, which leaves blank names in the trace view. judgment.span_name overrides the stored name at ingest.

Input and output

Convention Rule
Owner Store judgment.input and judgment.output on the span that owns the work.
Model calls Record input as structured messages with roles and content, not a flattened string.
Tool-calling model output Record judgment.llm.tool_calls when a model responds with tool calls instead of text.
Size Keep each span under 8 MiB of attributes. A larger span rejects the whole export request with 413.

Model usage and cost

  • Put model, provider, and token attributes on the llm span only. Copying them to a parent counts the same usage twice.
  • When a tool calls a model, keep both spans: the tool span for the action, its llm child for the usage.
  • Judgment calculates cost from provider, model, and token counts. Set judgment.usage.total_cost_usd only when the provider reports a real cost. A made-up 0 makes paid work look free.

Errors

An uncaught exception inside observe marks the span as an error. A returned failure, such as an error payload, a non-zero exit code, or an exhausted retry, needs an explicit error status. See Add context.

Resource attributes

Resource attribute Rule
service.name Defaults to the project name in both SDKs. Services in one distributed trace share a project, so set a distinct value per service with resource_attributes (Python) or resourceAttributes (TypeScript).
service.version Optional deployed version of the service.
deployment.environment.name Set by Tracer.init(environment=...). One project holds every environment.

Context propagation

Use W3C trace context. Inject it on the outgoing request and continue it before the downstream service starts any span; a span started first gets a new trace ID. The SDK helpers propagation.inject, Tracer.continue_trace(), and Tracer.continueTrace() also carry Judgment baggage (session, customer, and customer user IDs). See Tracing patterns.

Custom attributes

Use an application-owned namespace, such as app.channel or lunch.headcount. judgment.* is reserved for the keys in Attribute keys and mappers. set_propagating_attribute / setPropagatingAttribute ignores keys that start with judgment..

Existing OpenTelemetry providers

Tracer.init creates its own tracer provider. It does not install a global OpenTelemetry provider.

You have Do
An OpenTelemetry instrumentor Tracer.registerOTELInstrumentation(instrumentor) routes it through Judgment.
A library or framework that only uses the global provider Call JudgmentTracerProvider.install_as_global_tracer_provider() (Python) or JudgmentTracerProvider.installAsGlobalTracerProvider() (TypeScript) after Tracer.init.
Your own Python TracerProvider provider.add_span_processor(tracer.get_span_processor()), where tracer is the value Tracer.init returns.
An exporter that does not use judgeval Send OTLP to the ingest endpoint and map attributes with mappers.

Was this page helpful?