Reliability guide · By Mycelium ·

How to prevent duplicate
tool calls in LangGraph

Give each intended action a stable identity, claim it in durable storage before execution, and resolve the previous outcome before allowing a retry. A timeout alone does not prove that a tool failed.

Why an agent retry can send the same email twice

Imagine a tool sends a welcome email. The email provider accepts it, but the connection times out before your application receives the response. The agent sees an error and retries. If that retry sends a new request without resolving the first attempt, the recipient may receive two messages.

The same uncertainty occurs with payments, database writes, and long-running jobs. A workflow checkpoint records workflow progress; it does not by itself make every external effect atomic with that checkpoint. A worker can stop after the external action succeeds and before the application saves its result.

This guide applies to retries and redispatch in LangGraph applications, and to other agent runtimes with side-effecting tools. It does not claim every LangGraph tool call duplicates or that every deployment has the same failure behavior.

Run a local demonstration

With Python 3.10 or newer, create an isolated environment and run Mycelium's included demonstration:

python3 -m venv .venv
source .venv/bin/activate
pip install mycelium-runtime
mycelium demo

The activation command above is for macOS and Linux. On Windows PowerShell, use .venv\Scripts\Activate.ps1.

The tour simulates a redispatched tool, compares unguarded and ledgered execution, then exercises leases, reconciliation, and an ambiguous mutating outcome. The local run checked for this guide reported:

Unguarded executions: 2
Ledgered executions: 1
r1 == r2:   True

Redispatch gate:     HARD_BLOCK
Terminal outcome:   UNKNOWN
Tool body executions: 1

These are excerpts from separate scenarios in a synthetic, single-process demo with an in-memory ledger. No real email is sent. This is not a live LangGraph deployment test or proof of recovery across worker crashes. Production claims require durable storage and validation in your own environment.

Four requirements for safe retries

  1. Preserve the intended action's identity. Retries of one operation must resolve to the same identity. A new random ID on every attempt makes a retry look like new work. Conversely, identical arguments do not always mean the same intended action: two separately authorized emails may have identical content.
  2. Claim before executing. Use an atomic claim in a ledger shared by the workers that can execute the action. A process-local cache cannot coordinate separate workers or survive a restart.
  3. Record the outcome. Save successful results so later attempts can return them. Preserve an external operation reference when the provider supports a reliable lookup. If the provider offers idempotency keys, use them within that provider's documented scope and retention period.
  4. Treat uncertainty explicitly. If a mutating tool may have run, reconcile with the provider or stop for review. A missing local result or expired lease does not establish that the external action never happened.

How Mycelium resolves another attempt

Common execution states and the corresponding safe response
Prior action stateResponse
Completed, result storedReturn the stored result instead of running the tool again.
Still running with a valid leaseWait or poll; another worker does not start a duplicate execution.
Mutating outcome is unknownReconcile through a configured provider adapter when possible; otherwise block a blind retry.
Another execution is proven safeProceed only when the action classification and recovery policy permit it.

See the execution and recovery guide for the resolution gates and provider adapter documentation for reconciliation requirements.

Apply this to your LangGraph tool boundary

Start with one consequential tool. Use mycelium init to create a configuration, identify its side-effect class, and follow the configuration guide to wire that callable into the runtime. Pass the framework's tool-call identity where required, and verify that retries of the same intended action preserve identity in your actual graph.

For a durable single-process setup, choose a supported persistent backend such as SQLite. For multiple workers, use a supported shared backend such as Redis or PostgreSQL with consistent configuration. Read the shared-state documentation before choosing a deployment topology.

Inspect the setup with mycelium doctor --config mycelium.yaml, then follow the verification guide. Check normal success, repeated invocation, concurrent attempts, and an outcome that remains unknown. Ensure the wrapper is on the actual tool execution path; installing the package alone adds no protection.

What this does not guarantee

No local ledger can make an arbitrary remote service participate in an atomic transaction. Correct action identity, durable claims, correct tool classification, provider semantics, and trustworthy reconciliation all matter. When evidence cannot establish the outcome, blocking an action can be the intended result. Do not describe that behavior as an unconditional exactly-once guarantee.

The failure and threat model documents the assumptions and limits. The included demo's motivating redispatch report is LangGraph issue #7417; the simulator is not a claim about every current LangGraph release.

Protect your first tool

Follow the Mycelium quickstart, configure one tool, and verify the behavior before expanding to the rest of your agent. Mycelium complements your workflow engine and tracing tools by controlling the execution boundary.