Agentic AI · Module 25 of 41 · 9 concepts · about 16 min

Observability and Tracing

How to see what an agent did and why: traces and spans, what to record at each step, the metrics and alerts that matter, and why debugging an agent is harder than debugging ordinary software.

At work: The first thing built by every team that has run an agent in production, usually the week after the first incident nobody could explain.

Why agents are harder to debug

What it is

Ordinary software is debugged by reproducing the bug and stepping through the code. Agents break that method in five ways:

  • Non-determinism: the same input can take a different path each run.
  • The decision is not in your code: it is in a model's reply to a long prompt.
  • Silent failure: a wrong answer raises no exception. It looks exactly like a right one.
  • Distance: a mistake at step 3 shows up at step 12.
  • Many moving parts: prompt, model version, tools, retrieved documents, memory and state all influence the outcome.

Why it matters

So you cannot rely on reproducing a failure. You must have recorded it.

  • If the exact text sent to the model at the failing step was not saved, the cause is usually unrecoverable.
  • Logs written for ordinary software ('request received', 'response sent') say nothing about why the agent chose what it chose.
  • Observability for agents means capturing every step's inputs and outputs, so any run can be read afterwards like a story.

In the real world: An agent refunded the wrong order once in two thousand runs. It could never be reproduced; the saved trace showed a tool had returned two orders and the model picked the second.

Visual example

Comparison

Every row is a reason to record everything.

Debugging ordinary software versus an agent
ordinary softwareagent
Same input, same behaviourYesNot reliably
Where the decision livesIn code you can readIn a model's reply
A failure looks likeAn exception or error codeA plausible wrong answer
Cause and symptomUsually close togetherOften many steps apart
Main toolReproduce and step throughRead the recorded trace
What changed?The codeCode, prompt, model, tools, data or memory

Trying to debug by re-running the same request and watching. It behaves differently this time, you 'fix' something unrelated, and the original fault remains.

Practise it: Design an AI Customer Support Agent

Traces and spans

What it is

Two terms from distributed tracing:

  • A trace is the complete record of one run, from request to final answer. It has a unique trace id.
  • A span is one timed operation inside the trace: a model call, a tool call, a retrieval, a guardrail check. Each has a start, a duration, attributes and a parent.

Spans nest. The run is the root span; each agent step is a child; each model and tool call is a child of its step. The result is a tree you can expand and read.

Why it matters

The tree shape matches how an agent works.

  • One view shows the whole run, where the time went and where it failed.
  • Expanding a span shows exactly what went in and what came out.
  • The trace id links everything across services, and across agents in a multi-agent system.
  • OpenTelemetry is the open standard for this; using it means your traces work with most tools.

In the real world: A support engineer opens a customer's trace from the ticket and reads the whole run in under a minute; before tracing, the same question took an afternoon of searching logs.

trace 7f3a…  support-agent v2026-09-14.3   user 412   4.8 s   ₹0.31   ended: final_answer
└─ agent.run                                              4,812 ms
   ├─ guardrail.input              ok                         38 ms
   ├─ step 1
   │  ├─ llm.call   opus   in 2,480  out 96                1,410 ms
   │  ├─ tool.get_order      {order_id: 88213}               122 ms
   │  └─ tool.get_policy     {topic: late delivery}           61 ms
   ├─ step 2
   │  ├─ llm.call   opus   in 2,910  out 74                1,180 ms
   │  ├─ policy.check        refund_delivery_fee  allowed       2 ms
   │  └─ tool.refund_delivery_fee  {order_id: 88213}         340 ms
   ├─ step 3
   │  └─ llm.call   opus   in 3,060  out 58                1,020 ms
   └─ guardrail.output             ok                         44 ms

Logging each call as a separate line with no shared id. You then have thousands of model calls and no way to tell which belong to the same run, or in what order.

What to record at each step

What it is

For every model call:

  • the full prompt: system prompt version, messages, tool definitions;
  • the full response, including tool requests;
  • model and settings; input, output and cached tokens; cost; latency and time to first token; the stop reason.

For every tool call: name, inputs, result (or a reference if large), duration, success or error, and the idempotency key. For the run: user and tenant, agent version, step count, how it ended, total cost and time, and any human decisions.

