lemma.trace() and record its LLM calls and tools as typed children.
One agent execution = one trace. In Raindrop you often emit one event per interaction and group by
convoId; in Lemma you emit one trace per execution and group multi-turn conversations by threadId. See the trace contract.How the two models differ
Raindrop starts from the event — a single tracked AI interaction created withtrackAi() (single-shot) or begin() / finish() (the interaction API) — and layers tracing spans, signals, and feedback onto it. Conversations are formed by sharing a convoId across events.
Lemma starts from the trace — one end-to-end execution — and requires its structure to be explicit:
- One root trace per agent execution carrying the current user input and the final output or error.
- Typed children — generations, tools, and generic spans, not undifferentiated records.
- Per-generation message history — the full ordered message list sent to each LLM call.
Concept mapping
The structural intuition transfers cleanly. What changes is that Lemma needs its children typed and its root complete.| Raindrop concept | Closest Lemma primitive | What Lemma needs |
|---|---|---|
AI event / interaction (trackAi, begin + finish) | Lemma trace record (root) | One agent execution = one trace, with input and final output or error |
Event name (event) | Trace name | A stable agent / workflow name for grouping and filtering |
Event input / output | Trace input / output | Current user turn on the root; final answer or error on the root |
convoId | Thread (threadId / thread_id) | Groups multi-turn conversation traces |
userId | User (userId / user_id) | Per-user slicing |
model on the event | Generation model (type: "generation") | Model lives on a typed generation span, plus the full per-call message history |
Tracing span (withSpan) | Span (type: "span") | input and output (or error) |
Tool tracking (withTool, trackTool, startToolSpan) | Tool (type: "tool") | Tool name, arguments, and result or error |
| Auto-instrumented LLM spans | Generation (type: "generation") | model plus the full ordered message list |
| Span / tool nesting | parent_id nesting | Children under the step that caused them |
Event properties, feature flags | Trace or span metadata | Arbitrary JSON attached to the root or a child |
| Stumbles / issues | Lemma issue detection | Complete, correctly typed trees improve automated issue extraction |
type is a discriminator. Setting a model on an untyped span does not make it a generation — you must set type: "generation". The SDK typed helpers (recordGeneration / recordTool / recordSpan and their Python equivalents) do this for you.metadata.
Migration path
Install the Lemma SDK
Install the SDK and set
LEMMA_API_KEY and LEMMA_PROJECT_ID. See Setup.- TypeScript
- Python
Wrap each execution in a trace
Replace the Raindrop
begin() / finish() (or trackAi()) that bracketed one interaction with a single lemma.trace() around the whole execution. Put the current user turn on input and let the callback return the final answer.Record generations and tools as typed children
Emit each LLM call with
recordGeneration / record_generation and each tool call with recordTool / record_tool. This is the Lemma equivalent of Raindrop’s auto-instrumented LLM spans, withTool, and trackTool.Add thread and user context
Map
convoId to threadId / thread_id and userId to userId / user_id on the trace so multi-turn conversations group correctly. See Threads & context.Confirm the shape
Turn on debug mode (
LEMMA_DEBUG=1) and check the sending trace log’s spanCount (span_count in Python) to confirm your generations and tools were recorded. Cross-check fields against the trace contract.Re-instrumenting an interaction
The example below is the Lemma equivalent of a Raindropbegin() → tool tracking → finish() flow: one trace, a typed tool, and a generation carrying the full message list. Install and configure the SDK first — see Setup.
- TypeScript
- Python
input and llmInputMessages / llm_input_messages. The root trace input is only the current user turn. For live handles that measure real duration, error recording, and nesting a tool under a measured parent span, see Generations and Tool calls.
If your run spans streaming callbacks or helpers rather than one function, use a TypeScript trace handle and end it from the terminal callback. See Traces.
Pitfalls
| Pitfall | Fix |
|---|---|
| Emitting one Lemma trace per LLM call, mirroring per-event tracking | Wrap the whole execution in one lemma.trace(); record each LLM call as a generation child. |
Using convoId semantics as the trace identity | convoId maps to threadId / thread_id, which groups turns — it is not the per-execution trace id. |
Putting model on the root instead of a generation | Model belongs on a typed generation span; a model on an untyped span does not become a generation. |
| Missing message history | Put the full ordered message list on each generation’s input and llmInputMessages / llm_input_messages, not just the latest user string. |
| Untyped children | Record LLM calls with recordGeneration and tools with recordTool so they render and filter as generations and tools. |
Related pages
Setup
Install the SDK and point it at Lemma.
Trace contract
The exact shape and fields Lemma reads.
Building high-quality traces
Bad → better → best examples for the ideal path.
Instrument an agent
Build a complete Lemma trace one piece at a time.