๐Ÿ“š Study Notes / Home / GenAI / Session 8
Session 08 ยท Threads & Tracing

Threads & Tracing โ€” keeping memory of, and watching, your agent

In Session 7 you built your first agent with the Agent SDK. Now come the two skills that turn a toy agent into something you can actually trust: giving it a thread so it remembers the conversation across turns, and adding tracing so you can see exactly what it did and fix it when it goes wrong. As always, every topic starts with a tiny "explain like I'm 5" story, then we go deep with real examples. Take it slow.

โฑ 22 min read๐Ÿ“– 5 topics

1 Threads โ€” remembering across turns


Explain like I'm 5

Imagine you're chatting with a friend who has a terrible memory โ€” every time you say something new, they forget everything that came before. Annoying, right? To fix it, you keep a little notebook of your whole conversation, and before each new sentence you read the whole notebook back to them so they're caught up. A thread is that notebook. The SDK keeps it for you, so the agent feels like it remembers โ€” even though the model itself forgets the moment it finishes answering.

First, recall: the model is stateless

Back in Session 1 we learned the big, surprising truth: an LLM is stateless. It has no memory between requests. Each time you call the model, it reads what you send, produces an answer, and then completely forgets the whole exchange. ChatGPT only feels like it remembers because the app secretly re-sends the entire conversation with every new message.

So if the model has no memory, who keeps the conversation? Something on the outside has to store the history and re-send it each turn. In the Agent SDK, that "something" is a thread.

What a thread actually is

A thread (sometimes called a session or conversation) is a persistent object that holds everything about one ongoing conversation across multiple turns. A turn is one round of "user says something โ†’ agent responds." A thread accumulates turns.

Concretely, a thread holds:

What's in a threadWhy it's there
The full message historyEvery user message and every agent reply, in order โ€” the "notebook."
Tool calls & their resultsSo the agent remembers it already looked something up and what it found (recall the agent loop from Session 3).
Running state / scratch dataThings the agent is keeping track of mid-task (e.g. a partly-filled form, a running total).
An identifierA thread_id so you can save it, reload it later, and keep different users' conversations separate.
The one big idea

The model forgets; the thread remembers. Continuity in a chat is an illusion created by replaying the thread's stored history to the stateless model on every single turn. The thread is the memory.

How the SDK uses a thread, step by step

When you send a new message on an existing thread, the SDK does roughly this:

๐Ÿ“’
1. Load thread
Fetch stored history for this thread_id
โ†’
โž•
2. Append
Add the new user message
โ†’
๐Ÿง 
3. Send all
Pass the whole history to the model
โ†’
๐Ÿ’ฌ
4. Get reply
Model answers using full context
โ†’
๐Ÿ’พ
5. Save
Append the reply back to the thread

Notice step 5: the reply is written back into the thread. That's what makes the next turn aware of this one. Without a thread, you'd be back to the forgetful friend.

Example: multiple turns on one thread

Watch how a thread carries context so the agent can resolve "it" and "that one":

  • Turn 1. User: "What's the capital of Japan?"
    Agent: "Tokyo."  โ†’ thread now holds both lines.
  • Turn 2. User: "How many people live there?"
    The word "there" means nothing on its own. But because the SDK re-sends the whole thread, the model sees Turn 1 and knows "there" = Tokyo.
    Agent: "About 14 million in the city proper."
  • Turn 3. User: "And compared to Osaka?"
    Again, "compared" only makes sense with the prior turns in view. The thread supplies them, so the agent gives a sensible comparison.

Start a brand-new thread and ask only Turn 3 โ€” the agent has no idea what you're comparing. That gap is exactly what the thread fills.

Threads vs the context window

A thread can grow forever, but the model's context window (Session 1) is a fixed size. Once a thread's history is longer than the window, something has to give โ€” usually the oldest turns get trimmed or summarised before sending. So a thread is the full record; what reaches the model each turn is whatever fits. (Smarter ways to handle this are exactly what Session 9 on memory systems is about.)

Watch out

Because the whole thread is re-sent every turn, long threads cost more per message (more input tokens โ€” recall token economics in Session 1) and can eventually overflow the context window. Don't keep one giant thread running forever for unrelated topics; start fresh threads when the conversation truly changes subject.

Recap The model is stateless and forgets after every reply. A thread is the persistent object that stores the full conversation โ€” messages, tool calls, and state โ€” and re-sends it to the model each turn so the agent appears to remember. The thread is the memory; the model is just the engine that re-reads it.

