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

# Amazon Bedrock AgentCore observability with OpenTelemetry

> Route the OpenTelemetry traces and logs an agent emits on Amazon Bedrock AgentCore Runtime — nested agent-loop spans, token usage, tool calls, and prompt/response content — to Bronto.

This page covers capturing the telemetry an agent produces while running on the [Amazon Bedrock AgentCore](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html) Runtime and sending it to Bronto. Where [Amazon Bedrock LLM observability](/ai-features/aws-bedrock) instruments a single `Converse` call, an agent on AgentCore runs a multi-step reasoning loop with tool calls — producing deep, nested traces following the [OpenTelemetry GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md).

<Note>
  AgentCore, Strands, and the GenAI conventions evolve quickly. Commands and instrumentation behavior here were verified on **July 17, 2026** with `bedrock-agentcore-starter-toolkit`; AWS also offers a newer `agentcore-cli` with a different command set.
</Note>

For the broader ingestion picture (direct OTLP vs. a Collector, and where AgentCore fits among AWS services), see [Amazon Bedrock AgentCore Logs, Metrics and Traces](/integrations/aws-agentcore). For the attributes Bronto surfaces and how to search them, see [LLM Observability](/ai-features/llm-observability).

<Tip>
  This page uses Python with the [Strands](https://strandsagents.com/) agent framework, which emits GenAI spans through the tracer provider you configure. The same approach applies to any agent runtime: you disable AgentCore's managed telemetry pipeline and point the OpenTelemetry SDK at Bronto.
</Tip>

## How AgentCore observability works

AgentCore Runtime ships with a **managed OpenTelemetry pipeline** (the AWS Distro for OpenTelemetry) that, by default, delivers agent telemetry to **Amazon CloudWatch** GenAI Observability. To send the traces and logs to Bronto instead, you **disable the managed pipeline** and configure the OpenTelemetry SDK inside your agent to export OTLP to Bronto through an OTel Collector (or, where you don't run one, directly).

## Prerequisites

* Python 3.10 or later, and `pip`
* An agent deployed to AgentCore Runtime (this guide uses the [Strands](https://strandsagents.com/) framework and the `bedrock-agentcore` SDK)
* The AgentCore CLI: `pip install bedrock-agentcore-starter-toolkit`
* A running OTel Collector configured to forward to Bronto — see [Connect OpenTelemetry to Bronto](/agent-setup/open-telemetry) or, on AWS, [ADOT](/integrations/aws-adot). AgentCore's managed public network mode must reach it over a public endpoint; a private VPC-only address is not reachable. Export [directly to Bronto](#direct-export-vs-a-collector) when you do not operate a public Collector endpoint.
* A Bronto ingestion API key — held by the Collector, or sent as the `x-bronto-api-key` header when exporting directly. See [API Keys](/Account-Management/API-Keys) for how to create one with ingestion permissions.

## Disable the managed pipeline

Two switches take AWS's managed observability out of the way so your own exporters own the signal:

* At configure time, pass `--disable-otel`.
* At runtime, set `DISABLE_ADOT_OBSERVABILITY=true` — passed to the deployed agent with `--env` (see [Configure OTLP export](#configure-otlp-export) below).

```bash theme={"dark"}
agentcore configure -e agent.py -n my_agent -r eu-west-1 \
  -rf requirements.txt --disable-otel --non-interactive
```

## Install dependencies

```bash theme={"dark"}
pip install \
  opentelemetry-sdk \
  opentelemetry-exporter-otlp-proto-http \
  opentelemetry-instrumentation-botocore \
  strands-agents strands-agents-tools \
  bedrock-agentcore
```

## Configure OTLP export

Point the agent's OTLP exporters at your Collector, which holds the Bronto credential and forwards each signal to Bronto's per-signal endpoints.

The agent runs in a managed container, so plain shell `export` only reaches it during local development — for the deployed runtime, pass the same variables with repeated `--env` flags on `agentcore deploy`:

<CodeGroup>
  ```bash Deployed (agentcore deploy) theme={"dark"}
  # AgentCore public network mode needs an internet-reachable HTTPS endpoint,
  # such as ADOT on ECS behind a public ALB. Private VPC DNS won't work.
  agentcore deploy \
    --env DISABLE_ADOT_OBSERVABILITY=true \
    --env OTEL_EXPORTER_OTLP_ENDPOINT=https://<collector-host> \
    --env OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
    --env OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental,gen_ai_tool_definitions
  ```

  ```bash Local development theme={"dark"}
  # localhost works here because the agent and the Collector
  # share your machine — a deployed agent cannot reach it.
  export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
  export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
  export OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental,gen_ai_tool_definitions
  ```
</CodeGroup>

<Warning>
  `agentcore deploy --env` values are not automatically reused by a later deployment. Put every required telemetry flag in your deployment script and pass them on every redeploy, or the runtime can silently return to AWS's managed telemetry pipeline.
</Warning>

Keeping the exporters pointed at a Collector keeps the agent code vendor-neutral and the Bronto API key out of the runtime config. If no Collector is reachable from the runtime, export straight to Bronto instead — see [Direct export vs. a Collector](#direct-export-vs-a-collector) below.

Resource attributes (`service.name` → Bronto dataset, `service.namespace` → collection) are set in code on a shared `Resource`, exactly as in the [standard Python setup](/opentelemetry/python#set-resource-attributes) — `StrandsTelemetry` accepts a pre-configured tracer provider carrying that `Resource`, so all three signals share one identity with no environment variables to keep in sync.

Bootstrap the three signal providers at startup — **before** the agent framework or any instrumented library is imported:

```python telemetry.py theme={"dark"}
import logging, os

# Use the current Strands GenAI shape and include tool definitions.
os.environ.setdefault(
    "OTEL_SEMCONV_STABILITY_OPT_IN",
    "gen_ai_latest_experimental,gen_ai_tool_definitions",
)

from opentelemetry import trace
from opentelemetry._logs import set_logger_provider
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.instrumentation.botocore import BotocoreInstrumentor
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider


def setup_telemetry() -> None:
    # One Resource shared by traces and logs — same pattern as the
    # standard Python setup (/opentelemetry/python#set-resource-attributes).
    resource = Resource.create({
        "service.name": "my-agent",         # → Bronto dataset
        "service.namespace": "agents",      # → Bronto collection
    })

    # Traces: hand Strands a tracer provider built on the shared resource.
    # Strands creates its spans from the *global* provider, and only registers
    # one itself when you don't pass your own — so set it globally here.
    tracer_provider = TracerProvider(resource=resource)
    trace.set_tracer_provider(tracer_provider)

    from strands.telemetry import StrandsTelemetry
    StrandsTelemetry(tracer_provider=tracer_provider).setup_otlp_exporter()

    # Logs: bridge Python logging so structured `extra=` fields arrive as
    # first-class, queryable attributes in Bronto.
    logger_provider = LoggerProvider(resource=resource)
    logger_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))
    set_logger_provider(logger_provider)
    logging.getLogger().addHandler(LoggingHandler(logger_provider=logger_provider))
    logging.getLogger().setLevel(logging.INFO)

    # Trace the boto3 calls to AgentCore primitives (Memory, Code Interpreter,
    # Gateway) as child spans of the agent invocation.
    BotocoreInstrumentor().instrument()
```

Call `setup_telemetry()` at the very top of your entrypoint, before importing Strands:

```python agent.py theme={"dark"}
from telemetry import setup_telemetry
setup_telemetry()

from bedrock_agentcore.runtime import BedrockAgentCoreApp
from strands import Agent
# ...
```

## What you get on the trace

A single agent invocation becomes one trace with nested spans:

| Span                      | Source                        | Notes                                                                            |
| ------------------------- | ----------------------------- | -------------------------------------------------------------------------------- |
| Agent invocation / loop   | Strands                       | Root span for the reasoning loop                                                 |
| `chat`                    | Strands GenAI instrumentation | One per model call, carrying `gen_ai.*` attributes; the model id is an attribute |
| Tool calls                | Strands                       | One child span per tool the agent invokes                                        |
| AgentCore primitive calls | `BotocoreInstrumentor`        | Memory, Code Interpreter, and Gateway (MCP) calls as child spans                 |

Useful GenAI attributes on the model spans:

| Attribute                                                 | Example                   | Notes                                       |
| --------------------------------------------------------- | ------------------------- | ------------------------------------------- |
| `gen_ai.request.model`                                    | `eu.amazon.nova-pro-v1:0` | Requested model / inference profile         |
| `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens` | `412`, `173`              | Token usage (numeric — chart and sum these) |
| `gen_ai.operation.name`                                   | `chat`                    | Operation type                              |
| `gen_ai.response.finish_reasons.0`                        | `end_turn`                | Why generation stopped                      |

These are first-class, queryable fields in Bronto. See [LLM Observability](/ai-features/llm-observability) for the full recommended attribute set.

<Note>
  Strands tool-call spans use standard attributes including `gen_ai.tool.name` and `gen_ai.tool.call.id`; exact coverage varies by Strands version. AgentCore-primitive spans from `BotocoreInstrumentor` (Memory, Code Interpreter, Gateway) instead carry AWS SDK attributes such as `rpc.method` and `aws.request_id`. Inspect a real trace before building version-sensitive dashboards.
</Note>

## Capturing prompt, response, and tool IO

Strands captures message content by default and does not use `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`. To put content directly on span attributes for backends that cannot read OTel events, add `gen_ai_span_attributes_only` to `OTEL_SEMCONV_STABILITY_OPT_IN`. Treat this data as sensitive. You can also log each agent result as structured GenAI attributes:

```python theme={"dark"}
import logging

log = logging.getLogger(__name__)

# after the agent produces its answer:
log.info(
    "agent.result",
    extra={
        "event.name": "agent.result",
        "gen_ai.request.model": model_id,
        "gen_ai.output.messages": response_text,   # full response as a queryable field
        "session.id": session_id,
    },
)
```

Avoid printf-style logging (`log.info("tokens=%s", n)`) for data you want to query — that produces a flat message string rather than structured fields. See [LLM Observability](/ai-features/llm-observability) for the recommended attribute names.

## Verify in Bronto

Trigger an invocation of the deployed agent:

```bash theme={"dark"}
agentcore invoke '{"prompt": "What is the weather in Dublin?"}'
```

**Traces** — open [Explore Traces](/tracing/explore-traces) and filter by your `service.name`. Open the newest trace and confirm the nesting from the table above: the agent loop at the root, a `chat` span per model call, and tool-call / AgentCore-primitive spans beneath it.

**Logs** — open [Log Search](https://app.bronto.io/search) and filter by the same `service.name`.

<Note>
  Bronto indexes log-record attributes with a `$` prefix. Query the structured fields by name — for example `"$event.name"` or `"$gen_ai.request.model"`.
</Note>

Because the searchable body of a structured event is the event name, search agent content with **field predicates** rather than full-text:

```
"$event.name" = 'agent.result'
```

```
"$gen_ai.output.messages" ILIKE '%degraded%'
```

Token usage is numeric, so you can aggregate it — for example average output tokens per model, or total tokens over time — in aggregate views and [Visualizations](/Search-and-Visualize/Log-Visualization).

## Direct export vs. a Collector

* **Via an OTel Collector** (recommended) — the agent exports OTLP to a Collector that holds the Bronto credential and forwards each signal. This keeps the agent code vendor-neutral, gives you a place to add processors / sampling / fan-out, and matches how Bronto ingests other AWS telemetry. See [Amazon Bedrock AgentCore Logs, Metrics and Traces](/integrations/aws-agentcore) and [ADOT](/integrations/aws-adot).
* **Direct OTLP to Bronto** — when no Collector is reachable from the runtime, or for short-lived jobs: set `OTEL_EXPORTER_OTLP_ENDPOINT` to the Bronto ingestion base (EU `https://ingestion.eu.bronto.io`, US `https://ingestion.us.bronto.io`) and add your [API key](/Account-Management/API-Keys) as the `x-bronto-api-key` header. No Collector, VPC, or sidecar required.

  ```bash theme={"dark"}
  agentcore deploy \
    --env DISABLE_ADOT_OBSERVABILITY=true \
    --env OTEL_EXPORTER_OTLP_ENDPOINT=https://ingestion.eu.bronto.io \
    --env OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
    --env OTEL_EXPORTER_OTLP_HEADERS="x-bronto-api-key=<your-ingestion-key>"
  ```

## References

* [Amazon Bedrock AgentCore Logs, Metrics and Traces](/integrations/aws-agentcore)
* [Amazon Bedrock LLM observability](/ai-features/aws-bedrock)
* [LLM Observability](/ai-features/llm-observability)
* [Amazon Bedrock AgentCore documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html)
* [OpenTelemetry GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md)
