Back to Blog
Published:
Last Updated:
Fresh Content
Multi-Agent Systems in ProductionChapter 7

Your Retry Just Sent the Email Twice

13 min read
2,765 words
high priority
Muhammad Mudassir

Muhammad Mudassir

Founder & CEO, Cognilium AI

Your Retry Just Sent the Email Twice

TL;DR

A single model call fails atomically, so retrying the whole call is safe: there is no partial state to corrupt. A multi-agent run is a sequence of model and tool calls with no transaction around it, so it fails in the middle, after some agents have already committed real side effects like a sent email, a written row, or a submitted filing, and there is no rollback. This inverts the usual fix: a blind retry replays the steps that already succeeded and fires their side effects again, so the retry is not recovery, it is a second bug. Reliability in a multi-agent system is structural, at the action boundary, not a wrapper around the run. Make every side-effecting tool idempotent by giving it an idempotency key derived from the run and the logical action, so a replay returns the first result instead of acting again. Checkpoint each completed step to durable storage so a retry resumes at the failed step instead of re-running the whole pipeline and paying every token twice. For the steps you cannot make idempotent, a third-party charge or an irreversible external submit, define a compensating action, the saga pattern, because there is no rollback, only a forward action that cancels a prior one. And classify failures before retrying, because a timeout is transient and worth retrying while a schema violation is deterministic and will fail identically every time. Across a run of fourteen calls at a one percent failure rate, about one run in eight lands in a partial state, and what decides your reliability is not whether that happens but what a retry does when it does.

You secured the tool boundary against an attacker. Now the tool fails on its own, with no attacker in sight: a model call times out halfway through the run, a retry kicks in, and the send-email tool that already fired once fires again. A single model call fails atomically, so retrying the whole call is safe. A multi-agent run is fourteen calls with no transaction around them, so it fails in the middle, after some agents have already sent the email or written the row, and there is no rollback. A blind retry replays the steps that succeeded and duplicates their side effects, so the retry is not recovery, it is a second bug. What works is structural, at the action boundary: make every side-effecting tool idempotent with a key so a replay is a no-op, checkpoint each step so a retry resumes instead of restarts, and compensate the steps you cannot make idempotent. Across fourteen calls, one run in eight lands partial. The failure rate does not change. The cost of a failure does.
multi-agent reliabilityagent reliabilityidempotencyidempotency keyagent retriessafe retryretry with backoffpartial failureagent failure handlingagent error handlingcheckpoint and resumerun checkpointingsaga patterncompensating actioncompensating transactionexactly onceat least onceduplicate side effectsside effect deduplicationtransient vs deterministic failureagent fault toleranceproduction agent reliabilityagentic AI reliabilitymulti-agent failure recoverytool boundary guardrail

A single model call fails all at once, so you retry it and move on. A multi-agent run fails in the middle, after it has already sent the email, and the retry sends it again.

The last chapter was about an attacker reaching the tool: a poisoned document riding the hand-offs until it reached the agent that could send email, and making it fire on the wrong instruction. This chapter is the same tool boundary with no attacker in sight. Nothing was poisoned. The pipeline did exactly what you built it to do, and it still went wrong, because halfway through the run a model call timed out, a retry kicked in, and the send-email tool that had already fired once fired a second time. The security chapter worried about the tool doing the wrong thing. This one is about the tool doing the right thing twice.

Here is the uncomfortable part. The reflex that keeps a single-call system alive is a retry with backoff, and it is correct there, because a single model call fails atomically: it either returns or it does not, there is no half-finished state, and retrying the whole thing is safe. That reflex is not just useless in a multi-agent system, it is the bug. A multi-agent run does not fail atomically. It fails in the middle, after some agents have already committed real, irreversible actions, and a retry that replays the run replays those actions. The thing you added to make the system reliable is the thing that duplicates the side effect.

TL;DR

A single model call fails atomically, so retrying the whole call is safe: there is no partial state to corrupt. A multi-agent run is a sequence of model and tool calls with no transaction around it, so it fails in the middle, after some agents have already committed real side effects like a sent email, a written row, or a submitted filing, and there is no rollback. This inverts the usual fix. A blind retry replays the steps that already succeeded and fires their side effects again, so the retry is not recovery, it is a second bug. Reliability in a multi-agent system is structural, at the action boundary, not a wrapper around the run. Make every side-effecting tool idempotent by giving it an idempotency key derived from the run and the logical action, so a replay returns the first result instead of acting again. Checkpoint each completed step to durable storage so a retry resumes at the failed step instead of re-running the whole pipeline and paying every token twice. For the steps you cannot make idempotent, a third-party charge or an irreversible external submit, define a compensating action, the saga pattern, because there is no rollback, only a forward action that cancels a prior one. And classify failures before retrying, because a timeout is transient and worth retrying while a schema violation is deterministic and will fail identically every time, so retrying it is a slower way to fail. The failure rate of any one call is small. In a run of fourteen calls it compounds into roughly one run in eight landing in a partial state, and what decides your reliability is not whether that happens but what a retry does when it does.

