AI Agent Observability vs. Traditional APM: What Actually Changes

AI Agent Observability vs APM - 2

By Tom Morgan · Updated November 6, 2026 · Desk research plus one simulation I ran and published below · No sponsorship

I made a scripted agent slightly worse on purpose. Its error rate stayed flat, its latency dropped by about a third, and its token bill shrank. Every classic dashboard got better or stayed put. The share of correct answers fell from 91% to 76%.

That is the whole subject of this article. Traditional APM answers “did the request run, and how fast?” An agent can pass both tests and still be wrong, and in 2026 a lot of production teams learned this the expensive way.

The short version

  • APM is still necessary. Datadog’s 2026 telemetry says roughly 1 in 20 production LLM requests fail, mostly from capacity limits, and plain APM catches those.
  • What’s new is four things APM doesn’t record: an outcome label per run, the path the agent took, tokens per run, and the prompt/effort/harness settings as deployable versions.
  • The hard part is statistical, not technical. You need enough labeled runs to see a quality drop, and my test shows how fast that number grows.
  • Nobody has automated root-cause analysis for agent failures yet. The best published method found the failing step 14.2% of the time.
  • OpenTelemetry’s GenAI conventions are still at “Development” status. Use them, but put a thin layer of your own on top.

Evidence labels used below: [ESTABLISHED] = primary source or peer-reviewed; [PROBABLE] = credible but self-reported, vendor-sourced, or secondhand; [SPECULATIVE] = my inference.

I broke an agent on purpose

The test is deliberately boring. A scripted support agent answers questions about a policy. No language model is called; behavior is driven by a seeded random number generator, so you can reproduce every number. It emits real spans through the OpenTelemetry Python SDK (version 1.45.2 in my run), using the agent span names from the GenAI conventions: invoke_agent, chat, execute_tool.

For the first 150 runs, the agent is configured with high effort: in about 90% of runs, it verifies the policy document with a second tool call before answering. Then I flip one setting to medium effort, where it verifies in about 50% of runs. No errors. Nothing pages anyone. That mirrors the mechanism in the April 2026 Claude Code case below, where a default was lowered to cut latency.

Both dashboards are computed from the same spans. The APM view reads status codes, durations, and error flags. The agent view adds an outcome label (in real life: a checker, an eval, or a user signal) and token counts.

Every classic metric held or improved after a silent change; the share of correct answers fell Four paired bar charts, before and after switching a simulated agent from high to medium effort. Model-call error rate 3.9 percent to 3.8 percent. p95 latency 7.2 seconds to 5.0 seconds. Mean tokens per run 4,679 to 3,879. Correct answers 91.0 percent to 75.7 percent, a drop of 15.3 points. Every classic metric held or improved. The one that matters fell. Mean over 40 simulated runs of the experiment: 150 agent runs before the change, 150 after (effort high to medium) What an APM-style dashboard shows What an outcome check shows Model-call error rate share of model calls 3.9% 3.8% beforeafter p95 latency seconds per run 7.2 s 5.0 s beforeafter Tokens per run mean, input + output 4,679 3,879 beforeafter Correct answers outcome label -15.3 points 91.0% 75.7% beforeafter Green = looks fine or better to a classic dashboard. Root-span HTTP status was 200 in every run. Simulation, not production data. Across 40 seeds the quality drop ranged from 7 to 22 points.

Figure 1. Same spans, two dashboards. Means over 40 seeds × 300 runs. Reproduce it with the script at the end of this section.

Two things stand out.

The regression looked like an optimization. Latency fell about 37% on average (5.3 s to 3.3 s), tokens fell 17%, and errors didn’t move. A team watching speed and cost would have congratulated itself. In the real Claude Code incident, latency was the stated motive for the change (details in Case 1).

Seeing it takes more runs than you’d guess. In a single seed (seed 7) the drop was only 7 points, and the 95% confidence intervals of the two phases overlapped. That’s not a bug; it’s the lesson. Here is how often a standard two-proportion test flags the true ~16-point drop, if every run carries an outcome label:

Labeled runs per phaseChance the drop is flagged (p < 0.05)
3032%
5060%
10092%
15097%
300100%

(60 seeds per row.) Now, the part that’s my inference [SPECULATIVE]: real outcome labels are sparse. If only 1% of your runs get a thumbs-up, a thumbs-down, or an eval score (my assumption, not a measured rate), 100 labeled runs means about 10,000 runs of waiting. Meanwhile, the old config is still shipping.

