AIAI EngineerJun 29, 2026· 14:10

Your Agent Failed in Prod. Good Luck Reproducing It. - Tisha Chawla & Susheem Koul, Microsoft

Tisha Chawla and Susheem Koul of Microsoft argue that debugging production agent failures requires replayability, not bitwise determinism. They illustrate with a stock-selling agent that sold 1,000 shares instead of $1,000 worth due to a reasoning error, a problem temperature-zero decoding cannot fix because GPU non-determinism and batch invariance cause varied outputs. Instead of freezing model output, they propose recording every state transition at method boundaries using their open-source tool Chronicle. Chronicle annotates nodes to capture inputs, outputs, and metadata like model version, then enables deterministic replay by stubbing LLM calls during testing, letting engineers fix guardrails and verify with the same trace. They distinguish deterministic testing (for tools and guardrails) from behavioral testing (e.g., LLM-as-a-judge), advocating for replays over chasing API determinism to make on-call cycles bearable.

Transcript

The Problem0:00

Tisha Chawla0:01

Imagine something your agent didn't prod was wrong. It called the wrong tool, it wrote the wrong thing, and now suddenly your team is on on-call rotation to figure out what actually went wrong. Pretty common,right? Now, as per standard engineering response, your gut will tell you to pull the raw prompt from the telemetry logs, pass it to the same model using the same prompt, and run it locally to isolate the bug.

Which we'll all do. And surprisingly, it will work as well. Run it again. It will work again. You'll run it 10 more times. It will be just perfect every time. But now let's talk about that one run which costed you, and that will be gone.

You can't reproduce it. And if you can't reproduce it, you can't debug it. And if you can't debug it, you can't promise it won't happen to your next customer or user,right? Now, I am Tisha. I have Sushin with me as my co-presenter.

We both run agents against real production backends. You know, the kind of place where a bad write isn't "oh well, run it again." It's you on a call with a customer explaining where the data actually went. This whole talk is going to be about that one thing: you lose the second an agent goes hay-buy in production without being able to reproduce it.

That will be a North Star for the next 10 minutes to follow. Now let's look at how this actually blows up. You've got an agent hooked to a broker API, which is the scenario I'm taking. The user says, "Hey, sell $1,000 of stock."

Now comes the interesting part. Instead of doing the math, the agent sells the raw number 1,000 and dumps it straight into the quantity field. Guess what? It sells 1,000 shares instead. Now, at 190 bucks a share, a $1,000 in 10 will become how much?

A $190,000 disaster,right? And the terrifying part is that the API, or my infra, returned a clean 200 OK in 30 milliseconds. We got zero exceptions, zero alerts. If you see the trait, it's completely wrong. But your dashboards are sitting there, perfectly green, perfectly flawless.

When such a scenario, as we last discussed, comes up, what's the first thing which you will do to try and fix this? The reflex here is to, you know, just turn the model temperature down to absolute zero. Assuming greedy decoding will make everything deterministic,right?

Misconceptions2:27

Tisha Chawla2:45

But that's a complete misconception. Setting the temperature to zero doesn't fix a broken reasoning path. It just means the model is going to make the exact same logical error, the exact same way, at the exact same time, and honestly, even worse than that.

To back up the scenario we just discussed, look at the engineering threads on Reddit and Hacker News. The hard data shows that temperature zero isn't even truly deterministic on a hardware level. Running the same prompt a thousand times can still return dozens of completely different responses, just due to the underlying GPU non-determinism and the MoE architectures which are there.

So to understand why this actually happens, we'll have to look at it from first principles. It comes down to four simple things. One, sampling determinism isn't system determinism. Temperature zero just means "always take the arg max," but it doesn't guarantee that the underlying scores stay identical run to run.

First Principles3:33

Tisha Chawla3:56

