SCALAC.AI

When Your AI Agent Does the Same Thing Twice

An agent approves an invoice adjustment and sends it to the accounting system. The adjustment goes through. The confirmation never reaches the agent.

After a restart, the agent tries again. The accounting system accepts another adjustment against the same invoice. Finance now has two entries to reconcile, even though the agent made the right decision about the invoice.

This is an illustrative example, but the failure mechanism is established. A process can stop after another system has acted and before it records the result. Resuming the process leaves that uncertainty in place.

Durable execution helps an agent recover its progress. Preventing the second adjustment also depends on how the accounting system handles a repeated request. Before you approve an agent for production, your team should be able to explain both.

The adjustment happened. The agent doesn't know.

At the point of interruption, the two systems have different information. The accounting system has an entry. The agent has a request with no confirmed outcome. A timeout tells the agent how long it waited; it cannot tell it whether the adjustment happened.

One adjustment, two attempts

An invoice adjustment · illustrative example

One adjustment, two attempts

Agent workflowAccounting system
  1. 01

    The first request succeeds

    Send adjustment
    →
    Adjustment saved
  2. 02

    The confirmation never arrives

    Outcome unknown
    × ←
    Adjustment still saved

    The workflow restarts without a recorded result.

  3. 03

    The retry creates another entry

    Send adjustment again
    →
    Two adjustments

    If the receiver accepts the retry as a new operation.

The workflow resumes. The first adjustment remains in the accounting system.

Mechanism: Temporal documentation on activity idempotency. This example is not a client incident.

The second request causes trouble if the receiving system treats it as a new operation. The same uncertainty can arise when an agent sends a supplier email or creates a customer refund. Each tool has its own rules for recognising a repeat.

We covered the timeout decision in our article on moving agent orchestration to the JVM. For the person approving the rollout, the next question is practical: can the team establish what happened before it lets the agent act again?

What durable execution remembers

A durable runtime stores workflow progress so a process can recover after an interruption. In a system such as Temporal, replay reuses activity results that the runtime has recorded as complete. That saves the application from repeating work it already has a result for.

The difficult case is an activity whose external effect happened but whose completion never reached the runtime. Temporal’s documentation describes this case and recommends making activities safe to repeat. The runtime cannot infer an accounting entry from a missing response.

This distinction also applies to model calls. Recording a model’s result lets the workflow reuse that decision during recovery. If the result never became durable, the application may have to call the model again. Any external action based on that decision requires its own protection.

These mechanisms are established distributed-systems patterns. DBOS describes the same gap between an external operation and its completion record, including repeated emails. Our SIGNAL #5 coverage discusses the scope of exactly-once claims. A runtime can handle much of the recovery work; your team still has to check the guarantees of the tools it calls.

Five checks before the agent can retry

Take a step that changes something outside the agent’s workflow. For the invoice adjustment, the review looks like this:

CheckWhat the team should show
Same operation?One saved ID across attempts
Safe to repeat?Receiver deduplicates that ID
Outcome unknown?A lookup or a hold for investigation
Same decision?Recovery uses the saved action
Already happened twice?A tested correction and a named owner

These checks cover different jobs. Saving workflow progress allows recovery. Idempotency makes repeated attempts produce the same business effect. Reconciliation establishes whether the external records agree with yours. Compensation handles an effect that requires a corrective action, such as reversing an extra adjustment.

Buying a runtime helps with the first job. For the others, inspect the receiving API and the recovery procedure. An integration can support some of these checks and still leave an unresolved outcome for a person to investigate.

Give the adjustment one identity

The accounting system must recognise both requests as attempts to perform the same adjustment. Your application assigns an operation ID before sending the first request, saves it, and reuses it on a retry. This is the purpose of an idempotency key.

The receiving system must enforce that contract. Attaching an ID does nothing if the receiver ignores it, forgets it before the retry arrives, or allows two concurrent requests to create two entries. Stripe’s idempotent-request documentation defines the behaviour and retention of its keys.

Keep the ID under application control. A new model response can produce a different tool-call ID or different wording for the same intended action. If your application treats that new value as the operation’s identity, it may turn a retry into a second adjustment.

The ID also has to distinguish legitimate new work. Two separate adjustments to one invoice need separate identities. Reusing the invoice number alone would collapse them into one operation. Your team should be able to distinguish a retry from an authorised new adjustment without asking the model to decide which one it meant.

Burning tokens on failed retries?

