DEV Community

Cover image for Debugging Agents After the Fact: Put Context on the Trace
Prasad Rane
Prasad Rane

Posted on AI-assisted

Debugging Agents After the Fact: Put Context on the Trace

Most agent debugging I hear about starts after something went sideways. Something didn't behave the way you expected, and now you're piecing together why.

Agents are software. Loopy, nondeterministic software, but still running in prod and making decisions between human checkpoints. So when a run goes strange, you need the same thing you'd want for any distributed system: a record of what actually happened, step by step.

On October 14, 2026, Anthropic announced OpenTelemetry (OTel) traces for the Claude Platform, in public beta. This post covers what shipped, what the setup involves, why a trace fits agents well, and which context I'd attach to each trace so it answers questions later.

What shipped

From Anthropic's article, Agent observability on the Claude Platform, and the OpenTelemetry docs:

  • Claude Managed Agents, the Messages API and the Claude SDKs can emit OpenTelemetry spans for requests, model calls, agent turns and tool use, including MCP tool calls.
  • Spans use attributes from the OpenTelemetry GenAI semantic conventions, so platforms that support those conventions can recognize them without extra setup.
  • Traces go to any platform that accepts OTLP. Anthropic's partner list includes Datadog, Grafana Cloud, Honeycomb, Sentry, Splunk, New Relic, Langfuse, LangSmith, Braintrust, Arize AI and others.
  • It's a beta. The docs warn that span names, attributes and SDK options can change, and recommend pinning the SDK version.

Two sources of spans, two kinds of setup

There are two places spans come from, and they're set up differently.

API tracing is configured in the Claude Console. An admin adds a destination, and the API sends spans for Messages API requests, each model run inside a request, Managed Agents turns and server tools that the API runs.

SDK tracing happens in your application. The SDK emits spans for the requests it sends and the client tools the tool runner executes, but it doesn't export anything by itself. Your app registers an OpenTelemetry tracer provider and an exporter, and that exporter ships the spans to your platform. If your service already has OpenTelemetry set up, the SDK uses the tracer provider you registered.

Set up both and a request's SDK span and API span land in the same trace. Check the docs for the exact Console steps and permissions.

Two-way-span

Figure 1: API tracing is configured in the Console. SDK tracing runs through your app's own tracer provider and exporter.

Why a trace fits an agent

A single agent run isn't one request. It's a model call, then a tool call, then another model call that depends on what the tool returned, repeated until the agent decides it's done. One user message can fan out into a dozen steps.

Plain log lines without trace or span IDs leave you rebuilding that order in your head: which tool call followed which model call, which step took the time, where the run took a turn you didn't expect. Logs can carry trace context and structured fields too, and correlated logs help. A trace just gives you the timeline directly.

In a trace, each step is a span with its own timing and attributes. In a simplified application trace, model requests and tool use can appear as ordered siblings under an enclosing run span. The exact hierarchy depends on instrumentation. Anthropic's own example trace, for instance, has tool runner and request spans in between. Parent and child links aren't proof that one step caused another, so read the order and the attributes, not only the shape.

Figure 2: Uncorrelated log lines vs the same run as a trace

Figure 2: Same run, two views. A simplified trace groups the steps under one run span, in order.

Put your own context on the trace

Platform spans tell you what the model and tools did. They can't tell you which prompt version your app was running, which feature flag was on, or which customer hit the problem. That context lives in your code, so you add it.

The pattern is a span around your own agent turn. When you call the SDK inside it, the SDK's spans nest under yours. Here's an illustrative Python sketch, including the tracer provider and OTLP exporter the SDK needs. The attribute names are mine, so pick names that fit your team's conventions.

pip install anthropic opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
Enter fullscreen mode Exit fullscreen mode
import hashlib
import hmac
import os

import anthropic
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Register a tracer provider before the first SDK call. The OTLP exporter reads
# OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS from the environment.
provider = TracerProvider(resource=Resource.create({"service.name": "support-agent"}))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("support-agent")
client = anthropic.Anthropic()

PROMPT_VERSION = "triage-v7"  # whatever identifies your deployed prompt
REF_KEY = os.environ["CUSTOMER_REF_KEY"].encode()


def customer_ref(customer_id: str) -> str:
    # A keyed HMAC lets you find one customer's runs without exporting the raw ID.
    # It's a pseudonymous reference, not anonymization: anyone with the key can match it.
    return hmac.new(REF_KEY, customer_id.encode(), hashlib.sha256).hexdigest()[:16]


def run_turn(customer_id: str, user_message: str, flags: dict[str, bool]):
    with tracer.start_as_current_span("agent.turn") as span:
        span.set_attribute("app.prompt_version", PROMPT_VERSION)
        span.set_attribute("app.customer_ref", customer_ref(customer_id))
        for name, value in flags.items():
            span.set_attribute(f"app.flag.{name}", value)

        # The SDK's request span nests under agent.turn.
        return client.messages.create(
            model="claude-sonnet-5-5",
            max_tokens=1024,
            messages=[{"role": "user", "content": user_message}],
        )
Enter fullscreen mode Exit fullscreen mode

What I'd put on that span, and how:

  • Prompt version. The first thing I'd want when behavior changes. Did the prompt change, or did something else?
  • Feature flag values. A flag can send a run down a completely different path. Without it on the trace, two runs with the same input look inexplicably different.
  • A customer reference. Lets you pull up every run for one account. Use a keyed reference or an opaque token, never the raw ID. Treat it as pseudonymous data, not anonymous data.
  • Tool call args, filtered. Useful when a tool returned the wrong thing, but arguments can hold emails, secrets or whole documents. Allow list the fields you record and omit or redact everything else. Truncating a field can still leave the secret in it.

Filtering attributes you add yourself doesn't sanitize content captured automatically. Review the SDK and API content settings separately.

Decide what content leaves your system

The platform spans have their own content setting. By default, the content mode is metadata only: identifiers, timing, status, token counts and names you chose, such as tool and agent names. Full content adds prompts, responses and tool call arguments and results.

Full content is the most useful mode for debugging, and it also means your observability platform now stores prompts and tool data under its own access and retention rules. Check those before you turn it on. The same care applies to anything you pass as a conversation ID, which the SDK exports in both modes, so keep personal data and secrets out of it.

Figure 3: What to attach to a trace, and what to keep off it

Figure 3: Attach versions, flags and keyed references. Omit or redact anything sensitive before it reaches a span.

A short checklist

  • [ ] SDK tracing: a tracer provider and an OTLP exporter are registered before the first SDK call.
  • [ ] API tracing: a destination is set up in the Console if you want model runs, agent turns and server tools.
  • [ ] Every agent turn carries the prompt version and the relevant feature flag values.
  • [ ] Customer references are keyed or tokenized, and treated as pseudonymous.
  • [ ] Tool call args are allow listed, with everything else omitted or redacted.
  • [ ] The content mode is a deliberate choice, checked against your platform's retention settings.
  • [ ] The SDK version is pinned while the feature is in beta.
  • [ ] Someone has opened a real trace and followed a run end to end before the first incident.

Closing

I like that Anthropic reached for OpenTelemetry instead of inventing a format. It works with the observability tools we already use, so agent runs can sit next to the rest of your system's traces. Attach the prompt version, the relevant flag values, a customer reference and carefully filtered tool call args, and you have more context to narrow down what went wrong.

What do you attach to your agent traces today? I'd like to hear what you've found useful. The original discussion is on LinkedIn.

Top comments (0)