What this test does not show. The size of the drop is baked in by my parameters (verification 90% → 50%). What the test demonstrates is the shape of the problem: classic metrics can’t see this failure, and detecting it is a sample-size problem. It says nothing about how common such regressions are. <details> <summary>Full script (143 lines, Python 3, <code>pip install opentelemetry-sdk</code>)</summary>

python

"""
Scripted agent + real OpenTelemetry SDK. No LLM is called: behaviour is simulated
with a seeded RNG so anyone can reproduce the numbers.

Scenario (synthetic, modelled on the *mechanism* in Anthropic's April 2026
Claude Code postmortem: a default setting is lowered to cut latency):
  runs   0-149  config effort=high
  runs 150-299  config effort=medium   (silent change, no errors, nothing pages)

Two dashboards are computed from the SAME spans:
  APM view   : request count, error rate, p95 latency of the root span
  Agent view : outcome-correct rate, tool calls per run, p95 tokens per run
"""
import random, statistics as st, sys, math
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from opentelemetry.trace import Status, StatusCode

N_PER_PHASE = 150
PARAMS = {
    # probability the agent verifies the policy with a second tool call before answering
    "high":   {"verify": 0.90, "answer_ok_if_verified": 0.95, "answer_ok_if_not": 0.55},
    "medium": {"verify": 0.50, "answer_ok_if_verified": 0.95, "answer_ok_if_not": 0.55},
}
P_RATE_LIMIT = 0.04     # transient 429 on a model call, retried once (same in both phases)
P_RUNAWAY = 0.03        # agent loops on searches (same in both phases)

def simulate(seed):
    rnd = random.Random(seed)
    exporter = InMemorySpanExporter()
    provider = TracerProvider()
    provider.add_span_processor(SimpleSpanProcessor(exporter))
    tracer = provider.get_tracer("agent-demo")
    clock = 1_700_000_000_000_000_000  # ns, simulated

    def span(name, start, dur_ms, parent_ctx=None, **attrs):
        s = tracer.start_span(name, context=parent_ctx, start_time=start, attributes=attrs)
        return s, start + int(dur_ms * 1e6)

    for i in range(2 * N_PER_PHASE):
        effort = "high" if i < N_PER_PHASE else "medium"
        p = PARAMS[effort]
        t = clock + i * 60_000_000_000
        from opentelemetry import trace
        root = tracer.start_span(
            "invoke_agent support-bot", start_time=t,
            attributes={"gen_ai.operation.name": "invoke_agent",
                        "gen_ai.agent.name": "support-bot",
                        "http.response.status_code": 200,
                        "app.config.effort": effort})
        ctx = trace.set_span_in_context(root)
        cur = t
        tokens_in = tokens_out = 0
        tool_calls = 0

        def model_call(label, cur):
            nonlocal tokens_in, tokens_out
            ti, to = rnd.randint(900, 1600), rnd.randint(80, 400 if effort == "high" else 260)
            dur = rnd.gauss(1400 if effort == "high" else 950, 220)
            s = tracer.start_span(f"chat {label}", context=ctx, start_time=cur,
                                  attributes={"gen_ai.operation.name": "chat",
                                              "gen_ai.usage.input_tokens": ti,
                                              "gen_ai.usage.output_tokens": to})
            if rnd.random() < P_RATE_LIMIT:            # 429, then a successful retry
                s.set_status(Status(StatusCode.ERROR, "429 rate limited"))
                s.end(end_time=cur + int(300e6))
                cur += int(300e6) + int(800e6)          # backoff
                s = tracer.start_span(f"chat {label} (retry)", context=ctx, start_time=cur,
                                      attributes={"gen_ai.operation.name": "chat",
                                                  "gen_ai.usage.input_tokens": ti,
                                                  "gen_ai.usage.output_tokens": to})
            end = cur + int(max(dur, 200) * 1e6)
            s.end(end_time=end)
            tokens_in += ti; tokens_out += to
            return end

        def tool(name, cur):
            nonlocal tool_calls
            tool_calls += 1
            dur = max(rnd.gauss(350, 80), 80)
            s = tracer.start_span(f"execute_tool {name}", context=ctx, start_time=cur,
                                  attributes={"gen_ai.operation.name": "execute_tool",
                                              "gen_ai.tool.name": name})
            end = cur + int(dur * 1e6)
            s.end(end_time=end)
            return end

        cur = model_call("plan", cur)
        cur = tool("search_policy", cur)
        if rnd.random() < P_RUNAWAY:                    # runaway loop: many repeated searches
            for _ in range(rnd.randint(6, 10)):
                cur = model_call("loop", cur)
                cur = tool("search_policy", cur)
        verified = rnd.random() < p["verify"]
        if verified:
            cur = model_call("read", cur)
            cur = tool("fetch_policy_doc", cur)
        cur = model_call("answer", cur)
        correct = rnd.random() < (p["answer_ok_if_verified"] if verified else p["answer_ok_if_not"])
        root.set_attribute("app.outcome.correct", correct)   # from a checker / eval / user signal
        root.set_attribute("app.tool_calls", tool_calls)
        root.set_attribute("app.tokens_total", tokens_in + tokens_out)
        root.end(end_time=cur)
    provider.shutdown()
    return exporter.get_finished_spans()

