Skip to main content
A trace is one end-to-end agent execution, from the user’s input to the final response. Everything your agent does inside that execution (LLM calls, tool calls, retrieval, app logic) is recorded as child work inside the trace.
One agent execution = one trace. Use lemma.trace() as the boundary around the run.

The root trace

import { Lemma } from "@uselemma/tracing";

const lemma = new Lemma();

const answer = await lemma.trace(
  {
    name: "support-agent",
    input: userMessage,
  },
  async (trace) => {
    const answer = await runAgent(userMessage, trace);
    return answer;
  },
);
The SDK records the trace input, the returned value as output, and the trace name as the agent name.

Trace handles and span handles

Use the callback form when one function owns the whole run. In TypeScript, use a trace handle when work is coordinated across several helpers and you want to pass IDs around explicitly. In Python, keep the root trace in lemma.trace() / lemma.async_trace() and use span, tool, or generation handles inside the callback.
const trace = lemma.trace({
  name: "support-agent",
  input: userMessage,
  threadId,
  userId,
});

const span = trace.startSpan("retrieve-context");
const docs = await searchDocs(userMessage);
span.recordTool({
  name: "search_docs",
  input: { query: userMessage },
  output: docs,
});
span.end({ output: { count: docs.length } });

await trace.end({ output: "Here is what I found...", durationMs: 1234 });
The TypeScript trace handle has a stable trace.id. Calls are flushed to Lemma as trace snapshots, and trace.end({ output, durationMs }) performs a final flush. Callback traces in both SDKs measure total trace duration automatically; pass durationMs / duration_ms only when you already measured it.

Record by ID

Helpers can attach work to a trace when they only have IDs from the caller. This detached helper API is TypeScript-specific.
const trace = lemma.trace();

const span = lemma.startSpan({
  traceId: trace.id,
});

lemma.recordTool({
  traceId: trace.id,
  parentSpanId: span.id,
  name: "tool call",
});

await trace.flush();
Detached handle calls require traceId. If a detached span, generation, or tool belongs under a parent span, pass parentSpanId; calls that cannot attach safely warn and no-op.

Override output or record errors

Return values are captured automatically. Use trace.output() only when the recorded output should differ from the return value.
await lemma.trace({ name: "support-agent", input: userMessage }, async (trace) => {
  try {
    const response = await runAgent(userMessage);
    trace.output(response.text);
    return response;
  } catch (error) {
    trace.fail(error);
    throw error;
  }
});
If the callback throws, the SDK records the trace as failed, sends it to Lemma, and rethrows the original error.

Pass trace context to helpers

Pass the trace context into nested helper functions that need to record child work:
import type { TraceContext } from "@uselemma/tracing";

function recordToolResult(trace: TraceContext, result: unknown) {
  trace.recordTool({
    name: "search_docs",
    output: result,
  });
}
Avoid relying on ambient trace state. A process can coordinate multiple traces at once, so helpers should receive the trace or span handle they need explicitly.

Add the work inside

  • Generations for LLM calls (model, prompt, completion).
  • Tool calls for tool invocations (name, args, result).
  • Spans for everything else (retrieval, ranking, app logic).
support-agent              <- trace root (input, output)
|- draft-reply             <- generation
|- search_docs             <- tool call
`- final-answer            <- generation

Next steps

Generations

Capture LLM calls with model, prompt, completion, and timing.

Tool calls

Record tool arguments and results.

Threads & context

Group conversations and attach users.

Trace contract

The exact shape Lemma reads.