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

# Claude Managed Agents

> Send OpenTelemetry traces of Claude Managed Agents sessions to Phoenix from the Claude Console and the Claude SDK, with turns, model requests, and tools.

[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) runs your agent loop on Anthropic's servers and executes its tools in a sandbox that Anthropic hosts. The Claude Platform can [export OpenTelemetry traces](https://platform.claude.com/docs/en/manage-claude/opentelemetry) of each session, using the [OpenTelemetry GenAI semantic conventions](/docs/phoenix/tracing/concepts-tracing/otel-openinference/semantic-conventions). Phoenix converts those spans to OpenInference when they arrive. You can then inspect every agent turn, model request, and tool use in a session, with prompts, responses, and token usage.

OpenTelemetry export from the Claude API is now in public beta. Span names and attributes can change during the beta.

<Note>
  This page covers Anthropic's hosted Claude Managed Agents product. For the Claude Agent SDK, see [Claude Agent SDK for Python](/docs/phoenix/integrations/python/claude-agent-sdk) and [Claude Agent SDK for TypeScript](/docs/phoenix/integrations/typescript/claude-agent-sdk).
</Note>

There are two complementary ways to send spans to Phoenix, and most applications use both:

* **From the Claude Console.** The Claude API exports the spans it creates on Anthropic's servers: the Sessions API requests, agent turns, model requests, and tool uses inside the sandbox. You configure this once for your organization, with no code changes.
* **From your application.** The Claude SDK emits a span for each request it sends and links it to the API's spans with W3C trace context, so a session appears inside your application's own trace.

## Prerequisites

