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

# Agents Built with the Claude Agent SDK

> Trace agents built with the Claude Agent SDK in Arize AX using OpenInference, in Python or TypeScript, and group runs by conversation with session IDs.

The [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview) is Anthropic's framework for building agents on the same harness that powers Claude Code, driven by the `query()` function for one-off tasks or the `ClaudeSDKClient` class for multi-turn conversations. Once an agent reaches production, you need to see what it does on every run: which tools it calls, in what order, with what inputs, and how many tokens each turn costs.

Add Arize AX tracing to an agent you have already built with the Claude Agent SDK. You attach the OpenInference instrumentor for the SDK once, in [Python](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-claude-agent-sdk) or [TypeScript](https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-claude-agent-sdk), and every agent run and tool call is captured as a structured trace without changing your agent logic.

<Note>
  If your agent is built directly on the Anthropic SDK (a Messages API tool loop) rather than the Claude Agent SDK, see [Agents Built with the Anthropic SDK](/ax/cookbooks/instrument/anthropic-sdk-guide) instead. That path captures model calls automatically and has you add manual spans for the loop and tools.
</Note>

## Overview

You will instrument an existing Claude Agent SDK application, run it, and read the resulting traces in Arize AX. The steps apply to any app built on the SDK, whether it runs as a script, a worker, or behind a web framework such as FastAPI or Express.

Intended for developers who have a working Claude Agent SDK agent and want production observability for it. It assumes you are comfortable with:

* Python or TypeScript, including `async`/`await` and async iteration
* The Claude Agent SDK `query()` entry point (and `ClaudeSDKClient` for multi-turn work)
* Environment variables and package installation

By the end, you will be able to:

* Initialize Arize AX tracing so the SDK is instrumented before it runs
* Capture agent runs and tool calls as traces with no changes to your agent logic
* Group traces by conversation using session IDs
* Verify and read the trace tree in Arize AX

## Before you start

You need:

