# Why an AI Agent Can Execute the Same Action Twice

> Source: <https://dev.to/stringsofthemindoss/why-an-ai-agent-can-execute-the-same-action-twice-2mb7>
> Published: 2026-09-26 02:24:54+00:00

AI agents are becoming execution systems.

They no longer just answer questions. They send messages, create tickets, issue refunds, make bookings, update customer records, trigger deployments, provision resources, and call tools that change external state.

That creates a failure mode distributed-systems engineers already know well — but agent loops make it unusually easy to trigger:

**a tool call can succeed and still look like a failure to the agent.**

Consider a refund:

At step 6, the important question is no longer:

Did the request fail?

It is:

**Did the external effect already happen?**

That distinction is where ordinary retry logic can become dangerous.

A timeout describes what the caller observed. It does not prove what the external provider did.

After a lost acknowledgement, at least two realities may be consistent with the evidence the agent has:

This is an **ambiguous outcome**.

If the agent cannot distinguish those realities, blind retry is not merely a reliability mechanism. It can create a second real-world action.

The same pattern applies far beyond payments:

The underlying problem is not "AI hallucination." It is distributed-systems uncertainty at the boundary between **intent** and **external effect**.

Traditional applications already retry failed network operations. Agent systems add more ways for repetition to occur.

A tool may be repeated because:

These mechanisms can all be individually reasonable.

The danger appears when they cross a side-effecting boundary without preserving the identity and outcome of the **logical action**.

A request ID, tool-call ID, trace ID, retry counter, or timestamp usually identifies an **attempt**.

But a retry of the same refund is not a new business intention just because it has a new tool-call ID.

For safe retry handling, we need a stable **logical operation identity**.

For example:

```
refund / order_123
```

should identify the same intended refund across all retries of that action.

Attempt 1 might have one request ID.

Attempt 2 might have another.

But if both represent the same intended refund, the logical operation identity should remain stable.

This gives us an important distinction:

``` php
transport identity -> which attempt is this?

logical identity   -> which real-world action is this?
```

Those are not the same question.

There is another failure mode.

Suppose an application reuses the same logical operation ID but changes a value that affects the real-world action.

```
operation: send_invoice_4821
attempt 1 destination: alice@example.com
attempt 2 destination: bob@example.com
```

Those should not be treated as equivalent retries.

The operation identity therefore needs to be bound to the **effect-bearing payload**.

If a field can change the external effect — amount, destination, message body, booking details, resource configuration, recipient, etc. — changing it should produce a conflict or a new intentional operation.

Otherwise a deduplication mechanism can become a different kind of bug: incorrectly collapsing two distinct actions into one.

A useful execution model has at least three outcome states:

| State | Meaning | Safe default | 
|---|---|---|
| `CONFIRMED` | Authoritative evidence says the effect happened | Return/replay the known result; do not execute again | 
| `ABSENT` | Authoritative evidence says the effect did not happen | Execution may proceed | 
| `UNKNOWN` | The effect may have happened, but available evidence cannot prove which state is true | Reconcile or block | 

The critical rule is:

**UNKNOWN is not permission to execute again.**

This sounds conservative because it is.

If duplicate execution could be expensive or irreversible, safety sometimes requires giving up immediate progress.

That is the classic tradeoff between **safety** and **liveness**:

If provider truth is unavailable, a high-impact operation may have to remain blocked until a human or a trusted system can resolve it.

When an outcome is ambiguous, the strongest recovery path is often **reconciliation**.

Instead of retrying the mutation, perform a read-only check against an authoritative system.

For a refund, that could mean asking the provider whether the refund exists.

For a booking, check whether the reservation was created.

For a message, query the provider-side message ledger if such a facility exists.

The flow becomes:

```
external effect may have committed
            |
            v
         UNKNOWN
            |
            v
   authoritative lookup
       /          \
      /            \
CONFIRMED          ABSENT
   |                 |
   v                 v
do not repeat      execution may proceed
```

The phrase **authoritative** matters.

A missing local database row is not automatically proof that the external effect did not happen.

Neither is a timeout.

Neither is an empty cache.

`ABSENT` should require evidence strong enough to justify repeating the action.

There is an obvious counterargument:

Do we really want every search, calculation, and read operation going through durable execution coordination?

No.

That would add latency and complexity where there is little duplicate-effect risk.

A better model is selective routing.

| Route | Meaning | 
|---|---|
| `DIRECT` | No consequential external mutation identified | 
| `PROTECT` | Duplicate execution could create an undesirable external effect | 
| `BLOCK` | The system cannot establish that execution is safe | 

A web search is usually `DIRECT`.

A local calculation is usually `DIRECT`.

A refund, message send, booking, order creation, or deployment trigger may be `PROTECT`.

A tool with conflicting or insufficient safety information may be `BLOCK`.

The asymmetry matters:

For consequential tools, conservative classification is often the rational choice.

"Exactly once" sounds attractive, but it is easy to overstate when independent systems are involved.

A client generally cannot atomically commit both:

unless the systems share an appropriate transaction, deduplication, or reconciliation contract.

So the defensible target is narrower:

**One intended consequential operation should produce at most one corresponding external effect across retries — under explicit assumptions — or the system should block rather than guess.**

Those assumptions include:

That is a safety property, not a promise that every operation will eventually succeed.

We have been building **Once**, an open-source execution-safety layer for AI agent tools and MCP integrations, around this model.

The project separates attempt identity from logical action identity, preserves ambiguous outcomes, binds effect-bearing input to protected operations, and increasingly classifies toolsets so harmless calls can remain direct while consequential calls receive stronger protection.

The current public implementation includes:

`DIRECT / PROTECT / BLOCK` routing;
The important part is the boundary of the claim.

Once does **not** claim universal exactly-once execution across arbitrary providers and arbitrary deployments.

If authoritative truth is unavailable, the correct state may remain `UNKNOWN`.

That limitation is part of the design rather than something to hide.

I have now published the deeper technical treatment as a citable technical paper:

**Reliable Execution of Consequential AI Agent Actions Under Retries and Ambiguous Outcomes**

**Jamie Oswald — Once Research**

**Published:** 26 September 2026

**DOI:** [10.5281/zenodo.22969881](https://doi.org/10.5281/zenodo.22969881)

The paper covers:

`CONFIRMED / ABSENT / UNKNOWN`;
If you are building agents that can change external state, the question I would ask is simple:

**If this tool times out after the external effect commits, what prevents the retry from doing it again?**

If the answer is only "the framework retries carefully," there is probably another reliability boundary worth examining.
