Back to blog
July 11, 2026·5 min read·1,245views

Your Agent Should Never Retry a Tool Call

Parvesh Sainiby Parvesh Saini
Your Agent Should Never Retry a Tool Call

Every backend engineer carries the same reliability toolkit: timeouts, retries with exponential backoff, jitter. It's muscle memory. A request fails, you try again. That's how you turn a flaky network into a dependable system.

Then you build agent infrastructure, apply the same reflex to tool calls, and quietly create a system that sends duplicate emails, books duplicate meetings, and double-charges customers, while reporting success.

Timeouts are not failures

The retry reflex rests on an assumption so ingrained we forget it's an assumption: a failed request did nothing. For an idempotent GET, fine. For a tool call, you usually have no idea what happened.

A timeout means you didn't get the response. It says nothing about whether the upstream did the work. The MCP server that took 31 seconds to answer send_invoice may have sent the invoice at second 4 and stalled writing the confirmation. Retry it and the customer gets two invoices. Retry twice (backoff and all, very professionally) and they get three.

Classic distributed-systems theory, nothing new. What's new is where the writes live. In a REST API, side-effecting endpoints are a minority you can enumerate and protect. In an agent's tool catalog, side effects are the point. Agents exist to do things: send, create, book, execute, deploy. A tool catalog is a list of writes with a few reads sprinkled in, operated by a caller that cannot inspect what a "failure" left behind.

The agent is already a retry loop

Here's the part that makes infrastructure-level retries not just risky but redundant: the model retries on its own.

When a tool call errors, the error goes back into context, and the model decides what to do next: try again, try different arguments, try another tool, or tell the user. That's a semantic retry loop: it operates with full knowledge of what the task means and what failure implies. It can look at "error": "invoice already exists" and conclude the first attempt actually landed.

Now stack a transport-level retry underneath it. The gateway silently replays the timed-out call twice before the model even sees an error. Then the model, seeing a failure, retries at its level. You've built two nested retry loops, and the inner one is invisible to the outer one. The model reasons about a world where the tool ran once; the wire ran it three times. Every property you want from an agent: auditability, predictability, the ability to explain what happened, dies in that gap.

The audit trail suffers the same corruption. If your gateway logs one tools/call that internally became three upstream attempts, your log no longer answers "what did the agent do?" That's the only question that matters during an incident.

What deserves retries: the reads

None of this means "no retries anywhere." It means retries belong to calls whose replay is provably harmless, and in the MCP protocol you can enumerate those: initialize, tools/list, resources/list, prompts/list. Discovery and listing. Pure reads, defined by the protocol itself, the rare case where you get idempotency guarantees from the spec instead of from hoping.

That's exactly the split I shipped in Warden, my open-source MCP gateway: every upstream request gets a timeout, tools/list gets a small retry budget, and tools/call gets a budget of zero: deliberately, permanently, not configurable to be otherwise. A tool call fails once, the error goes to the model with full context, and the semantic layer decides. I wrote it into the README as a design principle rather than a default, because defaults get "fixed" by well-meaning pull requests.

The general rule for agent infra: retry budgets attach to protocol semantics, not to error types. "Was it a timeout or a 503?" is the wrong question. "Is this operation a read or a write?" is the right one, and if you can't tell, it's a write.

If you actually need safe replays, you need idempotency keys

Sometimes the business genuinely requires at-least-once delivery with exactly-once effect. Payments taught us this decades ago. The answer there isn't smarter retries; it's idempotency keys: the caller attaches a unique key per logical operation, the server deduplicates on it, and replays become harmless by construction.

Stripe's API is the canonical example, and it's instructive that the server does the hard part. You can't bolt exactly-once onto a tool that doesn't support it by being clever at the gateway. If a tool matters enough to replay, it matters enough to accept an idempotency_key parameter. Push the requirement to the tool author, where it belongs. An emerging pattern I'd like to see standardized in MCP: tools declaring idempotent: true in their definitions, making retry safety machine-readable instead of tribal.

Until then, the taxonomy is three buckets:

  1. Protocol reads (tools/list and friends): retry freely, small budget, backoff.
  2. Tools with declared/documented idempotency: retry with a key, if and only if the tool defines the dedup behavior.
  3. Everything else: one attempt, honest error, let the model decide. This bucket is 90% of real tool catalogs.

The checklist

  • Timeouts on every upstream call: a hung tool should cost you 30 seconds, not a session.
  • Retry tools/list. Never tools/call. Make the asymmetry explicit in code and docs.
  • Surface errors to the model verbatim and fast: it's a better retry engine than your gateway, because it knows what the task means.
  • One log entry per attempt, including internal ones. An audit trail that hides replays isn't an audit trail.
  • If a write must be replayable, demand idempotency keys from the tool. Don't simulate safety above it.

Agent reliability isn't about making failures invisible. It's about making them legible, to the model in the loop, and to the human reading the audit log afterward. Retries hide information. In a system whose entire job is deciding what to do next, hiding information is the one bug you can't patch later.


Join the newsletter

Get the latest updates straight to your inbox.

Your privacy is important. I never share your email.

© 2026 Parvesh Saini. All rights reserved.