Trace Strands agents deployed on AWS Bedrock AgentCore Runtime with OpenInference and send spans to Arize AX.
AWS Bedrock AgentCore is a managed runtime for deploying agentic applications — your agent code runs as a containerized service AWS provisions and operates. AgentCore is framework-agnostic and works with Strands Agents, CrewAI, LangGraph, LlamaIndex, Google ADK, and the OpenAI Agents SDK. Arize AX captures every invocation by configuring the AgentCore-deployed agent to ship OTLP traces to Arize AX through the same openinference-instrumentation-strands-agents processor used by the Strands Agents SDK guide.
This is a deployment workflow, not a local-script example. The agent file is shipped to AWS via the AgentCore starter toolkit, which provisions an ECR repository, builds and pushes the agent container, creates an IAM execution role, and launches the runtime. Local invocations target the deployed runtime over the network.
Bedrock AgentCore Runtime with Strands and Arize AX Notebook
Bedrock AgentCore available in your AWS region. AgentCore is currently available in a subset of regions — check the Bedrock AgentCore docs for the current list.
IAM permissions to create ECR repositories, execution roles, and AgentCore runtimes (the starter toolkit’s auto_create_* options handle the provisioning, but your principal must allow it).
Docker running locally — the starter toolkit builds the agent container image on your machine before pushing to ECR.
Create the agent’s runtime file — this is what AgentCore packages into the deployable container. The OTel + OpenInference setup looks the same as the Strands Agents SDK guide, with one runtime-specific quirk: AgentCore registers its own tracer provider by default, so you have to disable AgentCore’s built-in OTel (disable_otel=True at configure time and DISABLE_ADOT_OBSERVABILITY=true in the launch env) and explicitly call trace.set_tracer_provider(...) to install the Arize-bound provider.
# strands_claude.py — the file AgentCore deploys as your agentimport osfrom opentelemetry import tracefrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessorfrom opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( OTLPSpanExporter,)from bedrock_agentcore.runtime import BedrockAgentCoreAppfrom strands import Agentfrom strands.models.bedrock import BedrockModelfrom openinference.instrumentation.strands_agents import ( StrandsAgentsToOpenInferenceProcessor,)# Build a TracerProvider with the Arize project name baked into the# Resource and register it globally so Strands' internal# `trace.get_tracer(...)` finds it.resource = Resource.create({ "openinference.project.name": os.environ["ARIZE_PROJECT_NAME"], "service.name": "bedrock-agentcore-strands-agent",})provider = TracerProvider(resource=resource)provider.add_span_processor(StrandsAgentsToOpenInferenceProcessor())# The OTLP gRPC exporter reads endpoint + headers from# OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS — both# set at launch() time below.provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))trace.set_tracer_provider(provider)# The deployed app — AgentCore calls strands_agent_bedrock for every# /invocations POST.app = BedrockAgentCoreApp()@app.entrypointdef strands_agent_bedrock(payload, context=None): agent = Agent( name="OceanAssistant", model=BedrockModel( model_id="us.anthropic.claude-sonnet-4-6", region_name=os.environ.get("AWS_DEFAULT_REGION", "us-west-2"), ), system_prompt="Answer concisely in two sentences.", ) response = agent(payload.get("prompt", "")) return response.message["content"][0]["text"]if __name__ == "__main__": app.run()
The ocean is salty because rivers continuously dissolve mineral salts from rocks and soil and carry them to the sea, where they accumulate over millions of years. Water leaves the ocean through evaporation but the salts remain, steadily concentrating until reaching today's roughly 3.5% salinity.
Open your Arize AX space and select project bedrock-agentcore-tracing-example.
You should see a new trace within ~30–60 seconds (AgentCore cold-start adds latency on first invocation) with the same shape as the Strands tracing guide: an invoke_agent <agentName> root span (AGENT) wrapping an execute_event_loop_cycle (CHAIN) and a chat LLM span (model us.anthropic.claude-sonnet-4-6).
Overriding of current TracerProvider is not allowed. AgentCore registers its own TracerProvider on startup, and OTel disallows overriding a registered provider. Pass disable_otel=True to agentcore_runtime.configure(...) and set DISABLE_ADOT_OBSERVABILITY=true in env_vars at launch(...). With both set, your trace.set_tracer_provider(provider) in strands_claude.py succeeds and your Arize-bound provider becomes the global tracer.
Failed to export traces to https://otlp.arize.com:443 (StatusCode.PERMISSION_DENIED). The OTLP env vars aren’t reaching the deployed container, or the headers aren’t formatted correctly. AgentCore’s container-side environment expects OTEL_EXPORTER_OTLP_HEADERS as a comma-separated key=value string (no quoting, no JSON). Confirm the env vars in the launch(...) call match the format in the Run section above.
AccessDeniedException from Bedrock at agent invocation time. AgentCore’s auto-created execution role gets a default trust policy + minimal permissions; you may need to attach bedrock:InvokeModel and bedrock:InvokeModelWithResponseStream to it manually. The role ARN is in the agentcore_runtime.configure(...) return value.
Cold-start spans missing. The first invoke after launch can take 60–120s while the container provisions. Spans for that invocation will land once the agent’s BatchSpanProcessor flushes, which doesn’t happen until the next invocation. To force a flush on a long-lived runtime, call provider.force_flush() at the end of your @app.entrypoint function.
Region mismatch.BedrockModel(region_name=...) and the AgentCore Runtime region must agree, or the model call fails with ValidationException. Set both from AWS_DEFAULT_REGION or pass the same string to both.