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

# Claude Agent SDK

> Learn how to trace and evaluate agents built with Anthropic's Claude Agent SDK using Openlayer

<img style={{ borderRadius: "0.5rem" }} src="https://mintcdn.com/openlayer-44/kAhn5-SXrRWpg7fS/images/integrations/claude_agent_sdk_hero.png?fit=max&auto=format&n=kAhn5-SXrRWpg7fS&q=85&s=418dd4a9be30eeb0d6e2465e80163899" alt="Claude Agent SDK hero" width="2000" height="1046" data-path="images/integrations/claude_agent_sdk_hero.png" />

If you are building AI systems with the [Claude Agent SDK](https://docs.claude.com/en/api/agent-sdk/overview) — Anthropic's Python and TypeScript library that exposes the same agent loop, built-in tools, hooks, sub-agents, and MCP support that power Claude Code — you can stream your agents' traces to Openlayer for monitoring and evaluation.

This integration guide shows how to comprehensively capture each `query()` as a trace with nested steps for assistant turns, tool calls (including MCP and sub-agents), session metadata, cost, and tokens.

## Choosing an integration path

Openlayer supports **three** different ways to instrument the Claude Agent SDK. They all land traces in the same Openlayer pipeline; pick the one that fits your stack best.

| Path                                       | Setup                                                                                                             | When to use it                                                                                                                                                                |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **1. Openlayer wrapper** (recommended)     | One line — `trace_claude_agent_sdk()` (Python) or a drop-in `query` import (TypeScript)                           | You want the richest metadata out of the box (system prompt, resolved agent config, sub-agent definitions, raw assistant messages, full `ResultMessage`) and the least setup. |
| **2. OpenInference + OTLP**                | Install `openinference-instrumentation-claude-agent-sdk` and point its OTLP exporter at Openlayer's OTel endpoint | You already use OpenTelemetry across your stack and want Claude Agent SDK traces to flow through the same collector.                                                          |
| **3. Native Claude Agent SDK OTel (beta)** | Set `CLAUDE_CODE_ENABLE_TELEMETRY=1` and other `OTEL_*` env vars on `ClaudeAgentOptions.env`                      | You can't add new dependencies and you're comfortable with the SDK's beta-gated telemetry path. Zero code change.                                                             |

The rest of this page walks through each path. **If you're not sure, start with the Openlayer wrapper.**

## Path 1 — Openlayer wrapper (recommended)

A single line of setup auto-instruments every call to `query()` and `ClaudeSDKClient`. The wrapper:

* Wraps the agent loop into a root `AGENT` step per `query()` call.
* Captures each assistant turn as a nested `CHAT_COMPLETION` step (text, thinking, tokens, model).
* Captures each tool invocation as a nested `TOOL` step bracketed by the SDK's `PreToolUse` / `PostToolUse` / `PostToolUseFailure` hooks. MCP tools are parsed (`mcp__server__tool`) into `mcp_server` and `mcp_tool_name` metadata.
* Represents sub-agent dispatches (the `Agent` tool) as nested `AGENT` steps. The sub-agent's own assistant turns and tool calls nest underneath via `parent_tool_use_id`.
* Composes with any hooks you already have — your hooks are appended to, never replaced.

### Monitoring

<CodeGroup>
  ```python Python theme={null}
  # 1. Set the environment variables
  import os

  os.environ["ANTHROPIC_API_KEY"] = "YOUR_ANTHROPIC_API_KEY_HERE"
  os.environ["OPENLAYER_API_KEY"] = "YOUR_OPENLAYER_API_KEY_HERE"
  os.environ["OPENLAYER_INFERENCE_PIPELINE_ID"] = "YOUR_OPENLAYER_INFERENCE_PIPELINE_ID_HERE"

  # 2. Enable tracing with one line
  from openlayer.lib import trace_claude_agent_sdk

  trace_claude_agent_sdk()

  # 3. Use the Claude Agent SDK as you normally would.
  # Every `query()` is auto-traced.
  import asyncio
  from claude_agent_sdk import ClaudeAgentOptions, query

  async def main():
      async for message in query(
          prompt="Find any .py files in this directory and summarize them.",
          options=ClaudeAgentOptions(
              model="claude-haiku-4-5",
              allowed_tools=["Read", "Glob", "Grep"],
          ),
      ):
          if hasattr(message, "result"):
              print(message.result)

  asyncio.run(main())
  ```

  ```javascript TypeScript theme={null}
  // 1. Set the environment variables:
  //   ANTHROPIC_API_KEY
  //   OPENLAYER_API_KEY
  //   OPENLAYER_INFERENCE_PIPELINE_ID

  // 2. Use the drop-in `query` from Openlayer's integration subpath
  // (same signature as @anthropic-ai/claude-agent-sdk's `query`,
  // just auto-traced).
  import { query } from "openlayer/lib/integrations/claudeAgentSdk";

  for await (const message of query({
    prompt: "Find any .ts files in this directory and summarize them.",
    options: {
      model: "claude-haiku-4-5",
      allowedTools: ["Read", "Glob", "Grep"],
    },
  })) {
    if ("result" in message) console.log(message.result);
  }

  // Alternative — if you can't change imports, call traceClaudeAgentSdk()
  // once at startup and keep importing `query` from the original package:
  //
  //   import { query } from "@anthropic-ai/claude-agent-sdk";
  //   import { traceClaudeAgentSdk } from "openlayer/lib/integrations/claudeAgentSdk";
  //   traceClaudeAgentSdk();
  ```
</CodeGroup>

<CardGroup cols={2}>
  <Card title="See full Python example" icon="python" iconType="duotone" href="https://colab.research.google.com/github/openlayer-ai/openlayer-python/blob/main/examples/tracing/claude_agent_sdk/claude_agent_sdk_tracing.ipynb" />

  <Card
    title="See full TypeScript example"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="#7A58EE"
    width="24px"
    height="24px"
    viewBox="0 0 50 50"
  >
    <path d="M45,4H5C4.447,4,4,4.448,4,5v40c0,0.552,0.447,1,1,1h40c0.553,0,1-0.448,1-1V5C46,4.448,45.553,4,45,4z M29,26.445h-5V42h-4 V26.445h-5V23h14V26.445z M30.121,41.112v-4.158c0,0,2.271,1.712,4.996,1.712c2.725,0,2.62-1.782,2.62-2.026 c0-2.586-7.721-2.586-7.721-8.315c0-7.791,11.25-4.717,11.25-4.717l-0.14,3.704c0,0-1.887-1.258-4.018-1.258s-2.9,1.013-2.9,2.096 c0,2.795,7.791,2.516,7.791,8.141C42,44.955,30.121,41.112,30.121,41.112z"></path>
  </svg>
}
    iconType="duotone"
    href="https://github.com/openlayer-ai/openlayer-ts/blob/main/examples/claude-agent-sdk-tracing.ts"
  />
