Skip to main content
Claude Agent SDK hero If you are building AI systems with the Claude Agent SDK — 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.
PathSetupWhen 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 + OTLPInstall openinference-instrumentation-claude-agent-sdk and point its OTLP exporter at Openlayer’s OTel endpointYou 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.envYou 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. 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

# 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())

See full Python example

See full TypeScript example

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.
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.

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
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 that run at a regular cadence on top of them. Refer to the Monitoring overview for details on Openlayer’s monitoring mode, to the Publishing data guide for setup, or to the Tracing guide to understand how to trace more complex systems.

Path 2 — OpenInference + OTLP

If you already use OpenTelemetry across your stack, you can use Arize’s OpenInference instrumentation for the Claude Agent SDK and point its OTLP exporter at Openlayer. This path emits spans that follow the OpenInference semantic conventions (e.g. openinference.span.kind=AGENT|LLM|TOOL, llm.input_messages.*, tool.parameters). Openlayer ingests them via its OpenTelemetry endpoint.
Python
# 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()
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 for the full endpoint reference.

Path 3 — Native Claude Agent SDK OTel (beta)

The Claude Agent SDK’s bundled Claude Code CLI has built-in OpenTelemetry instrumentation 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.
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.
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())
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 wrapperOpenInference + OTLPNative SDK OTel
SetupOne line~10 lines + 4 packagesEnv vars only
Step type namesAGENT, 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, 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, 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.