2 What is tracing?


Explain like I'm 5

Imagine your friend goes to the shop to buy you a snack, but comes back with the wrong thing. You ask "what happened?" and they say "...I dunno." Useless! Now imagine they came back with a receipt and a little diary: "I went to aisle 3, picked the blue box, paid $2, it took 5 minutes." Now you can see exactly where it went wrong. Tracing is giving your agent that diary โ€” a record of every single thing it did on the way to its answer.

What tracing captures

Tracing means automatically capturing a structured, time-ordered record of everything that happened during one agent run (one full attempt to handle a request, which may involve many model calls and tool calls โ€” the agent loop from Session 3). A good trace records:

  • Every model call โ€” the exact prompt sent, the response, the model used.
  • Every tool call โ€” which tool, what arguments went in, what came back.
  • Inputs and outputs at each step, so you can read the data flowing through.
  • Timing โ€” how long each step took (latency).
  • Token usage & cost โ€” how many input/output tokens each model call burned.
  • Errors โ€” anything that failed, with the error message.

Two key words: span and trace

These two words are the whole vocabulary of tracing, so let's nail them:

TermWhat it isEveryday analogy
SpanA record of one single operation โ€” one model call, or one tool call. It has a start time, an end time, inputs, outputs, and a status.One line item on the receipt.
TraceThe whole tree of spans for one agent run, from the user's request to the final answer. Spans can nest inside spans (a model call that triggers a tool call that triggers another model call).The entire receipt + diary for one shopping trip.

So a trace is made of many spans, often nested like a tree. Reading a trace top-to-bottom is like replaying the agent's whole thought process in slow motion.

Example: what a trace looks like

A user asks an agent: "What's the weather in Paris and should I bring an umbrella?" The trace (simplified) might look like this tree of spans:

TRACE: "weather in Paris" run  (total 2.3s, 1,840 tokens)
โ”œโ”€ SPAN model_call #1            (0.6s)  LLM decides to call a tool
โ”‚    input:  user question + system prompt
โ”‚    output: tool_call get_weather(city="Paris")
โ”œโ”€ SPAN tool_call get_weather    (0.9s)
โ”‚    input:  {"city": "Paris"}
โ”‚    output: {"temp_c": 14, "rain": true}
โ””โ”€ SPAN model_call #2            (0.8s)  LLM writes the final answer
     input:  history + tool result
     output: "It's 14ยฐC and raining in Paris โ€” yes, bring an umbrella!"

Every step is visible: what the model decided, what the tool returned, how long each took. If the answer were wrong, you could point at the exact span where it went off.

Why you can't debug agents with print statements alone