def pct(xs, q):
    xs = sorted(xs); k = (len(xs) - 1) * q
    f, c = math.floor(k), math.ceil(k)
    return xs[f] + (xs[c] - xs[f]) * (k - f)

def wilson(k, n, z=1.96):
    p = k / n; d = 1 + z * z / n
    c = (p + z * z / (2 * n)) / d
    h = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / d
    return c - h, c + h

def summarize(spans):
    roots = [s for s in spans if s.name.startswith("invoke_agent")]
    out = {}
    for effort in ("high", "medium"):
        rs = [s for s in roots if s.attributes["app.config.effort"] == effort]
        ids = {s.context.span_id for s in rs}
        kids = [s for s in spans if s.parent and s.parent.span_id in ids]
        err = sum(1 for s in kids if s.status.status_code == StatusCode.ERROR)
        model_calls = sum(1 for s in kids if s.attributes.get("gen_ai.operation.name") == "chat")
        lat = [(s.end_time - s.start_time) / 1e9 for s in rs]
        ok = sum(1 for s in rs if s.attributes["app.outcome.correct"])
        tools = [s.attributes["app.tool_calls"] for s in rs]
        toks = [s.attributes["app.tokens_total"] for s in rs]
        out[effort] = dict(n=len(rs), http_2xx=sum(1 for s in rs if s.attributes["http.response.status_code"] == 200),
                           err_rate=err / max(model_calls, 1), p95_lat=pct(lat, .95), mean_lat=st.mean(lat),
                           correct=ok / len(rs), ci=wilson(ok, len(rs)),
                           tools=st.mean(tools), p95_tok=pct(toks, .95), mean_tok=st.mean(toks))
    return out

if __name__ == "__main__":
    seed = int(sys.argv[1]) if len(sys.argv) > 1 else 7
    r = summarize(simulate(seed))
    for k, v in r.items():
        print(k, {a: (round(b, 3) if isinstance(b, float) else b) for a, b in v.items() if a != "ci"},
              "ci=", tuple(round(x, 3) for x in v["ci"]))

Run python agent_vs_apm.py 7 for one seed. The 40-seed and power-curve numbers above come from looping simulate() and summarize() over seeds 1–40 (and 1–60 for the power table) with N_PER_PHASE set as shown. </details>

What APM still does well

Don’t read the above as “APM is obsolete.” Datadog’s State of AI Engineering 2026 report, published April 21, 2026, from usage data of thousands of customers, puts failed production AI requests at around 5%, with nearly 60% of those failures caused by capacity limits. Nearly seven in ten companies in the data use three or more models, median-usage teams more than doubled their tokens per request, and heavy users quadrupled theirs. [PROBABLE] (Datadog sells observability, and this is its own customer data.)

Coverage of the full report adds that in March 2026 the error rate fell to about 2% while rate limits were still close to a third of failures, around 8.4 million events in that month (e27 summary; I couldn’t open the full report, so treat the March figures as secondhand).

The most common failure in that dataset was a 429, not a hallucination. Latency, errors, retries, and capacity are classic APM territory, and an agent multiplies them because one user request fans out into many model calls. Datadog’s own framing is that failures increasingly come from system design: fragmented workflows, excessive retries, inefficient routing.

So the honest sentence is: APM is necessary and no longer sufficient.

What actually changes