Why it matters

The rule is: record enough that you could replay the decision.

  • The prompt is the most important thing to save and the most often skipped, because it is long.
  • Token and cost figures per step are what make cost debugging possible.
  • The stop reason and the 'ended' reason distinguish a real answer from a limit being hit.
  • Version stamps tie a change in behaviour to a change you made.

In the real world: A team that logged only response text could not explain a wrong refund; after adding full prompts, the next odd case was traced to a stale memory that had been placed in the context.

Visual example

Checklist

If a column is missing, a class of bug cannot be diagnosed.

What a trace must contain
levelrecordlets you answer
RunUser, tenant, version, steps, end reason, total cost and timeWhich runs fail, and since which release?
Model callFull prompt and response, tokens, cost, latency, stop reasonWhy did it decide that?
Tool callName, inputs, result, duration, error, idempotency keyWhat actually happened in the world?
RetrievalQuery, results with scores, what was keptWas the right information available?
GuardrailWhich check, verdict, reasonWhy was this allowed or blocked?
HumanWho decided, what they saw, whenWho approved this?
from opentelemetry import trace

tracer = trace.get_tracer("support-agent")

async def traced_model_call(messages, tools, *, run):
    with tracer.start_as_current_span("llm.call") as span:
        span.set_attribute("gen_ai.request.model", MODEL)
        span.set_attribute("agent.version", run.agent_version)
        span.set_attribute("agent.step", run.step)
        span.set_attribute("llm.prompt", redact(serialise(messages)))     # redact before storing
        reply = await call_model(messages, tools)
        span.set_attribute("llm.response", redact(serialise(reply.content)))
        span.set_attribute("gen_ai.usage.input_tokens", reply.usage.input_tokens)
        span.set_attribute("gen_ai.usage.output_tokens", reply.usage.output_tokens)
        span.set_attribute("llm.cost_rupees", cost_of(reply.usage))
        span.set_attribute("llm.stop_reason", reply.stop_reason)
        return reply

Recording everything, including customers' card numbers and addresses, in a tracing tool half the company can open. Redact sensitive fields before storage, restrict access, and set a retention period.

Metrics: latency, tokens, cost and failures

What it is

Metrics are numbers aggregated over many runs. The ones an agent team watches:

  • Outcome: task success rate, escalation rate, reversal rate (actions a person had to undo).
  • Efficiency: steps per task, tokens per task, cost per task, cost per resolved task.
  • Latency: time to first response, time per step, total task time, at the median and at the slow tail (p50, p95).
  • Failures: tool error rate per tool, invalid tool calls, runs ended by a limit, guardrail blocks, model errors and fallbacks.
  • Health: queue wait time, rate-limit hits, cache hit rate.

Why it matters

Traces explain one run; metrics show the pattern.

  • A rise in steps per task is the earliest sign that something has got worse, before success rate moves.
  • 'Ended by step limit' creeping up means the agent is getting stuck.
  • Cost per resolved task, not cost per task, is the business number: a cheaper agent that fails more is not cheaper.
  • Slicing by agent version, tool, tenant and task type turns a vague 'it feels worse' into a located cause.

In the real world: A dashboard showed steps per task rising from 5.1 to 6.4 over three days with success unchanged; the cause was a tool that had started returning an extra page of results, caught before the bill showed it.

Visual example

Dashboard

The third column is why the number is worth a chart.

The metrics on an agent dashboard and what each warns of
metrichealthy looks likea change means
Task success rateStable, near targetQuality has moved
Escalation rateStableToo high: timid or broken. Too low: over-confident
Steps per taskStable median and tailThe agent is wandering or retrying
Ended by limitRareLoops or impossible tasks
Tool error rate, per toolLowA dependency or description problem
Invalid tool callsNear zeroSchema or description drift
Cost per resolved taskStableContext growth or more failures
p95 task timeWithin targetA slow tool or provider

Watching averages. The average task takes six steps while one in fifty takes forty and accounts for a third of the bill. Look at the tail and at the most expensive runs.

Practise it: Design a Multi-Agent AI System, Design a Multi-Agent Data Analyst, Design an AI Coding Assistant, Design an Autonomous Research Agent