A single call fails all at once. A multi-agent run fails in the middle.

In a single model call the failure model is simple. The call returns a result or it raises an error, and there is no third state. Nothing happened halfway. So the recovery is the simplest thing in distributed systems: retry the whole call, maybe with backoff, and you are done. You can retry it five times and the worst case is five identical attempts, because a call that failed did not leave anything behind.

A multi-agent run has no such property, and the reason is that there is no transaction wrapping it. Take the same six-agent extraction pipeline this series has used throughout: a planner, a retriever, two extraction workers, a reconciler, and a reviewer that holds the two tools that touch the outside world, finalize to the system of record and send email. A run walks through those agents in sequence, and every one of them is a separate model call, several of them making separate tool calls. There is no COMMIT at the end and no ROLLBACK on failure, because the run is not a database transaction. It is fourteen independent calls in a trench coat. When the reviewer sends the client email and then the finalize call times out, the run has failed, but the email is already gone. Half the side effects are real and permanent. The other half never happened. The run is not failed or succeeded. It is stuck exactly halfway, and nothing in your stack knows how to feel about that.

The retry is the trap

The instinct is to wrap the run in the same retry you would wrap a single call in. A run failed, so run it again. In a stateless call that is exactly right. Here it replays the planner, the retriever, both extraction workers, the reconciler, and then the reviewer, and the reviewer sends the email a second time. You did not recover the run. You duplicated its side effect. The client now has two identical emails, or two filings, or two rows in a system of record that a downstream process will read as two separate events.

It is worse than a single accidental double, because retries hide in every layer of the stack and none of them coordinate. The model SDK retries on a 429. The job queue redelivers a message it did not see acknowledged. The load balancer times out and the client library retries the request. The user, watching a spinner that never resolves, hits the button again. Each of these is a reasonable local decision, and stacked on a run that takes irreversible actions, each one is another copy of the email. The naive answer, turn off retries, is not an option either, because transient failures are real and a run that never retries anything simply fails more. You cannot remove the retry. You have to make the thing it replays safe to replay.

Make the side effect idempotent

The fix lives at the same action boundary the security chapter put guardrails on, and it is the same shape of fix: deterministic code at the tool, not a model's judgment. Every side-effecting tool takes an idempotency key, a string derived from the run and the specific logical action it is performing, and the tool deduplicates on that key. The send-email tool is called with a key of, say, a hash of the run identifier, the recipient, and the message template. The first call with that key sends the email and records the key with its result. The second call with the same key does not send anything. It finds the recorded result and returns it. The retry still happens, the reviewer still calls send-email, and the client still receives exactly one email.

This is the single highest-leverage move in the chapter, because it converts a retry from a liability back into the safe primitive it is supposed to be. Once every side effect is idempotent, you can retry the whole run as many times as you like and the outside world sees each action exactly once. A run retried three times sends one email, not three. The key has to be derived from the logical action, not generated fresh per call, or you have defeated the entire mechanism: a new random key every attempt is the same as no key at all. And the dedup has to live server-side, at or behind the tool, because a check the agent performs in its own reasoning is exactly the check a replay will perform again and pass again.

Checkpoint the run so a retry resumes, not restarts

Idempotency makes a replay safe. It does not make it free. If the run fails at the reviewer, the sixth agent, a blind retry still re-runs the planner, the retriever, both extraction workers, and the reconciler before it gets back to the point of failure, and every one of those is a fresh model call you pay for again. The observability chapter measured a single run of this pipeline at around 41,000 tokens. Re-running five of its six agents to recover from a failure in the sixth pays roughly five sixths of that bill, tens of thousands of tokens, to redo work that already succeeded.

The fix is to checkpoint. As each agent completes, write its output to durable storage keyed by the run identifier and the step. On a retry, before running a step, check whether its checkpoint already exists, and if it does, load the output and skip straight to the next step. The retry resumes at the reviewer instead of restarting at the planner. It pays for the one failed step and its tail, a few thousand tokens, instead of re-running the whole pipeline. The checkpointed outputs are the same notes the agents pass each other from the hand-offs chapter, with one addition: they are written down where a second attempt can find them, so the second attempt inherits the first one's work instead of repeating it. Idempotency keeps a retry correct. Checkpointing keeps it cheap. You want both, because a correct retry that costs a full run every time is a reliability fix you will be tempted to turn off the first time the token bill arrives.