The same agent request as a traditional APM trace and as an agent trace Left: a traditional APM waterfall with one request span returning 200 OK in 3.3 seconds, with auth and model call children, and a verdict of healthy. Right: an agent trace listing plan, search_policy tool call, a skipped fetch_policy_doc verification step, and the answer, with attributes showing effort medium and outcome correct false, and a verdict that the answer was unverified. What APM records one request, one verdict POST /agent/ask 200 OK · 3.3 s auth + db · 0.2 s model call · 2.9 s Kept: status code, duration, error flag, service name, host Verdict: healthy 200 OK, latency down, error rate flat. Nothing here can say the answer was wrong. What an agent trace adds path, settings, cost, outcome invoke_agent support-bot plan tool: search_policy tool: fetch_policy_doc skipped chat: answer app.config.effort=medium · tokens=3.9k app.outcome.correct=false Verdict: answer unverified Verification step skipped; the outcome check says wrong. Latency and tokens look better, which is the trap.

Figure 2. The same simulated request, seen by APM and by an agent trace.

Here’s the full comparison, one row per assumption that breaks:

WhatAPM assumesAgents do insteadWhat to add
Unit of failureA request errors or times outA run completes with a wrong or harmful resultAn outcome label per run
Execution pathCall graph fixed in codePath chosen at runtime by the modelSpans for plan, tool call, handoff
RepeatabilitySame input, same pathIdentical prompts can divergeSampled full trajectories
CostRoughly flat per requestVaries with every stepTokens per run, p95 not just mean
PayloadDon’t log bodies (PII)The prompt and response are the evidenceRedacted, sampled content capture
Release unitA deploy is codePrompt, effort level, and harness settings change behaviorThose settings as trace attributes

Anthropic’s own measurements show why the cost row matters: in its data, agents use about 4× the tokens of a chat, and multi-agent systems about 15× [ESTABLISHED] as a company’s measurement, not an industry constant. If your dashboard plots requests per second but not tokens per run, the first place you’ll see the problem is the invoice.

Five cases

I’ve ordered these by how closely they match the experiment, not by date.

Case 1: Claude Code, spring 2026 — a default setting was the bug [ESTABLISHED]

Anthropic’s April 23, 2026 postmortem traced weeks of user complaints to three changes in the Claude Code harness (the API was not affected): the default reasoning effort was lowered from high to medium on March 4 to reduce latency severe enough that the UI looked frozen; a change meant to clear old reasoning from sessions idle over an hour shipped with a bug that kept clearing it on every turn (March 26); and a system prompt line limiting response length (April 16) hurt coding quality. All three were resolved by April 20.

Why it was hard to see: each change hit a different slice of traffic on a different schedule, so the aggregate looked like inconsistent degradation. Anthropic says its internal usage and evals didn’t initially reproduce the problems. The caching bug passed code review, unit tests, end-to-end tests, and dogfooding, and still took over a week to confirm. On the prompt change, weeks of internal testing showed no regressions on the evals it ran, but a later ablation on a broader eval set showed a 3% drop.

What Anthropic says it will change: per-model evals for every system prompt change, line-by-line ablations, soak periods, and gradual rollouts for changes that could trade off against intelligence. Those are release-engineering practices, applied to settings that used to feel like configuration.

My read [SPECULATIVE]: this is the experiment in the wild. A latency fix looked sensible, nothing errored, and quality moved quietly. If “effort level,” “prompt version,” and “harness version” aren’t attributes you can filter traces by, you can’t even ask whether quality moved when they changed.

Case 2: Three bugs behind a green dashboard, Aug–Sep 2025 [ESTABLISHED]

In its September 2025 postmortem, Anthropic describes a context-window routing error that began on August 5, affecting about 0.8% of Sonnet 4 requests. A load-balancing change on August 29 pushed it to 16% in the worst hour (August 31). About 30% of Claude Code users who made requests in that period had at least one message misrouted, and routing was “sticky,” so follow-ups tended to hit the same wrong server.

Anthropic’s explanation for slow detection is the most useful paragraph in this article: its evaluations didn’t capture the degradation users reported, partly because Claude often recovers from isolated mistakes; it lacked a clear way to connect the spike in reports to a routine load-balancing change; and its own privacy controls limited how engineers could examine interactions that weren’t submitted as feedback. Its fixes: more sensitive evals, quality evals running continuously on true production systems, and tooling to debug community feedback without giving up privacy.

That last point is the quiet tension in all agent observability: the prompt and the response are the evidence, and they’re also the sensitive part. Content capture is a decision, not a default.

Case 3: Anthropic’s Research system — trace the structure, not the conversation [ESTABLISHED]

When Anthropic built the multi-agent Research feature, it reported that agents are non-deterministic between runs even with identical prompts, so users’ reports of “not finding obvious information” couldn’t be explained by guessing. Full production tracing let the team see whether agents used bad queries, poor sources or hit tool failures. They monitored decision patterns and interaction structure without reading the contents of individual conversations.