Logs, metrics, traces and evaluations together

What it is

Four kinds of signal, each answering a different question:

  • Traces: what happened in this run?
  • Metrics: how are all runs doing, and is it changing?
  • Logs: discrete events with detail: an error, a policy block, a deployment.
  • Online evaluations: automated quality scores on a sample of live runs: was the answer supported, was the right tool used?

And one human signal: feedback such as thumbs up or down, corrections, and reviewers' verdicts, attached to the trace.

Why it matters

Ordinary monitoring tells you the service is up. It cannot tell you the answers are right.

  • An agent can return HTTP 200 on every request while being wrong on a fifth of them.
  • Online evaluations and user feedback are the only signals of quality in production.
  • Linking them to traces means every bad score has a run you can open.
  • Bad runs then become test cases, which is how the system improves (module 29).

In the real world: For a month every infrastructure chart was green while a retrieval change quietly dropped answer accuracy; a 5 percent sampled groundedness check would have caught it on day one.

async def after_run(run) -> None:
    """Sampled quality checks on live traffic, stored against the trace."""
    if random() < 0.05:                                       # judge 5% of runs
        verdict = await judge_groundedness(run.final_answer, run.tool_results)
        scores.record(run.trace_id, "grounded", verdict.passed, reason=verdict.reason)
    if run.ended != "final_answer" or run.cost_rupees > 20:
        review_queue.add(run.trace_id, reason=run.ended)      # every odd run gets human eyes

def on_feedback(trace_id: str, thumbs_up: bool, comment: str) -> None:
    scores.record(trace_id, "user_feedback", thumbs_up, reason=comment)
    if not thumbs_up:
        review_queue.add(trace_id, reason="negative feedback")

A green infrastructure dashboard taken as proof the agent is working. Uptime and error rates measure the plumbing. Quality needs its own measurements.

Practise it: Design an AI Coding Assistant

Alerts and the debugging workflow

What it is

Alerts turn metrics into action. Useful ones for agents:

  • success rate or groundedness score falls below a threshold;
  • cost per task, or total spend per hour, jumps;
  • share of runs ended by a limit rises;
  • one tool's error rate spikes;
  • any run exceeds a hard ceiling on cost or steps;
  • a guardrail's block rate changes sharply.

When an alert fires or a complaint arrives, the workflow is: find the affected traces, read one from the top, locate the first wrong step, classify the cause (prompt, tool, retrieval, memory, model), fix, and add the case to the test set.

Why it matters

The discipline is to find the first wrong step, not the last.

  • The visible failure is usually downstream of the real mistake.
  • Classifying the cause prevents the reflex of editing the prompt for what is actually a tool problem.
  • Adding the case to the test set is what stops the same bug returning with the next change.

In the real world: A cost alert fires at 02:00: one run has spent ₹900. The trace shows a search tool returning an error the agent kept retrying; the on-call engineer disables the tool with a flag and goes back to bed.

Visual example

Debugging

Step three is the one people skip.

From a complaint to a fix that stays fixed

  1. Find the traceFrom ticket, user or alert
  2. Read from the topEach step's input and output
  3. First wrong stepmost skippedWhere context first went wrong
  4. Classify the causePrompt, tool, retrieval, memory, model
  5. Fix and add a testThe case joins the evaluation set

Alerting on everything. A channel with fifty alerts a day is ignored by the second week. Alert on a few outcome and cost signals; put the rest on a dashboard.

Privacy, sampling and cost of observability

What it is

Recording everything has its own costs. Three controls:

  • Redaction: remove or mask personal and secret data before a trace is stored: card numbers, addresses, tokens.
  • Access and retention: restrict who can read traces, and delete them after a set period.
  • Sampling: keep full detail for every failed, expensive, flagged or escalated run, and for a percentage of the rest.

Why it matters

Traces contain whatever your users typed and whatever your tools returned.

  • That makes the trace store one of the most sensitive databases you run.
  • Full prompts for every run at high volume are large; sampling keeps the useful ones.
  • 'Keep all failures, sample successes' preserves almost all debugging value for a fraction of the storage.
  • Summary metrics should still be recorded for every run, even those whose full trace is dropped.

