> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uselemma.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Claude Agent SDK

> Instrument the Claude Agent SDK so each run is one nested trace in Lemma

The [Claude Agent SDK](https://docs.anthropic.com/en/api/agent-sdk/overview) runs Claude as a tool-using agent. Use its [Langfuse integration](https://langfuse.com/integrations/frameworks/claude-agent-sdk-js) to capture model and tool activity, point Langfuse at Lemma, and wrap each run in one root span so the whole execution is a single nested trace.

<Note>
  **One agent execution = one trace.** Wrap the run in a single root span so every model and tool call nests under it. See the [trace contract](/reference/trace-contract).
</Note>

<Note>
  Claude Agent SDK traces **render fully** in Lemma today. Automated [issue detection](/reference/good-vs-bad-traces) is being expanded to this shape — see [Good trace vs bad trace](/reference/good-vs-bad-traces) for current status.
</Note>

## Recipe

<Steps>
  <Step title="Install">
    ```bash theme={null}
    npm install @anthropic-ai/claude-agent-sdk @langfuse/tracing @langfuse/otel @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-proto
    ```
  </Step>

  <Step title="Register the Langfuse → Lemma exporter">
    Register the exporter once, before any agent runs. This matches [Setup](/tracing/instrumentation/setup).

    ```typescript theme={null}
    // instrumentation.ts — imported first, before your app code
    import { LangfuseSpanProcessor } from "@langfuse/otel";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";

    export const lemmaProcessor = new LangfuseSpanProcessor({
      exporter: new OTLPTraceExporter({
        url: process.env.LEMMA_BASE_URL,
        headers: {
          Authorization: `Bearer ${process.env.LEMMA_API_KEY}`,
          "X-Lemma-Project-ID": process.env.LEMMA_PROJECT_ID,
        },
      }),
    });

    new NodeTracerProvider({ spanProcessors: [lemmaProcessor] }).register();
    ```

    Set the environment variables. Lemma-only export needs no `LANGFUSE_*` credentials.

    ```bash theme={null}
    export LEMMA_BASE_URL="https://api.uselemma.ai/otel/v1/traces"
    export LEMMA_API_KEY="lma_..."
    export LEMMA_PROJECT_ID="proj_..."
    ```
  </Step>

  <Step title="Enable the Langfuse integration">
    Follow the [Langfuse Claude Agent SDK guide](https://langfuse.com/integrations/frameworks/claude-agent-sdk-js) to enable span emission for the SDK. The exporter you registered above ships those spans to Lemma.
  </Step>

  <Step title="Wrap the whole run in one root span">
    Wrap the agent loop in a single root span so each model turn and tool call nests under one trace. Record the input and final output on the root, set a stable agent name, and type child observations as `generation` and `tool`.

    ```typescript theme={null}
    import { query } from "@anthropic-ai/claude-agent-sdk";
    import { propagateAttributes, startActiveObservation } from "@langfuse/tracing";

    export async function runSupportAgent(userMessage: string, threadId: string) {
      return await startActiveObservation("support-agent", async (root) => {
        root.update({ input: userMessage });

        return await propagateAttributes(
          {
            traceName: "support-agent",
            sessionId: threadId,
            metadata: { "gen_ai.agent.name": "support-agent" },
          },
          async () => {
            let finalText = "";

            for await (const message of query({ prompt: userMessage })) {
              if (message.type === "assistant") {
                await startActiveObservation(
                  "claude",
                  async (gen) => {
                    gen.update({
                      output: message.message.content,
                      model: message.message.model,
                      usageDetails: {
                        input: message.message.usage.input_tokens,
                        output: message.message.usage.output_tokens,
                      },
                    });
                  },
                  { asType: "generation" },
                );
              }

              if (message.type === "result") {
                finalText = message.result;
              }
            }

            root.update({ output: finalText });
            return finalText;
          },
        );
      });
    }
    ```

    Record each tool invocation as a child `tool` observation the same way, so the trace nests cleanly:

    ```text theme={null}
    support-agent              ← trace root (input, output)
    ├─ claude                  ← generation (model, tokens)
    ├─ search_docs             ← tool call (args, result)
    └─ claude                  ← generation (final answer)
    ```
  </Step>

  <Step title="Flush before the process exits">
    In serverless or other short-lived runtimes, flush so the whole trace ships in one batch.

    ```typescript theme={null}
    import { lemmaProcessor } from "./instrumentation";

    // at the end of a request / serverless handler
    await lemmaProcessor.forceFlush();
    ```
  </Step>
</Steps>

<Warning>
  If model or tool calls show up as their own separate traces, the work ran outside the root's active context. Keep the agent loop inside the `startActiveObservation` callback. See [Troubleshooting](/tracing/troubleshooting/common-problems#every-span-appears-as-a-separate-trace).
</Warning>

## Verify in Lemma

Open the [Lemma dashboard](https://platform.uselemma.ai) → **Traces** and confirm:

* **One trace per run** — a full agent run is one trace, not one per turn.
* **Root has input and output** — the root span shows the user message and the final response.
* **Generations are nested** — each model turn appears as a child generation with model and token usage.
* **Tools are nested** — each tool invocation appears as a child tool span with arguments and result.

## Next steps

<CardGroup cols={2}>
  <Card title="Trace contract" icon="file-check" href="/reference/trace-contract">
    The exact shape Lemma reads.
  </Card>

  <Card title="Setup" icon="plug" href="/tracing/instrumentation/setup">
    Wire the Langfuse → Lemma exporter.
  </Card>

  <Card title="Threads and sessions" icon="users" href="/tracing/instrumentation/context">
    Group multi-turn conversations with a thread id.
  </Card>

  <Card title="Good vs bad traces" icon="circle-check" href="/reference/good-vs-bad-traces">
    What issue detection looks for, per shape.
  </Card>
</CardGroup>