Three more details from the same post matter for observability. Token usage by itself explained 80% of the performance variance in one benchmark (BrowseComp), so token economics are a quality variable, not just a cost one. Because agents are stateful, the team ships changes with rainbow deployments, shifting traffic gradually while old and new versions run side by side. And for agents that change state over many turns, they evaluate the end state rather than every step.

The 90.2% improvement over a single agent that the post reports is measured on Anthropic’s internal research eval, so weigh it accordingly.

Case 4: Air Canada — the defect was the sentence [ESTABLISHED] for the ruling, [SPECULATIVE] for the rest

In Moffatt v. Air Canada, 2024 BCCRT 149, the airline’s chatbot told a customer he could book at full price and claim a bereavement fare afterwards. The real policy didn’t allow that. The tribunal found negligent misrepresentation and awarded C$812.02 in total (damages, interest and fees), rejecting the argument that the chatbot was a separate entity responsible for itself.

I know nothing about Air Canada’s telemetry. But a conventional trace of that conversation would very likely have shown a clean response and normal latency. The failure lived entirely in the content, which is what APM doesn’t read. The sum is small; the precedent isn’t. A company answered for what its bot said.

Case 5: Replit — observability is not control [PROBABLE]

In July 2025, during a public 12-day experiment by SaaStr’s Jason Lemkin, Replit’s agent ran destructive commands against a production database during a code freeze. Press reports put the loss at records on more than 1,200 executives and over 1,100 companies. The agent then told the user a rollback wasn’t possible, and the user recovered the data manually. Heise reported that preview, test, and production shared one database at the time, and Replit’s CEO acknowledged the incident publicly (The Register). I’m relying on press coverage and the user’s own posts here, not a Replit postmortem.

Two lessons, and the second is uncomfortable. The agent’s own account of events was wrong, so you need an independent record of actions rather than the agent’s narration. And the freeze was an instruction, not a permission. A perfect trace would have made a better postmortem. It would not have stopped the deletion. Permissions would have.

The part nobody has solved: which step broke?

A 2025 ICML paper built a dataset (Who&When) from failure logs of 127 multi-agent systems and tested automated failure attribution. The best method identified the responsible agent 53.5% of the time but the failing step only 14.2%; some methods did worse than random, and longer logs made results worse. [ESTABLISHED] (peer-reviewed, but it tested 2025-era models; I found no newer benchmark.)

A Berkeley-led study, MAST (NeurIPS 2025 datasets track), annotated 1,642 traces from seven multi-agent frameworks and sorted failures into 14 modes in three groups: specification issues, inter-agent misalignment, and task verification. It cites earlier studies reporting failure rates between 41% and 86.7% for popular multi-agent frameworks. [ESTABLISHED]

Translation: tracing gives you the evidence, not an automatic verdict. If a vendor promises one-click root cause for agent failures, ask for step-level attribution accuracy on your own traces.

How many teams do this, and how well?

Teams see what agents did far more often than they test it Horizontal bars from LangChain’s State of Agent Engineering survey: any observability 89 percent, step-level tracing 62 percent, offline evals 52.4 percent, online evals 37.3 percent. Teams see what agents did far more often than they test it Share of respondents · LangChain State of Agent Engineering · 1,340 responses, Nov 18 to Dec 2, 2025 Any observability 89% Step-level tracing 62% Offline evals 52.4% Online evals 37.3% Blue = checks on quality. Grey = visibility into what ran.

Figure 3. Source: LangChain’s report (primary).

Among teams that already have agents in production, 94% report some observability, 71.5% have full tracing and 44.8% run online evals; roughly a third of all respondents name quality as their top barrier. [PROBABLE] Read the sample with care: 63% of respondents work in technology, 49% in companies under 100 people, and LangChain sells LangSmith.

On the analyst side, Gartner’s March 30, 2026 press release predicts LLM observability investment will reach 50% of GenAI deployments by 2028, up from 15% today (per press coverage of the release), and says the priority is shifting from speed and cost toward quality measures. That’s a forecast, not a measurement [PROBABLE], but it matches what the cases above show.

OpenTelemetry: use it, but don’t marry it

OpenTelemetry now has agent span conventions: create_agent, invoke_agent, invoke_workflow, plan and execute_tool, plus MCP spans. But every gen_ai.* attribute in the sources I could find (through early-to-mid October 2026) is still at Development status, the lowest of three stability levels. On June 12, 2026, v1.42.0 moved the conventions into a dedicated repository, semantic-conventions-genai, and a mid-2026 ClickHouse explainer notes the namespace went through three attribute renames in a single release cycle. Microsoft’s Agent Framework team has an open issue about emitting spans from a mix of convention versions, which confuses backends. [ESTABLISHED] as of those dates; some vendor posts claim stability the registry doesn’t show.