In the real world: A health company masks patient identifiers in traces at the point of capture and keeps full traces for thirty days; its auditors accept the design because the masking happens before storage, not at display.

Sampling uniformly at 1 percent. The rare failure you most need is then 99 percent likely to be missing. Decide what to keep after the run ends, when you know whether it was interesting.

Practise it: Design a Multi-Agent Incident Response System, Design a Tool-Calling Agent Over 500 APIs

Interview questions: observability and tracing

What it is

The questions interviewers ask about observability and tracing, from the first screening call to the senior round. Cover the answer, say yours aloud, then compare.

Why it matters

Reading an idea and explaining it are different skills.

  • Each answer below is the two or three sentences an interviewer is listening for.
  • The reasoning behind each one is in the cards above. If an answer surprises you, go back to that card.

In the real world: An interviewer hands over a trace and asks what went wrong; candidates who scroll to the end guess, and those who start at step one find the tool result that was empty.

Q: Why is debugging agents harder than traditional software?

Runs are non-deterministic, the decision is inside a model's reply, failures are silent plausible answers, cause and symptom are steps apart, and behaviour depends on prompt, model, tools, retrieved data and memory. You must record runs because you cannot reliably reproduce them.

Q: What is a trace and a span?

A trace is the full record of one run under one id. A span is a timed operation within it, such as a model or tool call, nested under its step. Together they form a tree of everything that happened.

Q: What do you log for each model call?

The full prompt and response, model and version, input, output and cached tokens, cost, latency, stop reason, and the agent and prompt version. Redacted before storage.

Q: Which metrics do you monitor?

Task success, escalation and reversal rates; steps, tokens and cost per task and per resolved task; latency at p50 and p95; tool error and invalid-call rates; runs ended by limits; guardrail blocks; queue wait and rate-limit hits.

Q: How do you know quality is dropping in production?

Sampled online evaluations such as groundedness, user feedback, reviewer verdicts and outcome metrics like reversals, sliced by version. Infrastructure metrics cannot show it.

Q: Walk me through debugging a wrong answer.

Open the trace, read from the top, find the first step where the context held something false or lacked something needed, classify the cause, fix that part, and add the case to the evaluation set.

Q: How do you handle sensitive data in traces?

Redact at capture, restrict access, set retention, and keep full traces for failed or flagged runs while sampling the rest.

Q: How do you trace across multiple agents?

Propagate one trace id through every hand-off and delegation, with a span per agent run and per message, so the whole task appears as one tree.

Saying 'we would add logging' with no mention of saving the prompt.

Practise it: Design a Multi-Agent Incident Response System, Design a Tool-Calling Agent Over 500 APIs

Exercise: add tracing to an agent

What it is

A short build that uses this module's ideas. Write it yourself before looking anything up; the checks at the end tell you whether it works.

Why it matters

An idea you have only read fades in a week.

  • Building it once, even in fifty lines, is what makes it yours.
  • Interviewers can tell the difference between someone who has read about a loop and someone who has debugged one.

In the real world: A team's first tracing was exactly this JSON-lines file and a forty-line viewer; it paid for itself the first time a customer asked why they had been refused.

Your task. Instrument the from-scratch agent so any run can be read afterwards as a tree, then use the traces to find a planted bug.

Steps

  1. Give each run a trace id and write a span record (name, parent, start, duration, attributes) for the run, each step, each model call and each tool call. A JSON-lines file is enough.
  2. Record the full prompt and response on model spans, and tokens, cost, latency and stop reason.
  3. Record inputs, result, duration and error on tool spans.
  4. Write a small script that prints a trace as an indented tree with durations and costs.
  5. Add a redaction function that masks anything that looks like a phone number or card number before writing.
  6. Have a friend plant a bug (for example, make get_policy return the wrong policy one time in three). Find it using only the traces.
  7. Compute, over twenty runs, the median and worst steps per task and cost per task.

You are done when

  • Any run can be printed as a tree showing where time and money went.
  • You found the planted bug by reading a trace, not by reading code.
  • No raw phone or card number appears in the stored file.
  • You have a one-line summary per run: steps, tokens, cost, how it ended.

Stretch. Export the spans with OpenTelemetry to any tracing tool and compare with your home-made viewer.