Two, floating point math isn't associative. The order you add your decimal matters,right, but a tiny shift in matrix operation alters the final logit, which in turn will flip the winning token. Three, it's not a concurrency issue. Run the same matrix multiplication alone on a GPU a thousand times, and I'll guarantee you'll get the exact same bits.

So the real culprit is batch invariance here, because your request gets grouped with whatever else hits the server that millisecond. Four, mixture of experts. Routing has the exact same bottleneck. Experts have strict capacity limits. If a batch overflows a specific subnetwork, tokens get rerouted.

Whether the token makes the cut depends entirely on the traffic you got batched with. So the ultimate takeaway here is that chasing text output is a losing battle, completely. We don't need the model to return the exact same token back every time.

We just need our system to execute the exact same state transition. Which means we've been asking the wrong question all along,right? The wrong question is, "How do I make the model deterministic?" And I've seen teams burning weeks on that and walk away deciding the system's just unknowable.

Replayability5:03

Tisha Chawla5:23

Theright question is, "How do I debug and retest a run I can't reproduce?" Because determinism was never the North Star. Debugging was. Two words we keep mixing up, which I'll talk about now, is bitwise determinism and replayability. Bitwise determinism is: same input, same output.

That's controllability. You're not getting it from a hosted API, and you don't actually want it. Because the randomness is what makes the model good. It buys the model explores more, and you'll get more creative answers. The other one is replayability, which is: rebuild a run that already happened, well enough to debug it.

That's observability. You don't need the model deterministic; you need the run recorded. And you don't freeze the model; you capture what it did. Now the question which we are all thinking about is, where do you record? For sure, not at the network layer, because half your agent will never touch the network.

Recording6:04

Tisha Chawla6:26

The local retrieval, the in-process tools, the memory, and the parts that do not shred under streaming and async. Record at the boundary instead, because you'll need to capture what enters each node and what leaves it. The meaning of each step, and not the packets.

What replay adds here is a deterministic CI where you stuff the model, you'll rerun the exact failure offline with zero model calls. Let's talk about the loop end to end now. It starts with annotation, recording, visualization, understanding, fixing, then the part we're working on which is replaying, and finally verifying.

Allright, let's now see how to bring the workflow we just discussed into action.

Chronicle Demo7:15

Susheem Koul7:15

So we've established that replayability is a core tenet of productionizing any AI agent. But how do we build this in code? As a proof of concept for this, what we have done is we have built something called Chronicle.

At the heart of Chronicle lies the concept of a boundary. Think of a boundary as a bounding box around any node in your agentic workflow. A node can be a tool call, it can be a call to an LLM, or a retrieval from a RAG.

It doesn't matter. As long as it's a method, it can be annotated with the boundary annotation. Now, what does this annotation do? It ensures that anything that goes into the method and comes out of the method gets recorded.

So any input and output pair will get recorded. On top of that, you can define parameters like your model version, or the version of the code that is running, so that the entire state during which the agent run happened gets frozen and saved as a trace.

Now, let's see this in action. We've been talking about this stock-selling agent which went hay-buyer in production. This is a representation of the same. You have your initial planning step, which takes into account the user input. It can use the place order tool to do the actual selling and buying of the stocks.

And then finally, it delegates to the finalize agent, which generates a succinct response for the end user. We have annotated all three of these methods with the boundary annotation. The first one is a tool, and the second and the third one is an LLM.

Let's run this.

So here's what happened. You gave a user request to sell about $1,000 worth of ACME stocks. You have the three nodes. You have the input and the output recorded for every one of them. You can see that the LLM mistook the 1,000 as the quantity and generated a tool call of place order with the symbol ACME and quantity 1,000.

And the place order tool obviously executed this input and sold $1,000, or 1,000 units of ACME stock at $190 per piece. This is where the problem started. Now you have a trace for it, but is this all you can see?

No. We record much more details than this. So you can go and see the hyper-detailed JSON for each of the nodes that the call went into or went out of. You can see the metadata, like the model version, the sampling versions that was there in the LLM call.