Practical reading: use OTel for transport and context propagation, and keep your own thin layer between your code and the attribute names, so a rename is a one-file change.

python

# One place that knows the gen_ai.* names. Your own app.* keys survive upstream renames.
def agent_run_attrs(agent, harness_v, prompt_v, effort):
    return {
        "gen_ai.operation.name": "invoke_agent",
        "gen_ai.agent.name": agent,
        "gen_ai.agent.version": harness_v,   # exists in the conventions (Development)
        "app.prompt.version": prompt_v,
        "app.config.effort": effort,
    }

What I’d do in the first two weeks

  • Keep your APM and add a run ID. One ID tying every model call, tool call, and handoff of a run together. Nothing else works without it.
  • Define “wrong” per workflow. A thumbs-down, a failed downstream check, a human override. A crude outcome label beats none, and the power table above says how many you need.
  • Make settings first-class attributes. Model, prompt version, effort level, harness version on every trace (Case 1).
  • Alert on tokens per run at p95. Agent cost explodes on a few long runs, so hide them.
  • Sample real traffic into evals. Include resumed and multi-turn sessions, not only fresh prompts (Cases 1 and 2).
  • Decide content capture on purpose. Redact, sample, set retention. If you can’t store content, store structure (Case 3).
  • Treat prompt and effort changes as releases. Soak period, gradual rollout, rollback path (Case 1).
  • Separate permissions from observability. Log everything, and also require approval for destructive actions (Case 5).
  • Use LLM judges for triage, humans for blame. With 14.2% step-level accuracy in the best published result, don’t automate the verdict.

What could be wrong with this analysis?

  • The simulation is synthetic, and its parameters are mine. It proves a mechanism, not a prevalence, and it contains no real model behavior.
  • Most hard numbers come from vendors and labs with something to sell or defend (Datadog, LangChain, Anthropic). I verified them against primary pages where I could open them; Datadog’s March figures and the Gartner percentages came through coverage.
  • Cases 1 and 2 are self-reported postmortems. They’re unusually candid, but they’re one side’s account.
  • Case 5 rests on press coverage and the user’s own posts, and my reading of Case 4’s telemetry is an inference.
  • LangChain’s sample skews toward tech and small companies; adoption rates elsewhere are likely lower.
  • The Who&When and MAST work used 2025-era models; step-level attribution may have improved since.
  • “Mostly APM plus four additions” is my judgment, not a finding. A heavily agentic product might reasonably put the balance elsewhere.
  • OpenTelemetry’s status can change quickly. Check the repository before you build on it.
  • I didn’t benchmark any commercial observability tool.

Questions people ask

Can I just use my existing APM tool? For latency, errors, and capacity, yes, and you should. You’ll still need outcome labels, run-level trajectories and token metrics, either in that tool or beside it.

Is OpenTelemetry’s GenAI standard stable? Not in the sources above: every gen_ai.* attribute is at Development status, so design for change.

Do I have to log prompts and responses? No. Anthropic’s Research team traced decision structure without reading conversations. But Anthropic’s 2025 postmortem also shows the cost: its privacy controls limited how engineers could investigate unreported interactions. Decide per workflow.

What’s the difference between observability and evals? Observability tells you what happened. Evals tell you whether it was good. LangChain’s survey found 89% with observability and 52% with evals.

Can an LLM find the root cause for me? Sometimes it can point at the right agent. Pinpointing the failing step succeeded about 14% of the time in the best published method, tested on 2025-era models.

Which tool should I pick? I haven’t benchmarked vendors, so I won’t rank them. Choose based on whether it speaks OpenTelemetry, how it handles content redaction, and whether you can attach your own quality scores.

The short ending

Every case here has the same shape: something changed, nothing errored, and the people who noticed first were users. Agent observability starts the day you decide what “wrong” means, measure it, and attach it to the setting that changed. Your dashboard can be green while your agent is wrong.

Sources

Primary (opened and read for this update)

Secondary (used for specific figures, flagged in the text)

About this piece: written by Tom Morgan from the sources linked above plus one simulation (script included; no language model was called and no commercial tool was tested). No sponsorship, no affiliate links. Limitation: most evidence comes from English-language engineering blogs, papers, and companies that build or run agent products.