When you wrote normal programs, sprinkling print() statements was often enough. Agents break that approach for several reasons:

  • They're non-deterministic. The same input can produce different steps each run (recall temperature, Session 1). A bug might appear only 1 time in 20 โ€” a print you happened to add won't be there next time it strikes.
  • They loop and branch. An agent might call a tool, reconsider, call another, loop back (Session 3's agent loop). Flat printed lines make it almost impossible to see which step led to which.
  • The data is huge. Full prompts, tool outputs, and histories are enormous. Dumping them as raw prints turns your console into an unreadable wall of text.
  • You need structure and timing. "Which step was slow? How many tokens did step 3 cost?" Prints don't measure that; spans do, automatically.
  • It happens in production. You can't add prints to a bug a user hit yesterday. But if tracing was on, the trace is already saved and waiting for you.
Key takeaway

Tracing is to agents what an X-ray is to a doctor. Without it you're guessing from the outside; with it you can see the skeleton of the whole run. In the Agent SDK, tracing is usually a built-in feature you turn on โ€” you don't have to hand-write all those spans yourself.

Recap Tracing records a structured, timed log of everything an agent run did โ€” every model call and tool call, with inputs, outputs, timing, tokens, and errors. A span is one operation; a trace is the whole nested tree for one run. Because agents are non-deterministic, looping, and data-heavy, print statements aren't enough โ€” you need traces.

3 Debugging agent workflows


Explain like I'm 5

If your toy robot ends up in the wrong room, you don't just shrug โ€” you follow its footprints backwards: "it turned left here, then went straight there... ah, it turned the wrong way at the kitchen!" A trace is the robot's footprints. Debugging an agent is just following those footprints back to the exact step where it took a wrong turn.

The common ways agent runs go wrong

From Session 3 you already met agent failure modes. Traces are how you actually spot which one happened. The usual suspects:

FailureWhat it looks likeHow the trace reveals it
Wrong toolAgent used the calculator when it should have searched the web.A tool-call span names the wrong tool.
Bad argumentsRight tool, but garbage inputs (e.g. searched "wether" instead of "weather", or passed the wrong city).The tool-call span's input field shows the bad args.
LoopingAgent calls the same tool over and over, never finishing.The trace shows the same span repeating many times.
Bad retrievalIn a RAG agent, it fetched the wrong documents, so the answer is built on the wrong facts.The retrieval span's output shows irrelevant chunks.
Tool error swallowedA tool failed, but the agent carried on as if it succeeded.A span has an error status, yet the next model call ignores it.
Model misreadsTool returned the right data, but the model summarised it wrong.The final model-call span: good input, wrong output.

A step-by-step debugging walkthrough

Here's the reliable routine for using a trace to find where a run went wrong. Always work from the symptom back toward the cause:

  1. Reproduce or locate the run. Find the trace for the exact failed request (by thread_id, user, or timestamp). With tracing on, it's already saved.
  2. Read the final answer span first. Confirm what actually went wrong โ€” wrong fact? Cut off? Refused? This tells you what kind of failure to hunt for.
  3. Walk backwards through the spans. Look at the step just before the final answer. Was the input to that step already wrong? If yes, the bug is earlier; keep going back. If the input was good but the output was bad, you've found the broken step.
  4. Inspect inputs vs outputs at the suspect span. This is the heart of it: at each span, ask "given this input, is this output reasonable?" The first span where a good input produced a bad output is your culprit.
  5. Check tool calls closely. Right tool? Right arguments? Did the tool return what you expected, or an error/empty result the agent then misused?
  6. Check for loops. Scan for repeated spans. If the agent kept retrying, look at why each attempt "failed" in its eyes.
  7. Form a fix and re-run. Adjust the prompt, tool description, or logic โ€” then run the same input again and compare the new trace to the old one.
Worked walkthrough

An agent that books meetings replied: "I've scheduled your meeting for 3pm." But the user wanted 10am. You open the trace and walk backwards:

  • Final span (model_call #2): output says "3pm". Its input contained {"time": "15:00"} from the tool result. So the model didn't invent 3pm โ€” it faithfully reported what the tool gave back. Bug is earlier.
  • Tool span (book_meeting): input was {"time": "15:00"}. But the user said 10am! So the wrong time was sent into the tool. Bug is even earlier.
  • First span (model_call #1): the model read "10am" and produced the tool call with "15:00". Found it. The model mis-converted "10am" โ€” likely confused by an ambiguous timezone instruction in the system prompt.

Fix: clarify the system prompt about timezones / 12-vs-24-hour format, re-run the same request, and confirm the new trace shows {"time": "10:00"}.

Common trap

Don't fix the symptom. Here it would be tempting to "fix" the final answer wording โ€” but the real bug was three spans upstream. Always trace back to the first step where good input became bad output; fixing anything later just hides the problem.

Recap Debugging an agent = reading its trace to find where the run went off. Start at the symptom (final answer), walk backwards span by span, and find the first step where a good input produced a bad output. Check tools for wrong choice, bad args, errors, or loops. Fix the root cause, re-run, and compare traces โ€” never patch the symptom.

4 Observability practices


Explain like I'm 5

A car has three kinds of "tell me what's happening" gadgets. The warning light that blinks when something specific happens ("door open!") โ€” those are logs. The dashboard dials showing speed and fuel as numbers โ€” those are metrics. And imagine a magic flight recorder that saved your whole journey turn by turn โ€” that's a trace. Observability just means having all three so you always know how your agent is doing.

The three pillars: logs vs metrics vs traces

Observability is the practice of being able to understand what your system is doing from the outside, without guessing. It rests on three kinds of signal โ€” people often confuse them, so here's the clean version:

SignalWhat it isAnswers the questionExample
LogsIndividual timestamped event messages."What happened, and when?"2026-06-20 10:02 ERROR weather_tool timed out
MetricsNumbers aggregated over time โ€” counts, averages, rates."How much / how often / how fast, overall?""Average latency = 2.1s; error rate = 3%."
TracesThe full step-by-step story of one request (Topic 2)."What exactly happened in this one run?"The Paris-weather span tree above.

They work together: a metric tells you something is wrong ("error rate jumped"), logs tell you roughly what ("weather tool timing out"), and a trace lets you see one bad run in full detail to confirm the cause.

The numbers that matter for agents

For LLM agents specifically, three metrics dominate everything:

MetricWhat it measuresWhy you care
LatencyHow long a run (or a step) takes.Slow agents feel broken; users leave. Agents are extra slow because they make multiple model + tool calls per answer.
Token costInput + output tokens per run, in money.Every model call in the loop costs tokens (Session 1). A chatty agent can be shockingly expensive at scale.
Error rateFraction of runs that fail or give bad answers.Your headline quality number. A rising error rate is the first sign something broke.
Example: a metric catches a regression

You ship a new version of your agent's prompt on Tuesday. On Wednesday a dashboard shows average tokens per run jumped from 1,800 to 4,500 and latency doubled. The metrics raised the alarm. You open a few traces from Wednesday and discover the new prompt caused the agent to call the search tool 3ร— per answer instead of once. Metrics found the problem; traces explained it. You roll the prompt back (prompt versioning, Session 1) and the numbers recover.

Tools you'll hear about

You don't build observability from scratch. There are dedicated platforms โ€” at a high level:

  • SDK tracing dashboards โ€” many agent frameworks (including the Agent SDK) ship a built-in trace viewer. You turn tracing on, run your agent, and view the span trees in a web UI.
  • LangSmith and similar LLM-observability tools โ€” specialised dashboards for inspecting traces, comparing prompt versions, tracking token cost and latency, and running evaluations over many runs.
  • General observability stacks (the kind used for any software) can ingest logs and metrics too, but the LLM-specific tools understand prompts, tokens, and tool calls out of the box.
Don't sweat the brand names

The specific tool matters far less than the practice. Whatever you use, you want the same three things: searchable traces of individual runs, dashboards of the key metrics, and alerts when a metric crosses a threshold.

What to monitor in production

Once your agent is live and real users depend on it, watch at minimum:

  • Latency (and percentiles โ€” the slowest 5% of users, not just the average).
  • Token cost per run and total daily spend (catch runaway loops before the bill does).
  • Error rate and tool-failure rate (which tools fail most?).
  • Loop / step-count โ€” how many model+tool steps an average run takes; sudden jumps mean trouble.
  • User feedback signals โ€” thumbs up/down, retries, abandonment.
  • A sample of full traces kept for spot-checking and for debugging reported bugs.
Privacy note

Traces contain real user inputs and outputs, which may include personal or sensitive data. In production, be careful what you store, who can view it, and for how long โ€” redact secrets and follow your data-handling rules. Observability is powerful, but it's still real user data.

Recap Observability = understanding your agent from the outside via logs (events), metrics (aggregated numbers), and traces (full per-run stories). The key agent metrics are latency, token cost, and error rate. Use SDK dashboards or tools like LangSmith; in production, monitor those metrics, watch for loops and runaway cost, keep sample traces, and respect user-data privacy.

5 Failure investigation: a case study


Explain like I'm 5

A detective doesn't guess who did it โ€” they follow the clues from the scene of the crime backwards until they find the real culprit. When an agent gives a wrong answer, you become the detective, and the trace is your pile of clues. Let's solve one mystery together, start to finish.

The case: a confidently wrong answer

You run a customer-support agent for an online store. It has a tool lookup_order(order_id) that fetches order details, and it uses RAG retrieval over the store's policy documents. A customer complains:

The bug report

Customer asked: "Can I return order #A-4471? It arrived broken." The agent confidently replied: "Sorry, order #A-4471 is outside the 30-day return window and cannot be returned." But the order arrived three days ago โ€” it absolutely should be returnable. The agent gave a wrong answer, and an angry customer.

The investigation process

Here's the repeatable flow a good investigation follows โ€” the same skeleton every time:

๐Ÿšฉ
1. Reproduce
Find the exact trace
โ†’
๐Ÿ”
2. Read trace
Walk spans backwards
โ†’
๐ŸŽฏ
3. Isolate
First bad-output span
โ†’
๐Ÿ› ๏ธ
4. Fix
Patch the root cause
โ†’
โœ…
5. Verify
Re-run, compare traces

Following the clues

We pull up the trace for that exact run and walk it backwards, applying the debugging routine from Topic 3:

Span (latest โ†’ earliest)InputOutputVerdict
model_call #2 (final answer)order data + retrieved policy text saying "30-day window from order date""outside the 30-day window, cannot return"Output matches its input โ†’ not the root cause. Look earlier.
retrieval span (RAG)query: "return policy"fetched the old 2023 policy doc (30 days from order date)๐Ÿšฉ Wrong document โ€” the current policy is 30 days from delivery date. Suspicious.
tool_call lookup_order{"order_id": "A-4471"}{"ordered": "2026-04-10", "delivered": "2026-06-17"}Tool worked fine. Note: ordered long ago, delivered 3 days ago.
model_call #1user questionplans: lookup order, then retrieve policyReasonable plan โ†’ fine.
Root cause found

Two things combined. First, the agent retrieved an outdated policy document (30 days from order date) instead of the current one (30 days from delivery date) โ€” a bad retrieval failure. Given that wrong policy, the final model call reasoned correctly from wrong facts: ordered 2026-04-10 is indeed >30 days ago, so it said "no return." The model wasn't broken; it was fed bad source material.

The fix, and verifying it

Fix: the root problem is in retrieval, so we fix retrieval โ€” remove/replace the stale 2023 policy doc in the knowledge base so only the current "30 days from delivery" policy can be retrieved. (We might also tighten the system prompt to always reason from delivery date.)

Verify: re-run the same question and read the new trace. Now the retrieval span returns the current policy, the final model call sees "30 days from delivery (2026-06-17)", and the answer becomes: "Yes โ€” your order was delivered 3 days ago, well within our 30-day return window. Here's how to start a return." The new trace confirms the fix at the exact span that was broken. Case closed.

Why the trace was essential

From the outside, this looked like "the model hallucinated." But the trace proved the model reasoned correctly โ€” the real fault was a stale document in the knowledge base. Without the trace you might have wasted days "fixing" the model with prompt tweaks, while the true bug sat quietly in your data. That's the whole point of this session.

Recap A real failure investigation: reproduce the run, read its trace backwards, isolate the first span where good input produced bad output, fix that root cause, then re-run and compare traces to verify. In our case a "hallucination" was actually bad retrieval of a stale policy doc โ€” only the trace revealed it. Fix the data, not the symptom.

โ˜… Putting it all together


This session gave you the two things that turn an agent from a fragile demo into something you can run for real. Here's the one-paragraph story that ties it together:

Because the model is stateless (Session 1), a thread stores the full conversation and re-sends it each turn so the agent appears to remember. To see what the agent did, you turn on tracing, which records a trace made of spans โ€” every model call and tool call with its inputs, outputs, timing, and tokens. When something goes wrong, you debug by walking the trace backwards to the first step where good input produced bad output (recall the failure modes from Session 3). Across many runs, observability โ€” logs, metrics, and traces, watching latency, token cost, and error rate โ€” tells you the health of the whole system. And when a real user hits a bug, a disciplined failure investigation using the saved trace takes you from "it gave a wrong answer" all the way to the true root cause โ€” which, as we saw, is often the data, not the model.

Quick self-check

The model is stateless, so how does an agent "remember" earlier turns?

It doesn't โ€” the thread does. The thread stores the full conversation history and the SDK re-sends it to the model every turn, creating the illusion of memory.

What's the difference between a span and a trace?

A span is the record of one operation (one model call or one tool call) with its input, output, timing, and status. A trace is the whole nested tree of spans for one complete agent run.

Why aren't print statements enough to debug an agent?

Agents are non-deterministic (bugs appear intermittently), they loop and branch, the data is huge, you need timing/token info, and bugs happen in production where you can't add prints after the fact. Tracing handles all of that automatically.

When debugging, where in the trace is the real bug usually located?

At the first span where a good input produced a bad output. Walk backwards from the final answer; the symptom is at the end, but the root cause is upstream. Never patch the symptom.

An agent gave a wrong answer that looked like a hallucination. The trace showed the final model call reasoned correctly. Where was the bug?

Earlier in the run โ€” in our case study it was bad retrieval (a stale policy document). The model reasoned correctly from wrong facts. The fix was in the data, not the model โ€” which only the trace could reveal.

Name the three key metrics to watch for an agent in production.

Latency (how slow), token cost (how expensive), and error rate (how often it fails). Metrics raise the alarm; traces explain the cause.

๐Ÿ“š References & Further Reading


Class material

Papers, docs & deep dives