You can see the input and the output. For example, this is for the place order tool. You can see the input went in as a cell of 1,000 quantity on ACME, and the output was obviously that it sold that quantity.

And if you go one step back, you can see the agent one node creating that tool call that caused this entire problem. It created a tool call to place order with the symbol ACME and quantity 1,000. Now, this is all good.

Testing9:53

Susheem Koul9:53

You have a recording, you have a trace, you figured out what the problem is. Now, what do you do with it? You figured out that the problem is the LLM created a wrong tool call. You cannot control the LLM.

That's what this entire discussion is about. You cannot enforce bitwise determinism. What you can do is you can put guardrails on your tools to enforce some level of credibility on your production agent. But how do you test this?

Once you have built the guardrails, how do you test it? That is another tenet that boundary offers. Since the boundary annotation is already providing a bounding box around your methods, it can be used to stub your methods during testing.

So think of it like this: you have a run which is recorded. That run recorded every input and output for every node. Now you have fixed your code at, let's say, the tool level, but you want the rest of the nodes to be stubbed so that the entire exact stack trace remains the same.

How do you do it? You run a test suite with the same trace that was recorded earlier. You stub every node other than the node that you changed, and you let boundary handle the rest,right? So let's see this one in action.

This is your test case. You have loaded the trace, and you have enabled the replay mode on boundary, which allows boundary to stub every node that you want. So for example, in this case, you want to stub the first agent call which generated the tool output.

But you want the tool to be run live so that you can test out your code changes. Once you do this, you run your agent. Now, another good thing about boundary is that since it's already capturing the output of your tool as well, you can use it to run assertions.

So you can take the output and assert that this time around, the tool call got blocked. So let's run this once.

Perfect. So you can see that agent one, which was the LLM, was stubbed. You can see the same input-output as the recorded trace, but the tool and the LLM ran live. And you can see that for the tool, this time the output is blocked.

And your assertion on the tool has passed because the order is blocked. So this is the power of merging your replayability traces with auto-generated testing and stubbing and assertions.

So we just saw how Chronicle not only records your agentic sessions, but it also uses those recordings as test cases. Now, when we talk about testing in AI agents, I want to draw a very clear distinction here. There are two ways of testing AI agents, and both of them are equally important.

Testing Types12:10

Susheem Koul12:25

There's the deterministic testing, and then the behavioral testing. The deterministic testing applies to, obviously, the deterministic nodes of your agent graph. This could be your guardrails or your tool calls. Now, this is exactly where Chronicle shines. Because, as we just saw, Chronicle freezes the entire agent run as a context.

So you can use the LLM node's context to stub the LLM outputs. This essentially kicks the probability out of the window, and your entire agent run can become a test case. This is rerunable, and since it never calls the model, it is free.

On the behavioral side of things, you measure things like the tone of the agent, or whether the trajectory it took wasright. This is more subjective, and this is where techniques like LLM as a judge are better off. Now, at this point, I want to talk about the key takeaways.

Takeaways13:11

Susheem Koul13:11

First, stop chasing bitwise determinism through the API. The fundamental principles on which the APIs are built today do not make this possible. Second, know what are variables for your session. For example, your LLM version, or your build ID, or your RAG chunks, and make sure that you are logging these.

Third, capture the full envelope. Don't focus on just the prompt. There are a lot more ingredients that go into that final response. Fourth, use the replays to debug. Make sure that you find the issues, fix the failures, and then finally use the same trace as a test case.

Fifth and final, keep the generation time variation alive. Don't try to pin the temperature to zero. After all, that is what brings the agency into your agent. So the QR on your screen, you can scan that to get access to the code for Chronicle and a bunch of nicely written articles.

And finally, thanks again for your time, and we hope that the traces that you put today in your agent make sure that your on-call cycle tomorrow is much better. Thank you.