Ten years of making systems recover correctly. Now applied to agents. Talk to our engineers.

Keep the original decision during recovery

The agent can create another kind of inconsistency if recovery asks it to reconsider the invoice.

On the first attempt, it approves an adjustment. On the second, it chooses to escalate the invoice to a person. The accounting system already has the adjustment, while the workflow now records an escalation. A check for duplicate adjustments will not catch this: the agent took two different actions.

Save the chosen action and the values needed to carry it out before issuing it. Recovery can then continue that action and check its outcome. If changed circumstances require a new decision, make that an explicit transition with the earlier effect accounted for.

The record should let someone connect the original decision, its operation ID and the receiver’s response. We explain the evidence side in our payment audit-trail article. That record helps the team reconstruct what happened; it does not prove that the original invoice decision was correct.

Decide what happens when the answer stays unknown

An API may accept an idempotency key, expose a status lookup, provide both, or provide neither. Your retry policy has to reflect that contract.

If the accounting API guarantees that a repeated request with the same key cannot create another adjustment, the application can retry within those guarantees. If it offers an authoritative status lookup, the application can check the original operation. A response that says „not found” may still require waiting if the provider’s records take time to become visible.

If neither route establishes a safe next action, hold the operation for investigation. Record who will handle it and how long the business can wait. A human approval before the initial request does not resolve uncertainty about what happened afterwards.

For an extra adjustment, finance may be able to post a correction. For a supplier email, someone may have to explain the duplicate. A correction can also encounter an interruption, so test that path with the same care as the original action.

Illustrative example

An agent handles invoices that fail a purchase-order match. Below a set threshold it posts the adjustment and emails the supplier without asking anyone. One interruption after the adjustment reaches the accounting system therefore leaves two questions open instead of one: whether the adjustment was posted twice, and whether the supplier was told twice. The recovery worked as designed. Only one of those two effects has a correction path.

Start with the steps that affect the business

Review the workflow step by step. Prioritise money movement, customer communication and changes to records that other teams rely on. For each, ask what a repeat would do, whether anyone could detect it, and how the team would correct it.

A read-only lookup has a different risk from posting an adjustment. It may still merit a saved result because it is expensive, or because the agent must use the same evidence after recovery. Choose where to persist results and where to protect effects based on those consequences. Cloudflare’s workflow rules describe this distinction at the implementation level.

The cost includes storage and engineering work, along with reconciliation and customer support when an operation goes wrong. Our production cost model for AI agents includes recovery and cleanup; its ranges are not a price estimate for this particular invoice flow. The broader checklist is in the LLM production whitepaper.

Give the integration an owner. In one Scalac client implementation, the team built the retry, timeout and streaming logic around the agent. That is an example of delivery work, not evidence that the client experienced duplicate actions. The scope of your team’s work will depend on what your runtime and receiving systems already provide.

Before release, test an interruption after the accounting system accepts the adjustment but before your workflow records success. Then resume the workflow and inspect both systems. Can the team account for the adjustment, avoid creating another one, and hold for investigation when it cannot establish the outcome?

That test gives you something concrete to review before expanding the agent’s authority. Unfinished integration work is one of the issues we discuss in why enterprise AI pilots stall before production.

If your team is working through this decision, send us one workflow. Include the steps that change external records and the APIs they call. We can help assess what recovery covers and where the integration still requires work.

FAQ

No. It preserves recorded workflow progress and stops completed steps from re-running during replay. Temporal’s documentation states that activities run at-least-once and should be written to be idempotent, normally with a key the receiving service understands. Adopting a runtime moves the boundary; it does not remove it.

Only if it satisfies four conditions: it represents the same logical business operation on every attempt, it survives a restart, the receiving system stores or deduplicates on it atomically, and it is not regenerated from new model output. An ID that changes on retry is observability, not idempotency. Hashing the serialised arguments fails the same test, because a semantically identical payload worded differently hashes differently.

Look where reconciliation happens rather than in the agent’s logs, which may not contain the external result. Duplicated effects usually surface as small unexplained differences: a credit note nobody can trace, a customer mentioning they received two emails, a count in a downstream system that no longer matches yours. Compare workflow records against the receiving system using operation IDs and external references.

No, and treating it as a blocker is how this gets deferred indefinitely. Run the five checks above over your steps. In most systems a handful qualify: money movement, anything that reaches a customer, anything written to a system of record. Ship with those covered. For the rest, a stable identifier or nothing at all is the right answer.