A diagram of why a multi-agent run cannot be made reliable with a retry, and what to do instead. The headline: you cannot retry your way out of a partial failure, because a single call fails all at once so a retry is safe, but a multi-agent run fails in the middle so a blind retry fires the side effect again. Across the top, the mechanic in three boxes: the run fails mid-way, where the reviewer already sent the email and then the finalize call timed out, then the blind retry replays steps one to five and reaches send email again, then the outcome is duplicated, the email sends twice and no metric flags it. Below on the left, four reasons a run fails in the middle: there is no transaction, so fourteen calls run with no COMMIT and no ROLLBACK; it fails in the middle, so some side effects are real and some never ran; the retry replays it, so it re-sends the email that already went out; and one run in eight lands partial, because fourteen calls at a one percent failure rate each leave about thirteen percent of runs with at least one failure. On the right, a table showing the fix changes the cost of a failure for the same run retried after a mid-way failure: emails per three retries fall from three to one, tokens to recover fall from about thirty-four thousand to about four thousand, and duplicate actions fall from three to zero, because the tool deduplicates on an idempotency key and a checkpoint lets the retry resume instead of restart. The fix band: the fix is at the action boundary, not around the run, so make every side-effecting tool idempotent with a key so a replay is a no-op, checkpoint each step so a retry resumes rather than restarts, compensate the steps you cannot make idempotent, and classify failures so you retry the transient and fail fast on the deterministic. The takeaway: a single call fails all at once so a retry is safe, but a multi-agent run fails in the middle after it has touched the outside world, so a blind retry is the second bug.

Some steps cannot be made idempotent. Compensate them.

Not every side effect has a key you control. An idempotency key works when the tool, or the service behind it, will honor it and dedup on your behalf. Plenty of real actions will not. A third-party API that charges a customer's card, a government portal that accepts a filing, a partner system that dispatches a shipment: some of these expose no idempotency mechanism, and some take an action in the physical world that a later call cannot make un-happen. For those, replay-safety is off the table, and you need the other tool.

That tool is the compensating action, the core of what distributed systems call a saga. You model the run as a sequence of steps where each step that takes an irreversible action is paired with a second action that cancels its effect: the charge is paired with a refund, the filing with a retraction, the dispatch with a recall. You record, durably, which steps actually completed. When the run fails partway, you do not roll back, because there is nothing to roll back to. You run the compensators, in reverse order, for exactly the steps that completed. This is the crucial mental shift. A rollback is the system pretending the action never happened. A compensation is a new, forward action that admits the first one happened and undoes its effect. The charge was real. The refund is also real. Both show up in the ledger, and that is correct, because both are things that actually occurred. Reserve compensation for the steps that genuinely cannot be made idempotent, because it is more machinery than a key, and prefer idempotency wherever the tool will let you have it.

Not every failure should be retried

Retrying assumes the failure was bad luck. Often it was not. A rate limit, a socket timeout, a transient 503 from a model provider: these are worth retrying, because the same call a moment later has a good chance of succeeding. A malformed structured output that fails schema validation, a tool that returns "recipient address is invalid," an argument the model got wrong: these are deterministic. The exact same call will produce the exact same failure every time, so a retry is not a second chance, it is the identical mistake at a later timestamp, burning tokens and latency to arrive at the same error. This is the brakes chapter's loop guard wearing a different hat: retrying a deterministic failure is a two-iteration version of the loop that never makes progress.

So classify before you retry, at the tool boundary where the error is raised. Transient failures get a bounded retry with backoff. Deterministic failures fail fast, stop the run, and escalate to a human or a repair step, because no amount of retrying will change the answer. A system that retries everything the same number of times treats a network blip and a schema violation identically, and pays full retry cost on the half of failures that were never going to resolve. Knowing which kind of failure you are looking at is the difference between a retry that recovers and a retry that just delays the inevitable.

Do the math on the failure surface

The reason this matters more in a multi-agent system than a single call is not that the individual calls are less reliable. It is that there are so many more of them, and a run needs all of them to land. Take the run concretely: six agents, each making one model call, plus roughly eight tool calls spread across them, so about fourteen independent calls that all have to succeed for the run to complete cleanly. Give each call a modest one percent chance of a transient failure. The probability that all fourteen succeed is 0.99 to the fourteenth power, which is about 0.87. So roughly thirteen percent of runs, better than one in eight, hit at least one failure somewhere in the chain.

Compare the single call: one call at one percent is a one percent failure rate, and a safe retry clears it. The multi-agent run is about thirteen times more likely to land in a partial state per run, purely from having fourteen calls instead of one, and every one of those partial states is a live question, does a retry duplicate a side effect or resume cleanly. At a thousand runs a day, thirteen percent is about a hundred and thirty runs landing halfway every day. Without idempotency and checkpointing, each of those is a potential double-sent email or a full and expensive re-run. With them, each is a cheap resume that the outside world never notices. Notice what the fix changed and what it did not. The failure rate is still thirteen percent, because you did not make the calls more reliable. What you changed is the cost of a failure, from a duplicated irreversible action down to a few thousand tokens of replay. In a system that fails one run in eight, that is the number that decides whether you can run it in production.