</CardGroup>

Once the code is instrumented, every `query()` call is published to Openlayer with:

* **Agent loop** with the resolved configuration: model, tools, MCP servers, skills, plugins, permission mode, working directory.
* **System prompt** and **sub-agent definitions** (per registered sub-agent: description, prompt, tools, model) captured on the root `AGENT` step.
* **Assistant turns** with text, thinking blocks, prompt/completion tokens, and the raw assistant-message JSON.
* **Tool calls** with input arguments, output, latency, `tool_use_id`, and `mcp_server` / `mcp_tool_name` for MCP tools.
* **Sub-agent dispatches** as nested `AGENT` steps. The sub-agent's own assistant turns and tool calls nest underneath.
* **Session metadata** (`session_id`, `num_turns`, `stop_reason`, `is_error`, `model_usage` breakdown, `permission_denials`) and the full `ResultMessage` JSON.
* **Cost** (`total_cost_usd`) and total **tokens**.

<Note>
  The Openlayer wrapper composes with hooks you've already configured. Hooks you
  pass via `ClaudeAgentOptions.hooks` are preserved — Openlayer's hooks are
  appended and act only as observers (they always return `{}`), so your hooks
  retain full control over `permissionDecision`, `updatedInput`, etc.
</Note>

### Multi-stage orchestration

If you make multiple `query()` calls that you want to appear as a single trace, wrap them in `tracer.create_step()`. Each `query()` becomes a nested `AGENT` step under your outer step.

```python Python theme={null}
from openlayer.lib.tracing import tracer
from openlayer.lib.tracing.enums import StepType

with tracer.create_step(name="codebase-audit", step_type=StepType.AGENT):
    async for m in query(prompt="Inventory the codebase", options=opts1):
        ...
    async for m in query(prompt="Now review the picked file", options=opts2):
        ...
```

After your AI system requests are continuously published, you can [create tests](/tests/overview) that run at a regular cadence on top of them.

Refer to the [Monitoring overview](/monitoring/overview) for details on Openlayer's monitoring mode, to the [Publishing data guide](/monitoring/publishing-data) for setup, or to the [Tracing guide](/monitoring/tracing) to understand how to trace more complex systems.

## Path 2 — OpenInference + OTLP

