AI agent retries: avoiding duplicate tool calls
· by Plori Engineering
Before an AI agent retries a tool call, determine whether the prior operation is absent, still running, completed with a saved result, or uncertain. Reuse a saved result when the record confirms completion. Reconcile uncertain operations with the external system before retrying them. Matching tool names and arguments do not prove that two calls are the same operation.
A durable agent needs a record of each tool operation and enough evidence to choose a recovery action. Otherwise, a retry can send the same message twice or start a second job while the first still runs.
In an April 2026 LangGraph issue, a user reported that the system dispatched long tool calls again while the original calls continued. The report describes repeated work and identical arguments, but it does not establish the root cause or a general LangGraph defect.
What must the system know before it retries?
Classify the prior operation before choosing a recovery action:
| Observed state | Safe recovery action |
|---|---|
| Confirmed absent | Start the operation. |
| Confirmed running | Continue to observe the existing operation. |
| Completed with a saved result | Return the saved result without calling the tool. |
| Outcome uncertain | Reconcile with the tool's external system, then decide. |
These states require evidence, not a guess based on elapsed time. A worker timeout does not prove that its tool stopped. A missing result does not prove that the tool had no effect. The worker may have completed the action and failed before it saved the result.
Operation identity also needs more than tool arguments. Two requests to send the same text to the same channel may be separate intended messages. After a worker restart, the same request may be a retry of one message. Give the logical action an operation identifier that stays stable across recovery attempts. Pass it to the tool when its API supports idempotency keys.
What can a completed-result cache guarantee?
A completed-result cache prevents repeated execution only after the system saved the result. On recovery, the agent can return it and continue with the next step.
Apache Airflow documents this pattern for its durable agent execution. It saves model responses and tool results as steps finish. A retry replays completed steps and executes the remaining steps. Its documentation also states the limit: the cache stores return values, not external side effects.
We tested reuse of saved results during a controlled plori production fault on September 7, 2026. Only one synthetic agent was running during the fault. Its shell tool call continued running and saved its result. After recovery, one successor consumed that result without repeating the shell call and then completed the run.
The public API recovered in about 37 seconds. The original connected WebSocket showed no run progress for about ten minutes, until recovery processed the interrupted run. Service readiness did not mean the in-progress agent resumed. The saved tool result let the successor continue without repeating the completed call once run recovery began.
This test proves one narrow property: a saved, completed tool result can survive a worker fault and be reused once. It does not prove exactly-once execution for all tools, zero lost actions, or uninterrupted streaming.
What should happen when the outcome is uncertain?
The uncertain state starts when the tool may have changed an external system but no durable result confirms completion. A generic retry policy cannot resolve it. The tool needs a reconciliation rule that understands its own side effect.
If an external system supports lookup by operation identifier, use it to confirm whether the action completed. Where the API supports idempotent retries, reuse the original key according to its contract. For a shell command, the correct inspection depends on what the command changed. Some commands are safe to repeat, and others require inspection or human review.
Airflow describes the same failure boundary. If a tool produces a side effect and then fails before its result enters the cache, a retry can produce that side effect again. Its guidance is to make custom tools idempotent, confirm whether an operation already completed, or use database constraints that reject duplicate writes.
Do not convert uncertainty into “not completed” merely to keep the agent moving. Mark the operation for reconciliation. If the external system cannot answer and a duplicate would be harmful, stop and request a decision.
How should you test retry behavior?
Place faults on both sides of the result-persistence boundary. First, complete a tool call. Save its result. Stop the worker before the agent advances. Recovery must reuse the result and show one external action.
Next, stop the worker after the external side effect but before result persistence. Recovery must find the prior action through an idempotency key, external receipt, or domain-specific query. If it cannot, the test should end in an explicit uncertain state rather than issue the action again.
Also test a tool that remains active after the agent worker loses contact. Confirm that recovery observes the existing operation instead of starting another one. A timeout alone must not authorize a replacement.
Our controlled fault did not stop the executor between its shell side effect and result persistence. We therefore did not test the uncertain window. The test had no customer tool call and cannot support a customer-impact claim. The next useful fault test belongs inside that window, with a tool that exposes a verifiable operation identifier.