* Python 3.10+, Node.js 18.19+, Go 1.26+, or Java 11+ and Gradle for the application-side example
* A running Phoenix instance with an OTLP/HTTP endpoint that Claude Platform can reach
* The **admin** role in a Claude Console organization, to add a destination. With the developer role, you can view destinations
* An `ANTHROPIC_API_KEY` from the [Claude Console](https://platform.claude.com/), in a workspace that sends spans to Phoenix

## Launch Phoenix

<Card>
  <Tabs>
    <Tab title="Local">
      ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      uvx arize-phoenix serve
      ```

      No [uv](https://docs.astral.sh/uv/)? `pip install arize-phoenix && phoenix serve` does the same thing. See [Terminal setup](/docs/phoenix/environments#terminal) for customization.
    </Tab>

    <Tab title="Container">
      ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      docker run -p 6006:6006 -p 4317:4317 arizephoenix/phoenix:latest
      ```

      Images are published to [Docker Hub](https://hub.docker.com/r/arizephoenix/phoenix). See [Docker](/docs/phoenix/self-hosting/deployment-options/docker) for volumes, PostgreSQL, and other options.
    </Tab>

    <Tab title="Self-Host">
      Run Phoenix on your own infrastructure, backed by PostgreSQL so traces persist beyond a single process. This is the option to reach for once Phoenix is shared across a team or environment.

      The [self-hosting guide](/docs/phoenix/self-hosting) covers [Kubernetes](/docs/phoenix/self-hosting/deployment-options/kubernetes), [Helm](/docs/phoenix/self-hosting/deployment-options/kubernetes-helm), [Railway](/docs/phoenix/self-hosting/deployment-options/railway), [AWS CloudFormation](/docs/phoenix/self-hosting/deployment-options/aws-with-cloudformation), [Google Cloud Run](/docs/phoenix/self-hosting/deployment-options/google-cloud-run), [Azure](/docs/phoenix/self-hosting/deployment-options/azure), and [Render](/docs/phoenix/self-hosting/deployment-options/render), plus authentication and configuration.
    </Tab>
  </Tabs>

  Phoenix serves its UI and OTLP HTTP on port **6006**, and OTLP gRPC on port **4317**. For a local instance that's [http://localhost:6006](http://localhost:6006). Leave it running while you work.
</Card>

Claude Platform sends spans from Anthropic's infrastructure, so a local Phoenix server is not reachable unless you expose it with a public HTTPS tunnel. Plain `localhost` works for your application's SDK spans only. For a deployed Phoenix instance or Phoenix Cloud, use its public base URL.

<Note>
  Pointing at a deployment with [authentication](/docs/phoenix/self-hosting/features/authentication) enabled? Set `PHOENIX_COLLECTOR_ENDPOINT` to that deployment's OTLP HTTP traces URL, for example `https://your-phoenix.example.com/v1/traces`, and `PHOENIX_API_KEY` to an API key from its **Settings** page. A local `phoenix serve` needs no key.
</Note>

## Export spans from the Claude Console

A *destination* tells the Claude API where to send the spans it creates. Use the **Arize Phoenix** destination to send Claude Platform spans to a Phoenix project:

<Frame caption="Claude Console destination configured to export Claude Platform spans to Phoenix">
  <img src="https://storage.googleapis.com/arize-phoenix-assets/assets/images/claude-managed-agents/phoenix-claude-managed-agents-add-destination-modal-workspaces.png" alt="Claude Console Add destination modal with Arize Phoenix selected, Phoenix URL, API key, project name, Full content mode, Workspaces setting, and Advanced settings size limit" />
</Frame>

1. In the Claude Console, open the [OpenTelemetry settings page](https://platform.claude.com/settings/telemetry-destinations) of your organization and click **Add destination**.
2. Set **Provider** to **Arize**.
3. Set **Product** to **Arize Phoenix**.
4. Set **Phoenix URL** to your Phoenix base URL, for example `https://phoenix.example.com`. Do not include `/v1/traces`; Claude Platform appends the per-signal path automatically.
5. Set **API key** to a Phoenix API key for Phoenix Cloud or an authenticated self-hosted instance.
6. Set **Project name** to the Phoenix project name, for example `claude-managed-agents-example`. The Console sends this as the destination's `x-project-name` header.
7. Set **Content mode** to **Full content**, so spans carry the system prompt, messages, and tool arguments as well as token counts. The default, **Metadata only**, sends only identifiers, timing, status, token counts, and the names and descriptions of your agents, tools, and MCP servers.
8. Set **Workspaces** to the workspaces whose spans you want in Phoenix. **All**, the default, exports from every workspace in your organization. **Selected** exports from only the workspaces you choose, and **All except** exports from every workspace except the ones you choose. Then click **Add destination**.
9. In the destination's **More actions** menu, click **Test**. The Console shows whether the test span was delivered. In Phoenix, check that an `anthropic.test` span arrived in the project.

<Frame caption="The destination's More actions menu in the Claude Console, with Test highlighted">
  <img src="https://storage.googleapis.com/arize-phoenix-assets/assets/images/claude-managed-agents/phoenix-claude-managed-agents-test-destination-menu.png" alt="Claude Console destination details panel for Phoenix showing endpoint, Full content mode, Healthy delivery, one workspace, and the More actions menu with Test highlighted" />
</Frame>

<Warning>
  A destination exports every request made with its workspaces' API keys, not just Managed Agents sessions. That includes Messages API requests from any other application or tool that uses those keys, with their prompts and responses in **Full content** mode. To send only your agent's traffic, give it a workspace and API keys of its own. **Workspaces** defaults to **All**, so choose **Selected** unless you want spans from every workspace in your organization in Phoenix.
</Warning>

Each destination sends to one Phoenix project. To keep workspaces apart, for example production and staging, add one destination per project and select each destination's workspaces. An organization can have up to 10 destinations, including disabled ones.

An admin can also add a destination with Anthropic's [Admin API or CLI](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#admin-api-and-cli). The Admin API has no Arize Phoenix provider, so enter the endpoint and headers yourself: the endpoint is your Phoenix base URL, such as `https://phoenix.example.com`, and the headers are `Authorization: Bearer <your-phoenix-api-key>` and `x-project-name: <your-project-name>`.

## Install

The rest of this page traces a Managed Agents session from your application. You register an OpenTelemetry tracer provider that exports to Phoenix, then run the session inside one of your own spans. The Claude SDK sends the trace context of that span with each request. The API's spans then nest under it, and the whole session appears as one trace.

<CodeGroup>
  ```bash Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  pip install arize-phoenix-otel anthropic
  ```

  ```bash TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  npm install @anthropic-ai/sdk @arizeai/openinference-core @arizeai/phoenix-otel tsx
  ```

  ```bash Go theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  go get github.com/anthropics/anthropic-sdk-go
  go get go.opentelemetry.io/otel
  go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
  go get go.opentelemetry.io/otel/sdk
  ```

  ```groovy Java theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  // build.gradle
  plugins {
      id 'application'
  }

  repositories {
      mavenCentral()
  }

  dependencies {
      implementation 'com.anthropic:anthropic-java:2.72.0'
      implementation 'io.opentelemetry:opentelemetry-sdk:1.66.0'
      implementation 'io.opentelemetry:opentelemetry-exporter-otlp:1.66.0'
  }

  application {
      mainClass = 'example.Main'
  }
  ```
</CodeGroup>

[Install or upgrade the Claude SDK](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk#install-the-sdk) to a version that emits spans. The SDK emits a span for each request and sends the trace context that joins the API's spans to yours. With an older SDK, the API's spans still reach Phoenix from the Console destination, in traces of their own.

<Warning>
  Don't also run the [OpenInference Anthropic instrumentor](/docs/phoenix/integrations/llm-providers/anthropic/anthropic-tracing) on the same client. Both would trace each request, and every request would appear twice.
</Warning>

## Configure credentials

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export PHOENIX_COLLECTOR_ENDPOINT="https://your-phoenix.example.com/v1/traces"
export PHOENIX_API_KEY="<your-phoenix-api-key>"
export PHOENIX_PROJECT_NAME="claude-managed-agents-example"
export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
export ANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE="content"
```

Use the same project name here as the destination's **Project name**, so the SDK's spans and the API's spans land in the same project. `ANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE=content` makes the SDK spans carry prompts and responses. Without it, they carry only metadata and token counts. The Java client reads it when you build it with `AnthropicOkHttpClient.fromEnv()`.

## Setup tracing

<CodeGroup>
  ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  # instrumentation.py
  import os

  from phoenix.otel import register

  tracer_provider = register(
      project_name=os.environ.get(
          "PHOENIX_PROJECT_NAME",
          "claude-managed-agents-example",
      ),
      endpoint=os.environ.get("PHOENIX_COLLECTOR_ENDPOINT"),
  )

  print("Phoenix tracing initialized for Claude Managed Agents.")
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  // instrumentation.ts
  import { register } from "@arizeai/phoenix-otel";

  export const provider = register({
    projectName:
      process.env.PHOENIX_PROJECT_NAME ?? "claude-managed-agents-example",
    url: process.env.PHOENIX_COLLECTOR_ENDPOINT,
    apiKey: process.env.PHOENIX_API_KEY,
  });

  console.log("Phoenix tracing initialized for Claude Managed Agents.");
  ```

  ```go Go theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  // main.go
  package main

  import (
      "context"
      "fmt"
      "log"
      "os"
      "strings"
      "time"

      "github.com/anthropics/anthropic-sdk-go"
      "go.opentelemetry.io/otel"
      "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
      "go.opentelemetry.io/otel/propagation"
      "go.opentelemetry.io/otel/sdk/resource"
      sdktrace "go.opentelemetry.io/otel/sdk/trace"
      semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
  )

  const prompt = "Use bash to compute the first 10 Fibonacci numbers. " +
      "Reply with only the numbers, comma-separated."

  func phoenixTraceEndpoint(endpoint string) string {
      endpoint = strings.TrimRight(endpoint, "/")
      if strings.HasSuffix(endpoint, "/v1/traces") {
          return endpoint
      }
      return endpoint + "/v1/traces"
  }

  func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
      project := os.Getenv("PHOENIX_PROJECT_NAME")
      if project == "" {
          project = "claude-managed-agents-example"
      }
      endpoint := os.Getenv("PHOENIX_COLLECTOR_ENDPOINT")
      if endpoint == "" {
          endpoint = "http://localhost:6006/v1/traces"
      }
      headers := map[string]string{"x-project-name": project}
      if apiKey := os.Getenv("PHOENIX_API_KEY"); apiKey != "" {
          headers["Authorization"] = "Bearer " + apiKey
      }
      exporter, err := otlptracehttp.New(
          ctx,
          otlptracehttp.WithEndpointURL(phoenixTraceEndpoint(endpoint)),
          otlptracehttp.WithHeaders(headers),
      )
      if err != nil {
          return nil, err
      }
      res, _ := resource.New(ctx, resource.WithAttributes(
          semconv.ServiceName(project),
      ))
      tp := sdktrace.NewTracerProvider(
          sdktrace.WithBatcher(exporter),
          sdktrace.WithResource(res),
      )
      otel.SetTracerProvider(tp)
      otel.SetTextMapPropagator(propagation.TraceContext{})
      return tp, nil
  }

  func shutdownTracer(tp *sdktrace.TracerProvider) {
      shutdownCtx, cancel := context.WithTimeout(
          context.Background(), 5*time.Second)
      defer cancel()
      _ = tp.Shutdown(shutdownCtx)
  }

  func main() {
      ctx := context.Background()

      tp, err := initTracer(ctx)
      if err != nil {
          log.Fatalf("register tracer: %v", err)
      }
      defer shutdownTracer(tp)

      fmt.Println("Phoenix tracing initialized for Claude Managed Agents.")

      // The client reads ANTHROPIC_API_KEY from the environment.
      client := anthropic.NewClient()

      environment, err := client.Beta.Environments.New(ctx,
          anthropic.BetaEnvironmentNewParams{
              Name: "phoenix-tracing-example",
              Config: anthropic.BetaEnvironmentNewParamsConfigUnion{
                  OfCloud: &anthropic.BetaCloudConfigParams{
                      Networking: anthropic.BetaCloudConfigParamsNetworkingUnion{
                          OfLimited: &anthropic.BetaLimitedNetworkParams{},
                      },
                  },
              },
          })
      if err != nil {
          log.Fatalf("create environment: %v", err)
      }

      agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
          Name: "Fibonacci Agent",
          Model: anthropic.BetaManagedAgentsModelConfigParams{
              ID: "claude-opus-5-5",
          },
          System: anthropic.String(
              "You are a concise assistant. Use bash to compute answers."),
          Tools: []anthropic.BetaAgentNewParamsToolUnion{{
              OfAgentToolset20260401: toolset(),
          }},
      })
      if err != nil {
          log.Fatalf("create agent: %v", err)
      }

      ctx, span := otel.Tracer("claude-managed-agents-example").
          Start(ctx, "fibonacci-agent")
      defer span.End()

      sessionParams := anthropic.BetaSessionNewParams{
          Agent: anthropic.BetaSessionNewParamsAgentUnion{
              OfBetaManagedAgentsAgents: &anthropic.BetaManagedAgentsAgentParams{
                  Type:    anthropic.BetaManagedAgentsAgentParamsTypeAgent,
                  ID:      agent.ID,
                  Version: anthropic.Int(agent.Version),
              },
          },
          EnvironmentID: environment.ID,
      }
      session, err := client.Beta.Sessions.New(ctx, sessionParams)
      if err != nil {
          log.Fatalf("create session: %v", err)
      }

      // Open the stream before sending the message so no events are missed.
      stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID,
          anthropic.BetaSessionEventStreamParams{})
      defer stream.Close()

      message := anthropic.BetaManagedAgentsUserMessageEventParams{
          Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
          Content: []anthropic.
              BetaManagedAgentsUserMessageEventParamsContentUnion{{
              OfText: &anthropic.BetaManagedAgentsTextBlockParam{
                  Type: anthropic.BetaManagedAgentsTextBlockTypeText,
                  Text: prompt,
              },
          }},
      }
      _, err = client.Beta.Sessions.Events.Send(ctx, session.ID,
          anthropic.BetaSessionEventSendParams{
              Events: []anthropic.BetaManagedAgentsEventParamsUnion{
                  {OfUserMessage: &message},
              },
          })
      if err != nil {
          log.Fatalf("send message: %v", err)
      }

  events:
      for stream.Next() {
          switch event := stream.Current().AsAny().(type) {
          case anthropic.BetaManagedAgentsAgentMessageEvent:
              for _, block := range event.Content {
                  fmt.Println(block.Text)
              }
          case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
              if event.StopReason.Type != "requires_action" {
                  break events
              }
          case anthropic.BetaManagedAgentsSessionStatusTerminatedEvent:
              break events
          }
      }
      if err := stream.Err(); err != nil {
          log.Fatalf("stream: %v", err)
      }
  }

  func toolset() *anthropic.BetaManagedAgentsAgentToolset20260401Params {
      allow := anthropic.BetaManagedAgentsAlwaysAllowPolicyParam{
          Type: anthropic.BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow,
      }
      ts := &anthropic.BetaManagedAgentsAgentToolset20260401Params{
          Configs: []anthropic.BetaManagedAgentsAgentToolConfigParamsUnion{
              {OfWebFetch: &anthropic.BetaManagedAgentsWebFetchToolConfigParams{
                  Enabled: anthropic.Bool(false),
              }},
              {OfWebSearch: &anthropic.BetaManagedAgentsWebSearchToolConfigParams{
                  Enabled: anthropic.Bool(false),
              }},
          },
      }
      ts.Type = anthropic.
          BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401
      ts.DefaultConfig.PermissionPolicy.OfAlwaysAllow = &allow
      return ts
  }
  ```

  ```java Java theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  // src/main/java/example/Main.java
  package example;

  import com.anthropic.client.AnthropicClient;
  import com.anthropic.client.okhttp.AnthropicOkHttpClient;
  import com.anthropic.models.beta.agents.*;
  import com.anthropic.models.beta.environments.BetaCloudConfigParams;
  import com.anthropic.models.beta.environments.BetaLimitedNetworkParams;
  import com.anthropic.models.beta.environments.EnvironmentCreateParams;
  import com.anthropic.models.beta.sessions.BetaManagedAgentsAgentParams;
  import com.anthropic.models.beta.sessions.SessionCreateParams;
  import com.anthropic.models.beta.sessions.events.*;
  import io.opentelemetry.api.common.AttributeKey;
  import io.opentelemetry.api.common.Attributes;
  import io.opentelemetry.api.trace.Span;
  import io.opentelemetry.api.trace.Tracer;
  import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
  import io.opentelemetry.context.Scope;
  import io.opentelemetry.context.propagation.ContextPropagators;
  import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
  import io.opentelemetry.sdk.OpenTelemetrySdk;
  import io.opentelemetry.sdk.resources.Resource;
  import io.opentelemetry.sdk.trace.SdkTracerProvider;
  import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
  import java.util.concurrent.TimeUnit;

  public class Main {

      private static final String PROMPT =
          "Use bash to compute the first 10 Fibonacci numbers. "
              + "Reply with only the numbers, comma-separated.";

      public static void main(String[] args) {
          String project = envOrDefault(
              "PHOENIX_PROJECT_NAME",
              "claude-managed-agents-example");

          Resource resource = Resource.getDefault().merge(Resource.create(
              Attributes.of(AttributeKey.stringKey("service.name"), project)));

          OtlpHttpSpanExporter.Builder exporter = OtlpHttpSpanExporter.builder()
              .setEndpoint(phoenixTraceEndpoint(envOrDefault(
                  "PHOENIX_COLLECTOR_ENDPOINT",
                  "http://localhost:6006/v1/traces")))
              .addHeader("x-project-name", project);
          String apiKey = System.getenv("PHOENIX_API_KEY");
          if (apiKey != null && !apiKey.isBlank()) {
              exporter.addHeader("Authorization", "Bearer " + apiKey);
          }

          SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
              .addSpanProcessor(BatchSpanProcessor.builder(
                  exporter.build()).build())
              .setResource(resource)
              .build();

          // The Claude SDK reads the global OpenTelemetry instance. It needs
          // the W3C propagator to link its spans to the API's spans.
          OpenTelemetrySdk otel = OpenTelemetrySdk.builder()
              .setTracerProvider(tracerProvider)
              .setPropagators(ContextPropagators.create(
                  W3CTraceContextPropagator.getInstance()))
              .buildAndRegisterGlobal();

          System.out.println(
              "Phoenix tracing initialized for Claude Managed Agents.");

          // fromEnv() reads ANTHROPIC_API_KEY and the
          // ANTHROPIC_OPEN_TELEMETRY_* variables.
          AnthropicClient client = AnthropicOkHttpClient.fromEnv();
          Tracer tracer = otel.getTracer("claude-managed-agents-example");

          var environment = client.beta().environments().create(
              EnvironmentCreateParams.builder()
                  .name("phoenix-tracing-example")
                  .config(BetaCloudConfigParams.builder()
                      .networking(BetaLimitedNetworkParams.builder().build())
                      .build())
                  .build());

          var agent = client.beta().agents().create(AgentCreateParams.builder()
              .name("Fibonacci Agent")
              .model(BetaManagedAgentsModel.of("claude-opus-5-5"))
              .system("You are a concise assistant. "
                  + "Use bash to compute answers.")
              .addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
                  .type(BetaManagedAgentsAgentToolset20260401Params.Type
                      .AGENT_TOOLSET_20260401)
                  .defaultConfig(
                      BetaManagedAgentsAgentToolsetDefaultConfigParams.builder()
                          .permissionPolicy(BetaManagedAgentsAlwaysAllowPolicy
                              .builder()
                              .type(BetaManagedAgentsAlwaysAllowPolicy.Type
                                  .ALWAYS_ALLOW)
                              .build())
                          .build())
                  .addConfig(BetaManagedAgentsWebFetchToolConfigParams.builder()
                      .enabled(false).build())
                  .addConfig(BetaManagedAgentsWebSearchToolConfigParams.builder()
                      .enabled(false).build())
                  .build())
              .build());

          Span span = tracer.spanBuilder("fibonacci-agent").startSpan();
          try (Scope scope = span.makeCurrent()) {
              var session = client.beta().sessions().create(
                  SessionCreateParams.builder()
                      .agent(BetaManagedAgentsAgentParams.builder()
                          .type(BetaManagedAgentsAgentParams.Type.AGENT)
                          .id(agent.id())
                          .version(agent.version())
                          .build())
                      .environmentId(environment.id())
                      .build());

              // Open the stream before sending the message so no events
              // are missed.
              try (var stream = client.beta().sessions().events()
                      .streamStreaming(session.id())) {
                  client.beta().sessions().events().send(session.id(),
                      EventSendParams.builder()
                          .addEvent(BetaManagedAgentsUserMessageEventParams
                              .builder()
                              .type(BetaManagedAgentsUserMessageEventParams
                                  .Type.USER_MESSAGE)
                              .addTextContent(PROMPT)
                              .build())
                          .build());

                  Iterable<BetaManagedAgentsStreamSessionEvents> events =
                      stream.stream()::iterator;
                  for (var event : events) {
                      if (event.isAgentMessage()) {
                          event.asAgentMessage().content()
                              .forEach(block -> block.text().ifPresent(
                                  text -> System.out.println(text.text())));
                      } else if (event.isSessionStatusIdle()) {
                          if (!event.asSessionStatusIdle().stopReason()
                                  .isRequiresAction()) {
                              break;
                          }
                      } else if (event.isSessionStatusTerminated()) {
                          break;
                      }
                  }
              }
          } finally {
              span.end();
          }

          tracerProvider.forceFlush().join(10, TimeUnit.SECONDS);
          tracerProvider.shutdown().join(10, TimeUnit.SECONDS);
      }

      private static String phoenixTraceEndpoint(String endpoint) {
          String trimmed = endpoint.replaceAll("/+$", "");
          if (trimmed.endsWith("/v1/traces")) {
              return trimmed;
          }
          return trimmed + "/v1/traces";
      }

      private static String envOrDefault(String name, String defaultValue) {
          String value = System.getenv(name);
          return value == null || value.isBlank() ? defaultValue : value;
      }
  }
  ```
</CodeGroup>

Go and Java keep the tracer setup and the example in one file, with the setup at the top of `main`. Neither registers the W3C trace context propagator by default, so both examples register it. Without it, the SDK can't link its spans to the API's spans. The Java example also registers the OpenTelemetry SDK globally, because the Claude SDK for Java uses the global instance.

## Run Claude Managed Agents

The example creates an environment and an agent, starts a session inside a `fibonacci-agent` span, and asks the agent to compute Fibonacci numbers with bash. It prints the agent's reply. Run the Python example with `python example.py` and the TypeScript example with `npx tsx example.ts`.

The `fibonacci-agent` span is a `CHAIN` span, with the request as its input and the agent's reply as its output. Python uses OpenInference's `@tracer.chain` decorator and TypeScript its `traceChain` wrapper, which set the span kind, record the input and output, and set the span's status for you. Go and Java set the `openinference.span.kind`, `input.value`, and `output.value` attributes themselves, and set the status to `OK` when the session finishes. Without a status, Phoenix shows a span's status as unset. Without a span kind, Phoenix shows a span's kind as unknown.

<CodeGroup>
  ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  # example.py

  # Importing instrumentation first registers the tracer provider
  # before the Claude SDK makes its first request.
  from instrumentation import tracer_provider

  import anthropic

  # The client reads ANTHROPIC_API_KEY from the environment.
  client = anthropic.Anthropic()
  tracer = tracer_provider.get_tracer("claude-managed-agents-example")

  REQUEST = (
      "Use bash to compute the first 10 Fibonacci numbers. "
      "Reply with only the numbers, comma-separated."
  )

  environment = client.beta.environments.create(
      name="phoenix-tracing-example",
      config={"type": "cloud", "networking": {"type": "limited"}},
  )

  agent = client.beta.agents.create(
      name="Fibonacci Agent",
      model="claude-opus-5-5",
      system="You are a concise assistant. Use bash to compute answers.",
      tools=[
          {
              "type": "agent_toolset_20260401",
              "default_config": {
                  "permission_policy": {"type": "always_allow"},
              },
              "configs": [
                  {"name": "web_fetch", "enabled": False},
                  {"name": "web_search", "enabled": False},
              ],
          },
      ],
  )

  @tracer.chain(name="fibonacci-agent")
  def run_agent(request: str) -> str:
      session = client.beta.sessions.create(
          agent={"type": "agent", "id": agent.id, "version": agent.version},
          environment_id=environment.id,
      )
      replies = []

      # Open the stream before sending the message so no events are missed.
      with client.beta.sessions.events.stream(session_id=session.id) as stream:
          client.beta.sessions.events.send(
              session_id=session.id,
              events=[
                  {
                      "type": "user.message",
                      "content": [
                          {
                              "type": "text",
                              "text": request,
                          },
                      ],
                  },
              ],
          )

          for event in stream:
              if event.type == "agent.message":
                  for block in event.content:
                      if block.type == "text":
                          replies.append(block.text)
              elif event.type == "session.status_idle":
                  if event.stop_reason.type != "requires_action":
                      break
              elif event.type == "session.status_terminated":
                  break

      return "\n".join(replies)


  print(run_agent(REQUEST))
  tracer_provider.shutdown()
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  // example.ts

  // Importing instrumentation first registers the tracer provider
  // before the Claude SDK makes its first request.
  import { provider } from "./instrumentation";
  import Anthropic from "@anthropic-ai/sdk";
  import { traceChain } from "@arizeai/openinference-core";

  // The client reads ANTHROPIC_API_KEY from the environment.
  const client = new Anthropic();

  const REQUEST =
    "Use bash to compute the first 10 Fibonacci numbers. " +
    "Reply with only the numbers, comma-separated.";

  async function main() {
    const environment = await client.beta.environments.create({
      name: "phoenix-tracing-example",
      config: { type: "cloud", networking: { type: "limited" } },
    });

    const agent = await client.beta.agents.create({
      name: "Fibonacci Agent",
      model: "claude-opus-5-5",
      system: "You are a concise assistant. Use bash to compute answers.",
      tools: [
        {
          type: "agent_toolset_20260401",
          default_config: { permission_policy: { type: "always_allow" } },
          configs: [
            { name: "web_fetch", enabled: false },
            { name: "web_search", enabled: false },
          ],
        },
      ],
    });

    const runAgent = traceChain(
      async (request: string): Promise<string> => {
        const session = await client.beta.sessions.create({
          agent: { type: "agent", id: agent.id, version: agent.version },
          environment_id: environment.id,
        });
        const replies: string[] = [];

        // Open the stream before sending the message so no events are missed.
        const stream = await client.beta.sessions.events.stream(session.id);
        await client.beta.sessions.events.send(session.id, {
          events: [
            { type: "user.message", content: [{ type: "text", text: request }] },
          ],
        });

        loop: for await (const event of stream) {
          switch (event.type) {
            case "agent.message":
              for (const block of event.content) {
                if (block.type === "text") replies.push(block.text);
              }
              break;
            case "session.status_idle":
              if (event.stop_reason.type !== "requires_action") break loop;
              break;
            case "session.status_terminated":
              break loop;
          }
        }

        return replies.join("\n");
      },
      { name: "fibonacci-agent" },
    );

    console.log(await runAgent(REQUEST));
    await provider.shutdown();
  }

  main().catch((err) => {
    console.error(err);
    process.exit(1);
  });
  ```

  ```bash Go theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  # The full example is in main.go above.
  go run main.go
  ```

  ```bash Java theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  # The full example is in Main.java above.
  gradle run
  ```
</CodeGroup>

<Note>
  Agents and environments are persistent and versioned. The example creates them on every run to stay self-contained. In your application, create them once, store their IDs, and pass the IDs to each `sessions.create` call.
</Note>

### Expected output

```text wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
Phoenix tracing initialized for Claude Managed Agents.
0, 1, 1, 2, 3, 5, 8, 13, 21, 34
```

## Verify in Phoenix

1. Open Phoenix and select project **`claude-managed-agents-example`**.
2. You should see a new trace within about a minute. The API's spans can arrive after yours. The trace has a `fibonacci-agent` root span, the SDK's client spans for each Sessions API request, and the API's spans under them: an `anthropic.session.turn` span with `anthropic.model_request` and tool spans inside it.

<Frame caption="A Claude Managed Agents session in Phoenix, with the trace tree expanded">
  <img src="https://storage.googleapis.com/arize-phoenix-assets/assets/images/claude-managed-agents/phoenix-claude-managed-agents-fibonacci-trace.png" alt="Phoenix trace view of a fibonacci-agent trace with status OK. The trace tree shows the fibonacci-agent root span, Sessions API request spans, an anthropic.session.turn span, anthropic.model_request spans, and an anthropic.tool_use bash span." />
</Frame>

1. If no traces appear, see [Troubleshooting](#troubleshooting).

The Console's **Delivery** column for your destination also shows **Healthy** once the API delivers its first spans to Phoenix.

## What Phoenix captures

The SDK and the API mark their spans with `gen_ai.*` attributes, and Phoenix converts them to OpenInference attributes on ingestion. See [Translating semantic conventions](/docs/phoenix/tracing/concepts-tracing/translating-conventions).

| Span | From | What it shows in Phoenix |
| - | - | - |
| `anthropic.sessions.*`, `anthropic.agents.*`, `anthropic.environments.*` | SDK (`CLIENT`) | Each request the SDK sends, with its IDs, status, and timing. Requests that you make outside your own spans, such as creating the agent before the session, are in traces of their own |
| `anthropic.sessions.*` | API (`SERVER`) | Each Sessions API request, as the API handled it, nested under the SDK's span with the same name. Turns that the request starts are its children |
| `anthropic.session.turn` | API | An `AGENT` span for one agent turn: the system prompt, the turn's input and output messages, its stop reason, and its child spans |
| `anthropic.model_request` | API | An `LLM` span for one model request, with the model and token usage. The messages are on the turn span |
| `anthropic.tool_use <tool>`, `anthropic.mcp_tool_use <tool>` | API, or SDK | A `TOOL` span for a built-in or MCP tool, such as `anthropic.tool_use bash`, with its arguments as the input. The API emits it for tools in its sandbox, and the SDK for tools in a self-hosted sandbox and for MCP tools that a tool runner runs |
| `anthropic.custom_tool_use <tool>` | SDK | A `TOOL` span for a custom tool that your application ran, when you use the SDK's session tool runner, with the tool's name, description, and arguments as the input |
| `anthropic.session.outcome_evaluation` | API | A grader evaluation of an [outcome](https://platform.claude.com/docs/en/managed-agents/outcomes) |
| `anthropic.environment.work` | SDK | One work item that a [self-hosted sandbox](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk#trace-a-self-hosted-sandbox)'s worker ran, with the worker's tool spans and requests inside it |

No span shows a custom tool that your application runs without a tool runner, a multiagent or advisor tool, or a tool use that the permission policy or your application denied. Add your own `TOOL` span for a custom tool, as in the [cookbook](/docs/phoenix/cookbook/tracing/claude-managed-agents-guide).

Phoenix groups a session's spans into a [session](/docs/phoenix/tracing/how-to-tracing/setup-tracing/setup-sessions) by `gen_ai.conversation.id`, which holds the session thread ID, so each Managed Agents thread is one session in the **Sessions** tab. Select it to read the thread as a conversation. If you set the SDK's `conversation_id` option, the SDK's spans carry your ID in `gen_ai.conversation.id`, so they form a session of their own, apart from the API's spans. To find every span of a multiagent session across all its threads, filter on `anthropic.session.id`. A turn that starts because an agent on another thread replied or finished has no parent, so it's the root of a trace of its own.

### Known limitations

* **Some Sessions API spans can show an unknown kind.** The `anthropic.agents.create`, `anthropic.environments.create`, `anthropic.sessions.events.send`, and `anthropic.sessions.events.stream` spans don't say what kind of operation they are, so Phoenix shows their kind as unknown. Your own spans don't have this problem as long as they set a span kind.
* **Cache token counts aren't shown.** The spans report cache reads and writes as `gen_ai.usage.cache_read.input_tokens` and `gen_ai.usage.cache_write.input_tokens`, which Phoenix doesn't map yet. Phoenix counts all of a request's input tokens as regular prompt tokens, so cost estimates for requests that read from the prompt cache can be higher than what you were billed. The raw attributes stay on the span.
* **Session requests appear as LLM spans.** The `anthropic.sessions.create` spans carry the agent's model, so Phoenix classifies them as `LLM` spans, with zero tokens. They aren't model calls. The model calls are the `anthropic.model_request` spans.
* **Messages API requests count tokens more than once.** For a Managed Agents session, only the `anthropic.model_request` and `anthropic.session.outcome_evaluation` spans carry token usage, so trace totals are correct. For a Messages API request, the usage is on the SDK's `anthropic.messages.create` span, the API's span of the same name, and the API's `anthropic.model_request` children, so trace totals are higher than the request used. See Anthropic's guidance on [counting tokens](https://platform.claude.com/docs/en/manage-claude/opentelemetry#count-tokens).
* **The Sessions list doesn't summarize these sessions.** The session ID is on the SDK's and the API's spans, not on the root span of each trace, so the Sessions list shows no first message, last message, duration, or tokens, and a session shows zero traces. Select the session to read its conversation.
* **Tool spans don't show an output.** Tool results are in the `anthropic.tool_result.content` attribute, which Phoenix doesn't map to the span output yet. Read it from the span's attributes. On the SDK's `anthropic.custom_tool_use` spans, `tool.parameters` also holds the call's arguments rather than the tool's JSON schema.
* **Images and documents aren't included.** Spans record only the type of an image or document in a prompt or response, not its data.
* **Long content is shortened.** Each span holds a limited amount of content, by default 0.75 MB. When content is over the limit, the SDK or the API shortens it and sets `anthropic.content.truncated` to `true`. Change the destination's limit under **Advanced settings**, and the SDK's with `ANTHROPIC_OPEN_TELEMETRY_TRACES_MAX_CONTENT_BYTES`. See Anthropic's [Content limits](https://platform.claude.com/docs/en/manage-claude/opentelemetry#content-limits).

## Troubleshooting

* **No traces at all.** Check that `PHOENIX_COLLECTOR_ENDPOINT`, `PHOENIX_API_KEY`, and `PHOENIX_PROJECT_NAME` are set in the shell that runs the example. Register the tracer provider before the first Claude SDK call, and check that `ANTHROPIC_OPEN_TELEMETRY` isn't `false`. Set `OTEL_LOG_LEVEL=debug` to see export errors.
* **The destination shows Failing.** Hold the pointer over **Failing** to see the error. A `401` or `403` usually means the API key is missing or wrong. A `404` usually means the destination was pointed at `/v1/traces`; use the Phoenix base URL because Claude Platform appends the OTLP signal path itself. A rejection that mentions project routing means the destination has no project name. If you change the destination's Phoenix URL or authentication, enter the **API key** again.
* **The destination shows Disabled.** Hold the pointer over **Disabled** to see why. The API disables a destination after 24 hours of failed deliveries, or when the endpoint redirects or resolves to an IP address that isn't public. Fix the cause, then click **Enable** in the destination's **More actions** menu, with the **Workspace** filter set to **All**.
* **Only your own spans appear, with no API spans.** Check that the destination's **Workspaces** include the workspace of your `ANTHROPIC_API_KEY`, and that the destination's **Project name** matches `PHOENIX_PROJECT_NAME`. The API's spans can take about a minute to arrive.
* **The API's spans are in a separate trace.** The SDK didn't send the trace context. Check that you [installed or upgraded the Claude SDK](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk#install-the-sdk) to a version that emits spans. In TypeScript, import your `instrumentation.ts` before creating the Claude client, and don't pass `global: false` to `register()`. If you also use `@opentelemetry/instrumentation-undici`, which `@opentelemetry/auto-instrumentations-node` includes, create the client with `openTelemetry: { propagation: false }`: that package adds a second `traceparent` header, which makes the trace context invalid. In Go and Java, register the W3C trace context propagator as the examples do. If your application uses another propagator, such as B3, add the W3C trace context propagator too. In Java, also build the OpenTelemetry SDK with `buildAndRegisterGlobal()`, and check that no other dependency pins `opentelemetry-api` below 1.57.0. Requests to Claude Platform on AWS always start traces of their own.
* **Java spans are missing when the program exits.** The `BatchSpanProcessor` exports in the background. Keep the `forceFlush` and `shutdown` calls at the end of `main`, or the JVM can exit before it delivers them.
* **Each request appears twice.** Each request has an SDK span and, inside it, an API span with the same name, as listed in [What Phoenix captures](#what-phoenix-captures). That's expected. If a request has three spans of that name, another library also traces the Claude SDK, for example the OpenInference Anthropic instrumentor. Remove one of the two, or turn the SDK's spans off with `ANTHROPIC_OPEN_TELEMETRY=false`.
* **Spans have no prompts or responses.** Set the destination's content mode to **Full content** and `ANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE=content` for the SDK. If your organization has a HIPAA, FERPA, or K-12 data-handling restriction with Anthropic, destinations export only metadata, whatever their content mode. The SDK's spans still carry content.

## Resources

<CardGroup>
  <Card horizontal icon="book-open" href="https://platform.claude.com/docs/en/manage-claude/opentelemetry" title="Export OpenTelemetry traces from the Claude API" />

  <Card horizontal icon="book-open" href="https://platform.claude.com/docs/en/managed-agents/overview" title="Claude Managed Agents documentation" />

  <Card horizontal icon="graduation-cap" href="/docs/phoenix/cookbook/tracing/claude-managed-agents-guide" title="Trace and Evaluate Claude Managed Agents" />

  <Card horizontal icon="arrows-rotate" href="/docs/phoenix/tracing/concepts-tracing/translating-conventions" title="Translating semantic conventions" />
</CardGroup>
