Skip to main content
When a user turn spans a host and a sandbox such as E2B, record one Lemma trace. The host owns the root and the API key. The child records a journal (local span, tool, and generation records). You apply that journal on the host and ingest() once. A context token is the payload the host exports. It includes traceId, parentSpanId, threadId, userId, and startedAt. The helpers are startTurn / attachTurn / apply (Python start_turn / attach_turn / apply). A thread id groups successive conversation turns. It does not join a host and a sandbox into one turn. For later turns in other processes, see Instrumenting multi-turn agents.

Host, child, and coordinator

The work splits across three roles:
  • Host: process that holds the Lemma client and API key
  • Child: sandbox or worker that records the journal
  • Coordinator: process that applies the journal and sends (the host, unless another process holds the journal)
The host starts the root, exports the token, and calls ingest(). The child never constructs Lemma and never calls /traces/ingest.

Record the host and child as one turn

Export a token on the host, attach in the child, apply the journal, then end once.
1

Start the host root and export a token

Call startTurn / start_turn, start a sandbox span, and export with that span as parentSpanId. In a sandbox, pass the token as env (for example LEMMA_TURN).
2

Attach in the child and record locally

Call attachTurn / attach_turn with the token. Record generations, tools, and spans on the local handle. Emit local.records() on the app’s existing event channel. Do not set LEMMA_API_KEY in the child.
3

Apply the journal and ingest once

On the host, apply the journal, end the sandbox span, then turn.end() or ingest(). Do not call /traces/ingest from the child. Lemma ingest happens once at the end.
This example runs all three steps in one process so you can copy it. Split export / attachTurn / apply across host and child in production.
apply accepts any of:
  • Full journal: local.records()
  • JSON string: a serialized journal
  • One record: a single event
  • Array of records: streamed events
apply only updates the in-memory tree on the host or coordinator. It does not call /traces/ingest. Stream journal events into apply as they arrive, or apply the full journal once. Send to Lemma once with turn.end() or ingest() after the sandbox finishes.

Assemble a trace from a stored journal

When you have a token and journal and no live turn handle, build a TraceContext and ingest() once. The journal can be a file or a queued payload:
Re-applying the same journal is idempotent. Span IDs are stable, so a retried assemble plus ingest does not duplicate children.

Recover when the sandbox exits uncleanly

If the sandbox dies before a complete journal, apply whatever records arrived. End the sandbox span as ERROR, leave incomplete tools as they are, then fail and end() so one payload still goes out.
turn.end() is strict: a 4xx/5xx from Lemma throws so you can retry the same payload. Automatic lemma.trace() delivery fails open.

Helpers for each role

Journal fields are camelCase in both languages (traceId, parentSpanId, parentId, startedAt, …). A TypeScript host can apply a Python child’s journal. A Python host can apply a TypeScript child’s journal.

Agent traces

Callback traces, handles, and ingest() in one process.

Instrumenting multi-turn agents

New root per user turn, same threadId for the conversation.

Threads & context

threadId and userId on the root.

Common issues

Two roots, missing children, sparse ingest.