An agent that works in testing and breaks in production is often not failing at all. It is running more than once.
Testing calls it once, synchronously, and someone reads the answer. Production puts it behind a queue, a worker, a timeout and a retry policy. That stack is at-least-once by construction. A retried read does no harm. A retried agent turn is a second decision, and the side effects come attached: a record written, a notification sent, a provider called, a refund issued.
Two mechanics make this worse than ordinary retry risk.
First: the model is non-deterministic, so attempt two is not a repeat of attempt one. It is a new decision, made on a world the first attempt already modified.
Second: agents fail mid-plan. Steps one through three landed, the worker died on four, and the retry starts back at step one. Nothing threw. The plan just happened one and a half times.
The discipline here is the one payment systems already use. Derive an idempotency key from the triggering event, not from the model's output (the output changes on every attempt, so hashing it gives you a fresh key and no protection at all). Record each tool call with its key before you execute it, and on replay return the recorded result instead of calling again. Treat the agent's plan as a durable log of attempted steps, not a conversation you throw away.
The determinism you need was not going to come from the model. It comes from the step log around it.
So it is worth knowing where the retry boundary sits in your agent: before the model call, or after the side effect...