A single call fails all at once, so a retry is safe. A multi-agent run fails in the middle, after it has already touched the outside world, so a retry is the second bug. Reliability is not adding retries. It is making the thing a retry replays safe to replay.

When you do not need any of this

The honest caveat, as in every chapter of this series. If no agent in your run takes an irreversible side effect, if the whole pipeline only reads, reasons, and returns text to a person who decides what to do with it, then a failure just means run it again, and a blind retry is perfectly safe because there is nothing to duplicate. A pure analysis pipeline that never sends, writes, files, charges, or submits does not need idempotency keys or sagas, and the single-agent baseline is again the cheapest thing that could possibly work. The entire apparatus earns its cost at one specific moment: when an agent takes an action that touches the outside world and cannot be un-taken. For the document-heavy systems this series is about, systems that file, notify, and write to a client's system of record, that moment is the whole point of building them. You cannot tell a filing pipeline to stop filing. You can only make sure that when it fails halfway, and one run in eight will, it does not file the same thing twice.

We build production multi-agent systems for document-heavy work in legal, finance, and insurance, where agents file, notify, and write to systems of record, so a run that fails halfway is a real filing with real liability, not a lost request you can quietly retry. If your agents take any action that touches the outside world and cannot be un-taken, the partial-failure path is already there, whether or not it has bitten you yet. Talk to an engineer or see how we work.

Share this article

Muhammad Mudassir

Muhammad Mudassir

Founder & CEO, Cognilium AI | 10+ years

Mudassir Marwat is the Founder & CEO of Cognilium AI. He has shipped 100+ production AI systems acro...

Founder & CEO of Cognilium AI; 50+ projects delivered with 96% client satisfaction; 4 production AI products built and operated; multi-cloud AI architecture (AWSGCPAzure)
Agentic AIRAG → GraphRAG retrievalVoice AIMulti-Agent Orchestration

Frequently Asked Questions

Find answers to common questions about the topics covered in this article.

Still have questions?

Get in touch with our team for personalized assistance.

Contact Us

Related Articles

Continue exploring related topics and insights from our content library.

Your Multi-Agent System Has No Brakes
9 min
1
Muhammad Mudassir
July 7, 2026

Your Multi-Agent System Has No Brakes

You wired the agents and gave them a shared place to coordinate. Now, what stops them? In most multi-agent systems no single component owns the decision of what runs next and when to stop, so control is emergent, and emergent control does not reliably halt. It fails four ways: non-termination, premature termination, mis-routing, and a runaway cost tail where the median run is fine but the one-in-fifty tail run costs twenty to fifty times more. This chapter builds the brakes: a termination predicate the system checks instead of the agent's self-report, a hard global budget that bounds worst-case cost, one controller that owns routing, and loop detection that reads the shared store. Architecture is the engine. Control is the brakes.

words
Read Article
Your Multi-Agent System Is a Black Box
11 min
2
Muhammad Mudassir
July 8, 2026

Your Multi-Agent System Is a Black Box

You gave the agents brakes in the last chapter. Brakes stop a runaway, but they do not tell you which agent is dragging, what a run costs, or why last night's batch tripled. Most multi-agent systems ship to production as a black box: the team can see that a run happened and returned, and nothing in between. Worse, agent failures are silent, they return a 200 OK with a confident wrong answer, so uptime and error rate read green while the output is wrong. This chapter builds the instrument panel: the trace tree as the unit of observability, per-span cost attribution that finds the one agent owning two-thirds of the bill, structural sampling that keeps every run worth investigating, and continuous semantic evaluation, the only signal that catches a wrong answer no system metric will flag. Brakes stop a runaway. Instruments show it coming.

words
Read Article
One Poisoned Agent Poisons the Chain
12 min
3
Muhammad Mudassir
July 9, 2026

One Poisoned Agent Poisons the Chain

You gave the agents brakes, then instruments. Neither stops a poisoned document from turning the pipeline against you. A single model call has one trust boundary, trusted system prompt versus untrusted input; a multi-agent system erases it, because every agent's output becomes the next agent's trusted input. So an injection planted in one document read by one agent propagates through the hand-offs to the agent that can send email or write to a system of record. The lethal trifecta, untrusted content, private data, and the ability to communicate out, gets split across your agents and recombined by the chain, so a per-agent review clears every agent and still misses the exploit. You cannot filter your way out. What works is structural: least privilege so the reader holds no dangerous tool, quarantine untrusted content behind a tool-less model, and a deterministic guardrail on the dangerous action. Tracing shows the attack. It does not stop it.

words
Read Article

Explore More Insights

Discover more expert articles on AI, engineering, and technology trends.