* An existing agent built with the Claude Agent SDK
* An Arize AX account ([sign up](https://arize.com/sign-up/))
* An `ANTHROPIC_API_KEY` from the [Claude Console](https://console.anthropic.com/)

## Get your Arize AX credentials

Sign in to your [Arize AX account](https://app.arize.com/) and create a tracing project from **Projects → New Tracing Project**. The setup page shows the credentials you need: copy your **Space ID**, then click **Create API Key** to generate a key and save it somewhere safe. You set them as `ARIZE_SPACE_ID` and `ARIZE_API_KEY` when you configure your environment below, and they route your traces to the correct space.

<Frame>
  <img src="https://storage.googleapis.com/arize-assets/doc-images/quickstarts/new-tracing-project-page.png" alt="New Tracing Project setup page in Arize AX showing the Space ID and the Create API Key button" />
</Frame>

<Tabs>
  <Tab title="Python">
    You need **Python 3.10 or later**, plus [Node.js](https://nodejs.org/) 18 or later and the Claude Code CLI (`npm install -g @anthropic-ai/claude-code`), which the Agent SDK runs as a subprocess even from Python.

    <Steps>
      <Step title="Install the instrumentor">
        Install the OpenInference instrumentor for the Claude Agent SDK alongside the SDK and tracing dependencies.

        ```bash theme={null}
        pip install arize-otel \
          openinference-instrumentation-claude-agent-sdk \
          openinference-instrumentation \
          claude-agent-sdk
        ```
      </Step>

      <Step title="Configure credentials">
        Set your Arize AX and Anthropic credentials as environment variables. The tracing setup reads them at startup, so you keep secrets out of your source.

        ```bash theme={null}
        export ARIZE_SPACE_ID="<your-space-id>"
        export ARIZE_API_KEY="<your-api-key>"
        export ARIZE_PROJECT_NAME="claude-agent-sdk-app"
        export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
        ```

        `ARIZE_PROJECT_NAME` is the project your traces land in. Name it for the app so runs stay grouped where you expect them.
      </Step>

      <Step title="Initialize tracing before the SDK runs">
        Set up tracing in a dedicated module so it runs once, before your agent code uses the SDK. `register()` builds a tracer provider from your Arize credentials, and `ClaudeAgentSDKInstrumentor().instrument(...)` patches the SDK to emit spans.

        ```python tracing.py theme={null}
        import os

        from arize.otel import register
        from openinference.instrumentation.claude_agent_sdk import (
            ClaudeAgentSDKInstrumentor,
        )

        # register() reads ARIZE_SPACE_ID and ARIZE_API_KEY from the environment
        # when the arguments are omitted. Pass them explicitly if you prefer.
        tracer_provider = register(
            space_id=os.environ["ARIZE_SPACE_ID"],
            api_key=os.environ["ARIZE_API_KEY"],
            project_name=os.environ.get("ARIZE_PROJECT_NAME", "claude-agent-sdk-app"),
        )

        # Patch the Claude Agent SDK so every run and tool call is traced.
        ClaudeAgentSDKInstrumentor().instrument(tracer_provider=tracer_provider)
        ```

        Import this module before any `claude_agent_sdk` import in your entry point. The instrumentor patches the SDK at import time, so an agent client created before instrumentation runs will not emit spans.

        ```python main.py theme={null}
        import tracing  # noqa: F401 - must be imported before claude_agent_sdk

        from claude_agent_sdk import query, ClaudeAgentOptions
        # ... the rest of your application
        ```

        <Note>
          If your app serves the SDK from a web framework such as FastAPI or Flask, instrument the Agent SDK itself. Web-server instrumentation records HTTP requests, not the agent, tool, and model activity that make up an agent trace.
        </Note>
      </Step>

      <Step title="Run your agent">
        Run the agent the way you already do. The instrumentor traces the run with no change to your agent logic.

        ```python theme={null}
        import tracing  # noqa: F401 - must be imported first

        import asyncio

        from claude_agent_sdk import query, ClaudeAgentOptions


        async def main():
            async for message in query(
                prompt="What files changed in the last commit?",
                options=ClaudeAgentOptions(allowed_tools=["Bash", "Glob"]),
            ):
                if hasattr(message, "result"):
                    print(message.result)


        asyncio.run(main())
        ```

        For multi-turn conversations that retain context, `ClaudeSDKClient` is traced the same way, producing one AGENT span per response turn. Custom tools you register with the SDK's in-process MCP server support are traced the same way too: each tool the agent invokes appears as a child span under the agent run, with its inputs and outputs.
      </Step>

      <Step title="Group traces by conversation">
        Auto-instrumentation captures each run on its own. To follow a multi-turn conversation as a single thread, tag its runs with a shared session ID that is stable for the conversation, such as the user or thread ID. Wrap the agent call in the `using_session()` context manager.

        ```python theme={null}
        from openinference.instrumentation import using_session

        async def handle_turn(session_id: str, prompt: str):
            with using_session(session_id):
                async for message in query(prompt=prompt):
                    if hasattr(message, "result"):
                        print(message.result)
        ```

        Every span emitted inside the block carries the same `session.id`, so Arize AX groups the turns together and you can replay a full conversation.
      </Step>

      <Step title="Verify in Arize AX">
        Open your Arize AX space and select the project you set in `ARIZE_PROJECT_NAME`. Within about 30 seconds of a run, a new trace appears with an AGENT root span carrying the prompt as input, the result as output, and `session.id`, `llm.model_name`, and token-count metadata. Tools the agent invoked appear as child TOOL spans with their inputs and outputs.

        If no traces appear, see [Troubleshooting](#troubleshooting).
      </Step>
    </Steps>
  </Tab>

  <Tab title="TypeScript">
    You need **Node.js 18 or later** and the Claude Code CLI (`npm install -g @anthropic-ai/claude-code`), which the Agent SDK runs as a subprocess.

    <Steps>
      <Step title="Install the instrumentor">
        Install the OpenInference instrumentor for the Claude Agent SDK alongside the SDK and tracing dependencies.

        ```bash theme={null}
        npm install @anthropic-ai/claude-agent-sdk \
          @arizeai/openinference-instrumentation-claude-agent-sdk \
          @arizeai/openinference-core \
          @opentelemetry/api \
          @opentelemetry/sdk-trace-node \
          @opentelemetry/exporter-trace-otlp-proto \
          @opentelemetry/resources
        ```
      </Step>

      <Step title="Configure credentials">
        Set your Arize AX and Anthropic credentials as environment variables. The tracing setup reads them at startup, so you keep secrets out of your source.

        ```bash theme={null}
        export ARIZE_SPACE_ID="<your-space-id>"
        export ARIZE_API_KEY="<your-api-key>"
        export ARIZE_PROJECT_NAME="claude-agent-sdk-app"
        export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
        ```

        `ARIZE_PROJECT_NAME` is the project your traces land in. Name it for the app so runs stay grouped where you expect them.
      </Step>

      <Step title="Initialize tracing before the SDK runs">
        Set up tracing in a dedicated module so it runs once, before your agent code uses the SDK. Build a tracer provider that exports to Arize AX, then instrument the SDK. The SDK ships as a frozen ES module, so `manuallyInstrument()` cannot patch its exports in place. Copy the module into a mutable object, patch that copy, and export it so your app calls `query()` on the patched copy.

        ```typescript instrumentation.ts theme={null}
        import * as ClaudeAgentSDKModule from "@anthropic-ai/claude-agent-sdk";
        import { ClaudeAgentSDKInstrumentation } from "@arizeai/openinference-instrumentation-claude-agent-sdk";
        import {
          NodeTracerProvider,
          SimpleSpanProcessor,
        } from "@opentelemetry/sdk-trace-node";
        import { resourceFromAttributes } from "@opentelemetry/resources";
        import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";

        const provider = new NodeTracerProvider({
          resource: resourceFromAttributes({
            "openinference.project.name":
              process.env.ARIZE_PROJECT_NAME ?? "claude-agent-sdk-app",
          }),
          spanProcessors: [
            new SimpleSpanProcessor(
              new OTLPTraceExporter({
                url: "https://otlp.arize.com/v1/traces",
                headers: {
                  "arize-space-id": process.env.ARIZE_SPACE_ID!,
                  "arize-api-key": process.env.ARIZE_API_KEY!,
                },
              }),
            ),
          ],
        });
        provider.register();

        const instrumentation = new ClaudeAgentSDKInstrumentation({
          tracerProvider: provider,
        });

        // The ESM namespace is frozen, so manuallyInstrument() cannot patch it in
        // place. Copy it into a mutable object, patch that, and export the copy.
        export const ClaudeAgentSDK = { ...ClaudeAgentSDKModule };
        instrumentation.manuallyInstrument(ClaudeAgentSDK);
        ```

        <Note>
          If your app serves the SDK from a web framework such as Express, instrument the Agent SDK itself. Web-server instrumentation records HTTP requests, not the agent, tool, and model activity that make up an agent trace.
        </Note>
      </Step>

      <Step title="Run your agent">
        Import the SDK from your `instrumentation.ts`, so the patch is applied, and call `query()` on it.

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

        for await (const message of ClaudeAgentSDK.query({
          prompt: "What files changed in the last commit?",
          options: { allowedTools: ["Bash", "Glob"] },
        })) {
          console.log(message);
        }
        ```

        Grant tools and other settings through the `options` argument to `query()`. Each tool the agent invokes appears as a child span under the run, including custom tools you register with the SDK's in-process MCP server support, with their inputs and outputs.
      </Step>

      <Step title="Group traces by conversation">
        Auto-instrumentation captures each run on its own. To follow a multi-turn conversation as a single thread, tag its runs with a shared session ID that is stable for the conversation, such as the user or thread ID. Set the session on the active context with `setSession`, then run the agent inside `context.with`.

        ```typescript theme={null}
        import { context } from "@opentelemetry/api";
        import { setSession } from "@arizeai/openinference-core";
        import { ClaudeAgentSDK } from "./instrumentation";

        async function handleTurn(sessionId: string, prompt: string) {
          await context.with(
            setSession(context.active(), { sessionId }),
            async () => {
              for await (const message of ClaudeAgentSDK.query({ prompt })) {
                console.log(message);
              }
            },
          );
        }
        ```

        Every span emitted inside the block carries the same `session.id`, so Arize AX groups the turns together and you can replay a full conversation.
      </Step>

      <Step title="Verify in Arize AX">
        Open your Arize AX space and select the project you set in `ARIZE_PROJECT_NAME`. Within about 30 seconds of a run, a new trace appears with an AGENT root span carrying the prompt as input, the result as output, and `session.id`, `llm.model_name`, and token-count metadata. Tools the agent invoked appear as child TOOL spans with their inputs and outputs.

        If no traces appear, see [Troubleshooting](#troubleshooting).
      </Step>
    </Steps>
  </Tab>
</Tabs>

## What gets captured

The Claude Agent SDK instrumentor records the shape of each agent run:

* **Agent spans.** One AGENT root span per `query()` call or per `ClaudeSDKClient` response turn, carrying the prompt, the final result, the model name, and token counts.
* **Tool spans.** One TOOL child span per tool invocation, with the tool name, inputs, and outputs, so you can see what the agent decided to do and what each tool returned.
* **Session grouping.** The `session.id` you set, which threads related runs into one conversation.

The Agent SDK runs Claude through the Claude Code CLI as a subprocess rather than calling the model in your process, so the agent span summarizes model-level detail such as the model name and token counts. The instrumentor does not emit a separate LLM span for each underlying generation. In Python, you can add the Anthropic instrumentor to capture per-generation input, output, and token usage as their own LLM spans; see [Capture model-level LLM spans](#capture-model-level-llm-spans) below.

## Capture model-level LLM spans

In Python, the Agent SDK instrumentor emits AGENT and TOOL spans, with the model name and token counts summarized on the agent span, but it does not emit a separate LLM span for each underlying model generation. To record per-generation input, output, and token usage as their own spans, add the Anthropic instrumentor next to this one in your tracing module:

```bash theme={null}
pip install openinference-instrumentation-anthropic
```

```python theme={null}
from openinference.instrumentation.anthropic import AnthropicInstrumentor

# Add this next to ClaudeAgentSDKInstrumentor in tracing.py.
AnthropicInstrumentor().instrument(tracer_provider=tracer_provider)
```

For the full setup, see the [Claude Agent SDK integration reference](/ax/integrations/python-agent-frameworks/claude-agent-sdk/claude-agent-sdk-tracing#capture-llm-spans).

## Troubleshooting

<Tabs>
  <Tab title="Python">
    * **No traces in Arize AX.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs your app. Enable OpenTelemetry debug logs with `export OTEL_LOG_LEVEL=debug` and rerun.
    * **Agent spans missing.** `ClaudeAgentSDKInstrumentor().instrument(...)` must run before `query()` or `ClaudeSDKClient` is used. Make sure your tracing module is the first import in your entry point.
    * **`claude` executable not found.** The Agent SDK runs the Claude Code CLI as a subprocess. Install it with `npm install -g @anthropic-ai/claude-code`, or point the SDK at an existing binary with `CLAUDE_CODE_EXECUTABLE`.
    * **`401` or `404` from Anthropic.** Verify `ANTHROPIC_API_KEY` is set and valid, and that your key has access to the model your agent requests.
    * **Tool spans expected but not present.** TOOL spans emit only when the agent invokes a tool. Grant tools through `ClaudeAgentOptions(allowed_tools=[...])` and give a prompt that requires them.
  </Tab>

  <Tab title="TypeScript">
    * **No traces in Arize AX.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs your app. Enable OpenTelemetry debug logs with `export OTEL_LOG_LEVEL=debug` and rerun.
    * **`Cannot assign to read only property 'query'`, or no spans.** A native ES module namespace is frozen, so `manuallyInstrument()` cannot patch it directly. Copy it into a mutable object (`{ ...ClaudeAgentSDKModule }`), patch and export that copy, and call `query()` on the export rather than on the original `@anthropic-ai/claude-agent-sdk` import.
    * **`claude` executable not found.** The Agent SDK runs the Claude Code CLI as a subprocess. Install it with `npm install -g @anthropic-ai/claude-code`, or point the SDK at an existing binary with `CLAUDE_CODE_EXECUTABLE`.
    * **`401` or `404` from Anthropic.** Verify `ANTHROPIC_API_KEY` is set and valid, and that your key has access to the model your agent requests.
    * **Tool spans expected but not present.** TOOL spans emit only when the agent invokes a tool. Grant tools through the `options` argument and give a prompt that requires them.
  </Tab>
</Tabs>

## Next steps

<CardGroup cols={2}>
  <Card title="Set up sessions" icon="layer-group" href="/ax/instrument/set-up-sessions">
    Group multi-turn runs into conversations with session and user IDs.
  </Card>

  <Card title="Customize your traces" icon="sliders" href="/ax/instrument/customize-your-traces">
    Attach metadata, tags, and custom attributes to your spans.
  </Card>

  <Card title="Evaluate agents" icon="clipboard-check" href="/ax/concepts/evaluators/evaluating-agents">
    Score tool selection, tool results, and goal completion.
  </Card>

  <Card title="Claude Agent SDK integration" icon="book-open" href="/ax/integrations/python-agent-frameworks/claude-agent-sdk/claude-agent-sdk-tracing">
    The reference page for the instrumentor, including capturing model-level spans.
  </Card>
</CardGroup>
