Skip to main content
Lemma traces are JSON trees delivered to POST /traces/ingest. A trace is one agent execution: the root record carries the current input, final output or error, agent name, optional thread/user context, and timing. All other work is child spans in trace.spans[], typed as span, generation, or tool, optionally nested via parent_id. The SDK builds this payload for you. This page defines what Lemma accepts, what each field means, how ingest normalizes it, and which fields unlock which product behavior.
One agent execution = one trace. LLM calls, tool calls, retrieval, and app logic are child spans inside that trace, not separate top-level traces.

Mental model

The SDK sends a trace record plus child spans. At ingest, Lemma creates a synthetic root span ({traceId}:root) that carries root-level fields for storage and display. Producers should target the trace record; they do not create the synthetic root themselves.

Requirement tiers

Ingest accepts a name-only trace. Semantic fields are not HTTP-required, but without them Lemma can only render timing and a sparse tree. For the pedagogical ideal path, see Building high-quality traces.

Ingest payload

Envelope and validation

Successful ingest returns 201. Invalid shape returns 400. Missing trace.id or span id values are generated server-side. Missing or invalid spans[].type defaults to "span". Missing parent_id attaches the child under the synthetic root.

Delivery and retries

Deliver one complete trace when the execution (agent turn) finishes. This is required; patching a trace over time is not currently supported. Treat ingest as a single delivery, not an incremental merge API.
  • Required: one complete delivery per execution — root input/output, thread/user, and all child spans in one payload, sent when the turn is done.
  • Retries: re-sending the same payload with the same span IDs is idempotent; spans whose IDs already exist are skipped.
  • Not a merge API: omitted root fields do not preserve prior values. There is no field-level upsert, and patching a trace across calls is not supported.
  • Processing: after an idle window Lemma processes the trace once; a later successful re-delivery does not restart processTrace / extractSignals. Late new span IDs may still append for display.
  • Do not reuse one trace.id across conversation turns — see Instrumenting multi-turn agents.

Trace record

Semantic guidance: include root input and either output or error. Without them, the dashboard can show timing but not what the user asked or whether the run succeeded. Callback traces set output from the return value unless you override it with trace.output(...). Uncaught exceptions mark the root as failed and still send the trace.

Child spans

Every child is a named object in trace.spans[].

Typed children

Type is a discriminator. Setting model alone does not make a generation; set type: "generation".

Nesting

Children nest through parent_id. In TypeScript, recording from a span handle sets the parent automatically. In Python, pass parent_id explicitly on start_tool / start_generation / start_span when the child belongs under another span.

Generations and message history

trace.input is the current user turn. A generation should record the full ordered message list sent to that LLM call, including system prompt, prior turns, tool results, and the current user message. Prefer both:
  • input — the prompt payload for that call
  • llmInputMessages / llm_input_messages — structured messages flattened into llm.input_messages.{index}.message.*
model is the primary model identifier. Native generation props such as llmProvider, llmInvocationParameters, and llmOutputMessages populate OpenInference-style attributes. Pass usage (camelCase in TypeScript, snake_case kwargs in Python) when the provider returns token counts — see Token usage.

Token usage

Generations may include a top-level usage object. Lemma does not invent token counts. Coverage semantics: Healthy zero ≠ missing instrumentation. Analytics uses absence vs explicit zero to decide whether a metric is unsupported or genuinely empty.

Wire format (ingest JSON)

All fields are optional. Only include fields the provider actually returned.

TypeScript DX

Python DX

The SDK also flattens usage into span attributes using OTel GenAI names (gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.usage.cache_read.input_tokens, gen_ai.usage.cache_creation.input_tokens, gen_ai.usage.reasoning.output_tokens) plus OpenInference compatibility keys (llm.token_count.prompt, llm.token_count.completion). Do not derive user-facing aggregates or estimated cost in the SDK — the backend computes windowed Analytics quantities from this source telemetry.

Tools

Tool spans should carry:
  • name — stable tool identifier
  • input — arguments
  • output or error — result or failure
  • optional toolParameters / tool_parameters for the schema
Ingest uses tool_name when present, otherwise the span name, and normalizes tool I/O into tool-call aliases for display.

Errors

Record a failure on the exact span, generation, or tool where it happened:
  • Keep the input that led to the failure when it is safe to record.
  • Pass error instead of inventing an output.
  • End the failed child so timing is preserved.
  • Rethrow when the whole execution should fail; leave the root successful when the agent recovers.
For callback traces, uncaught exceptions automatically fail the root. If you catch at the root boundary, call trace.fail(error). For completed work, pass error to recordSpan / recordGeneration / recordTool (or the Python equivalents). For live handles, call .end({ error }) / .end(error=...).

Timing

Duration resolution order:
  1. Explicit duration_ms / durationMs when provided
  2. Otherwise ended_at - started_at when both timestamps exist
  3. Otherwise, for children with missing duration, Lemma allocates remaining parent time
Child duration inference:
  • Starts from the synthetic root and walks the parent → child tree
  • Explicit sibling durations claim time first
  • Siblings with duration_ms == null split the remaining parent duration equally
  • If explicit siblings already exceed the parent duration, omitted siblings receive 0
  • The same rule applies recursively when a child becomes a parent
Prefer live handles (startSpan / startTool / startGeneration) so .end(...) measures real elapsed time. One-shot record* helpers omit child duration when you do not pass it.

Client fields vs ingest normalization

Clients send first-class JSON fields. Ingest generates display and analysis aliases. Do not hand-build the normalized aliases unless you are bypassing the SDK. Prefer native SDK props and first-class fields.

SDK mapping

Native contract props

Use native props instead of hand-building flattened attribute names. In Python, use snake_case.
model also populates llm.model_name for generation records. Object and array attribute values are serialized where Lemma expects string-valued attributes. For fields without a native prop, pass raw attributes.

Provenance attributes (Lemma extensions)

Every emitted span includes SDK provenance so Analytics can attribute coverage gaps to an integration or language without provider-specific frontend logic: These are Lemma-specific extensions (not OpenTelemetry GenAI canonical names).

Semantic requirements

Issue detection

Complete, correctly typed trees improve classification and automated issue extraction. Root I/O, model fields, and tool payloads are not general ingest gates. Some environments may further restrict issue extraction to specific framework shapes; treat that as an environment policy, not as the base SDK contract.

Privacy

Redact secrets, credentials, and sensitive user data before tracing them. Framework integrations always record inputs, outputs, and error messages: without them a trace cannot show what a run consumed, produced, or why it failed.

Analytics telemetry

Required attributes for Analytics widgets and upgrade behavior.

Building high-quality traces

Bad → better → best examples for the ideal instrumentation path.

Instrument an agent

Build a complete Lemma trace one piece at a time.

Generations

Capture model, full messages, and completion.

Common issues

Diagnose missing spans, flat trees, and blank I/O.