If you already use [OpenTelemetry](https://opentelemetry.io/) across your stack, you can use Arize's [OpenInference instrumentation for the Claude Agent SDK](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-claude-agent-sdk) and point its OTLP exporter at Openlayer.

This path emits spans that follow the [OpenInference semantic conventions](https://github.com/Arize-ai/openinference/blob/main/spec/semantic_conventions.md) (e.g. `openinference.span.kind=AGENT|LLM|TOOL`, `llm.input_messages.*`, `tool.parameters`). Openlayer ingests them via its [OpenTelemetry endpoint](/integrations/opentelemetry).

```python Python theme={null}
# pip install openinference-instrumentation-claude-agent-sdk \
#             openinference-instrumentation-anthropic \
#             opentelemetry-sdk \
#             opentelemetry-exporter-otlp-proto-http

import os
from opentelemetry import trace
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from openinference.instrumentation.claude_agent_sdk import ClaudeAgentSDKInstrumentor
from openinference.instrumentation.anthropic import AnthropicInstrumentor

# 1. Wire the OTLP/HTTP exporter to Openlayer's OTel endpoint.
exporter = OTLPSpanExporter(
    endpoint="https://api.openlayer.com/v1/otel/v1/traces",
    headers={
        "Authorization": f"Bearer {os.environ['OPENLAYER_API_KEY']}",
        "x-bt-parent": f"pipeline_id:{os.environ['OPENLAYER_INFERENCE_PIPELINE_ID']}",
    },
)
tracer_provider = trace_sdk.TracerProvider()
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(tracer_provider)

# 2. Attach both instrumentors. The Claude Agent SDK instrumentor handles the
# agent loop and tools; the Anthropic instrumentor enriches the underlying
# LLM calls with model name, input/output messages, and token counts.
ClaudeAgentSDKInstrumentor().instrument(tracer_provider=tracer_provider)
AnthropicInstrumentor().instrument(tracer_provider=tracer_provider)

# 3. Use the SDK normally — traces flow to Openlayer via OTLP.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    async for message in query(
        prompt="Find any .py files in this directory.",
        options=ClaudeAgentOptions(model="claude-haiku-4-5", allowed_tools=["Glob"]),
    ):
        print(message)

asyncio.run(main())

# Ensure pending spans flush before the process exits.
tracer_provider.shutdown()
```

<Note>
  Openlayer's OTel endpoint accepts OTLP HTTP/protobuf at
  `https://api.openlayer.com/v1/otel/v1/traces`. The `Authorization` header
  carries your Openlayer API key, and `x-bt-parent` routes the trace to the
  correct inference pipeline. See the [OpenTelemetry integration
  page](/integrations/opentelemetry) for the full endpoint reference.
</Note>

## Path 3 — Native Claude Agent SDK OTel (beta)

The Claude Agent SDK's bundled Claude Code CLI has [built-in OpenTelemetry instrumentation](https://docs.claude.com/en/api/agent-sdk/observability) that emits `claude_code.interaction`, `claude_code.llm_request`, `claude_code.tool`, and `claude_code.tool.execution` spans. You can point it directly at Openlayer's OTel endpoint by setting environment variables — no Openlayer or OpenInference packages required.

<Warning>
  Native SDK traces are in **beta**. Span names and attributes may change
  between SDK releases. Tool inputs and outputs are redacted by default; enable
  them with `OTEL_LOG_TOOL_DETAILS=1` and `OTEL_LOG_TOOL_CONTENT=1` if you need
  them.
</Warning>

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  import os
  from claude_agent_sdk import query, ClaudeAgentOptions

  OTEL_ENV = {
      # Enable telemetry + the (beta) traces signal.
      "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
      "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",

      # Traces only — disable other signals if you don't want them.
      "OTEL_TRACES_EXPORTER": "otlp",
      "OTEL_METRICS_EXPORTER": "none",
      "OTEL_LOGS_EXPORTER": "none",

      # OTLP/HTTP -> Openlayer.
      "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
      "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "https://api.openlayer.com/v1/otel/v1/traces",
      "OTEL_EXPORTER_OTLP_TRACES_HEADERS": (
          f"Authorization=Bearer {os.environ['OPENLAYER_API_KEY']},"
          f"x-bt-parent=pipeline_id:{os.environ['OPENLAYER_INFERENCE_PIPELINE_ID']}"
      ),

      # Optional: surface tool inputs / outputs in spans (off by default).
      "OTEL_LOG_TOOL_DETAILS": "1",
      "OTEL_LOG_TOOL_CONTENT": "1",
  }

  async def main():
      options = ClaudeAgentOptions(
          model="claude-haiku-4-5",
          env=OTEL_ENV,  # Passed through to the CLI subprocess.
      )
      async for message in query(prompt="List the files in this directory", options=options):
          if hasattr(message, "result"):
              print(message.result)

  asyncio.run(main())
  ```

  ```javascript TypeScript theme={null}
  import { query } from "@anthropic-ai/claude-agent-sdk";

  const otelEnv = {
    CLAUDE_CODE_ENABLE_TELEMETRY: "1",
    CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",

    OTEL_TRACES_EXPORTER: "otlp",
    OTEL_METRICS_EXPORTER: "none",
    OTEL_LOGS_EXPORTER: "none",

    OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
    OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:
      "https://api.openlayer.com/v1/otel/v1/traces",
    OTEL_EXPORTER_OTLP_TRACES_HEADERS:
      `Authorization=Bearer ${process.env.OPENLAYER_API_KEY},` +
      `x-bt-parent=pipeline_id:${process.env.OPENLAYER_INFERENCE_PIPELINE_ID}`,

    OTEL_LOG_TOOL_DETAILS: "1",
    OTEL_LOG_TOOL_CONTENT: "1",
  };

  for await (const message of query({
    prompt: "List the files in this directory",
    // In TypeScript, `env` REPLACES the inherited environment, so spread
    // process.env first so PATH, ANTHROPIC_API_KEY, etc. are preserved.
    options: { env: { ...process.env, ...otelEnv } },
  })) {
    if ("result" in message) console.log(message.result);
  }
  ```
</CodeGroup>

When this path is active, traces in your Openlayer pipeline appear with span names like `claude_code.interaction` (one per turn), `claude_code.llm_request` (one per Claude API call), and `claude_code.tool` (one per tool invocation, with `claude_code.tool.execution` as a child).

### Bonus: W3C trace context propagation

If your application already starts OpenTelemetry spans before calling `query()`, the SDK reads `TRACEPARENT` and `TRACESTATE` from the subprocess environment and parents `claude_code.interaction` under your span automatically — so an agent run appears inside your existing distributed trace.

## Comparison: which path produces what

|                                            | Openlayer wrapper                                      | OpenInference + OTLP                               | Native SDK OTel                                                                    |
| ------------------------------------------ | ------------------------------------------------------ | -------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Setup                                      | One line                                               | \~10 lines + 4 packages                            | Env vars only                                                                      |
| Step type names                            | `AGENT`, `CHAT_COMPLETION`, `TOOL` (Openlayer's model) | `openinference.span.kind` (`AGENT`, `LLM`, `TOOL`) | `claude_code.*`                                                                    |
| System prompt captured                     | ✅                                                      | ⚠️ partial                                         | ⚠️ only with `OTEL_LOG_USER_PROMPTS=1` (beta-gated)                                |
| Sub-agent definitions on AGENT step        | ✅                                                      | ❌                                                  | ❌                                                                                  |
| Raw `ResultMessage` JSON                   | ✅                                                      | ❌                                                  | ❌                                                                                  |
| MCP `server` / `tool_name` parsed          | ✅                                                      | ✅                                                  | ✅                                                                                  |
| Sub-agent nesting via `parent_tool_use_id` | ✅                                                      | ✅                                                  | ✅ (via W3C trace context)                                                          |
| Tool inputs/outputs in trace               | ✅                                                      | ✅                                                  | ⚠️ off by default — requires `OTEL_LOG_TOOL_DETAILS=1` + `OTEL_LOG_TOOL_CONTENT=1` |
| Cost (`total_cost_usd`)                    | ✅                                                      | ✅                                                  | ✅                                                                                  |
| Stable API                                 | ✅                                                      | ✅                                                  | ⚠️ beta — span names may change                                                    |
| Portable to other OTel backends            | ❌                                                      | ✅                                                  | ✅                                                                                  |

## Development

In [development mode](/development/overview), Openlayer becomes a step in your CI/CD pipeline, and your tests get automatically evaluated after being triggered by some events.

Openlayer tests often rely on your AI system's outputs on a validation dataset. As discussed in the [Configuring output generation guide](/development/configuring-output-generation), you have two options:

1. either provide a way for Openlayer to run your AI system on your datasets, or
2. before pushing, generate the model outputs yourself and push them alongside your artifacts.

For AI systems built with the Claude Agent SDK, if you are **not** computing your system's outputs yourself, you must provide your **Anthropic API key**.

To do so, navigate to "**Workspace settings**" → "**Environment variables**," and click on "Add secret" to add your `ANTHROPIC_API_KEY`.

If you don't add the required Anthropic API key, you'll encounter a "Missing API key" error when Openlayer tries to run your AI system to get its outputs.
