
<p><b>Key facts.</b></p>
<ul>
<li>Retries are "selfish": each client improves its own odds at the expense of the shared resource, so uncoordinated retries can keep a dependency down long after the original fault clears (AWS, <a href="https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/" target="_blank" rel="noopener">Timeouts, retries, and backoff with jitter</a>).</li>
<li>The standard damping is exponential backoff plus jitter, which spreads retries out in time so callers do not synchronize into repeated load spikes (AWS Builders' Library, same source).</li>
<li>Retry amplification compounds across layers: if three stacked services each retry three times, one user request can become many calls at the bottom, which is how cascading failures form (Google SRE, <a href="https://sre.google/sre-book/addressing-cascading-failures/" target="_blank" rel="noopener">Addressing Cascading Failures</a>).</li>
<li>The circuit breaker pattern stops calls to a failing dependency for a cooldown, giving it room to recover instead of being hammered by retries (Nygard, <a href="https://pragprog.com/titles/mnee2/release-it-second-edition/" target="_blank" rel="noopener">Release It!</a>, 2nd ed.).</li>
</ul>
<h2>Why do retries spiral instead of helping?</h2>
<p>A retry is just another request. The thing that is failing is usually failing because of load. When a dependency slows, every caller hits its timeout at roughly the same moment and retries. That doubles or triples the traffic hitting an already-struggling service. Which pushes latency up further. Which trips more timeouts. Which produces more retries. The recovery logic has become the load. Retries are selfish: each client does the locally rational thing while collectively keeping the dependency pinned down. The original fault might have lasted two seconds. The retry storm it triggered can last until someone sheds load by hand.</p>
<h2>Why is this worse for an agent?</h2>
<p>An agent's retry is not a cheap network call. It is another full model invocation. When a tool call returns a rate-limit error or times out, a naive agent loop tries again. Each attempt re-sends the growing context and burns more tokens. So an agent retry spiral hits two resources at once: load on the failing dependency and a climbing token bill. Worse, agents often retry things that are not transient at all, like a malformed request the model keeps reproducing. The loop never naturally ends. A blip that a well-behaved client would ride out becomes, for an unbounded agent, an open-ended drain on both the dependency and your budget.</p>
<div class="fig"><img src="/blog/article10-diagram.png" alt="A four-stage feedback loop: dependency slows, callers time out, callers retry, load rises, returning to the start"/></div>
<h2>What turns a small blip into a full outage?</h2>
<p>Synchronization and no limit. If every caller retries on a fixed schedule, their retries line up into repeated waves in lockstep, a thundering herd. Add layered retries, where each service in a chain retries the one below it, and the call volume multiplies: three retries times three layers is nine calls at the bottom. With no retry budget and no circuit breaker, nothing in the system is allowed to say "stop trying for now." That is the difference between a two-second hiccup and an hour-long incident. Not the size of the original fault, but whether the recovery traffic was bounded and spread out, or unbounded and synchronized.</p>
<h2>How do you stop the spiral?</h2>
<table style="width:100%;border-collapse:collapse;margin:1.5rem 0;font-size:0.97rem;"><tr><th style="text-align:left;padding:10px 12px;border-bottom:2px solid #e5e7eb;color:#5E6AD2;font-weight:600;">Control</th><th style="text-align:left;padding:10px 12px;border-bottom:2px solid #e5e7eb;color:#5E6AD2;font-weight:600;">What it does</th><th style="text-align:left;padding:10px 12px;border-bottom:2px solid #e5e7eb;color:#5E6AD2;font-weight:600;">How</th></tr>
<tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Backoff with jitter</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">De-synchronizes retries</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Wait exponentially longer each attempt, plus a random offset, so callers do not retry in lockstep</td></tr>
<tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Retry budget</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Caps total retry load</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Allow retries only up to a small fraction of normal traffic; beyond that, fail fast</td></tr>
<tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Circuit breaker</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Gives the dependency room</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">After repeated failures, stop calling it for a cooldown, then probe before resuming</td></tr>
<tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Cap attempts</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Guarantees an exit</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">A hard max-retry count, then escalate instead of looping</td></tr>
<tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Idempotency</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Makes retries safe</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Keys so a repeated call cannot double-charge or double-write</td></tr>
</table>
<p>Retries are not free safety. They are load you are choosing to add at the worst possible moment, so they have to be governed. Spread them out with jitter. Cap them with a budget and a hard limit. Put a circuit breaker in front of anything that can get overwhelmed, so a struggling dependency gets room to recover instead of a beating. For agents, add the token dimension: treat a retry as a spend decision, not a reflex. Which failures in your workflow are genuinely transient and worth one bounded retry, and which are dead ends to escalate immediately, is the pattern-level reliability VibeModel builds as the Pattern Intelligence Layer.</p>
<h2>Frequently asked questions</h2>
Can a newer model retry its way out of a spiral?
Retries become the load; a newer model spends tokens faster, backoff is the floor. (aws.amazon.com)
What is a retry death spiral?
It is when retries become the dominant load on a failing dependency, so the recovery traffic keeps it down. A slow service triggers retries, the retries add load, the load slows it further, and the loop feeds itself until something external breaks it.
Aren't retries supposed to improve reliability?
Only when they are bounded and spread out. Uncoordinated retries are "selfish": each helps one caller while collectively overwhelming the shared resource. Backoff with jitter, a retry budget, and a circuit breaker are what make retries safe.
Why is a retry spiral expensive for AI agents specifically?
Because each agent retry is a full model call that re-sends context and spends tokens. So a spiral hits two resources at once: load on the failing tool and a climbing token bill, and unbounded agents often retry non-transient errors that never resolve.
What is the single most important fix?
A circuit breaker plus a hard retry cap. The breaker stops hammering a failing dependency so it can recover, and the cap guarantees the loop exits and escalates instead of running forever. Add backoff with jitter so retries never synchronize.

