Quick answer
When an agent chains several API calls into one workflow, a failure midway leaves the system in a partial state that the agent is bad at reasoning about. It may report the task as failed but leave the earlier writes committed, or blindly retry the whole sequence and create duplicate resources. The responsibility is yours to design the workflow so steps are idempotent, compensating actions exist for anything that cannot be rolled back, and the agent is told explicitly how to recover rather than left to guess. Do not assume an agent that reports an error also undid the work it already completed.
Why a linear sequence is fragile
Consider a checkout flow with three distinct writes. Each succeeds against a different service.
If step 3 fails, steps 1 and 2 are already committed. An agent that simply reports "fulfillment failed" has not released the reservation or refunded the charge. A retry that starts over at step 1 may reserve a second batch of inventory and charge the card again. The defect is not the failed call; it is the absence of a defined recovery path for the partial state.
| Agent behavior on failure | Consequence | Risk |
|---|---|---|
| Reports error, does nothing else | Earlier writes stay committed | Stale charges and reservations |
| Retries the whole sequence | Duplicate resources and charges | High |
| Re-runs only the failed step | Safe if the step is idempotent | Low |
| Runs a compensating action | Undoes the earlier committed work | Low, but must be designed |
Make every step idempotent
The first line of defense is a stable idempotency key carried through the whole workflow, so a retry never creates a second resource. The agent can then safely re-attempt a single failed step because the server recognizes the same key and returns the existing result.
After a crash, the agent should first query the status of each step using those keys rather than assume nothing happened. That tells it exactly where the sequence stopped.
Design compensation for non-transactional work
There is no distributed transaction across independent services, so anything that cannot be rolled back needs an explicit compensating operation: a refund against a payment, a release against a reservation. Encode the recovery rule in the instructions you give the agent, naming the compensation and its order.
- On failure, query each step's status to learn the actual state.
- Re-attempt the failed step if it is idempotent and the error is transient.
- If the workflow cannot continue, run compensations in reverse order, confirming each succeeds.
- Only then report the workflow as failed, with the final reconciled state attached.
Tell the agent the rule, not just the steps
An agent given only the happy path will improvise recovery, and improvisation is what produces duplicates. Give it the failure contract alongside the sequence: which calls are idempotent, which compensation belongs to which step, and the instruction never to restart from the beginning. The difference between a safe and a dangerous workflow is often a single paragraph of explicit recovery guidance, but it has to be present before the failure occurs.
Outbox pattern for reliable step records
When the agent and the services can crash at any instant, in-memory knowledge of the workflow is not enough; the sequence itself should be persisted as it advances. The outbox pattern writes the intended next step and its status to durable storage in the same operation as the local change, so a restarted process can read the recorded state instead of inferring it from scattered services. A worker then drives each pending step, and because the record carries the idempotency key, a step that completed but whose acknowledgement was lost is safely retried without duplication. This moves recovery from a prompt the agent might forget into an invariant the platform enforces. Even without a full outbox, persisting a small workflow row with the status of each step gives the agent an authoritative place to reconcile after a crash, and prevents the worst outcome: two concurrent retries both believing they are the first attempt.
Testing the partial-failure path
Use a mock that fails a chosen step on demand and verify the agent reconciles correctly. Assert that no duplicate ids appear after a retry, that the compensation was called for the committed step, and that the final reported state matches what the services actually hold. Run the same test with the failure at each step, because the correct recovery differs depending on how far the sequence progressed. A happy-path test passing tells you nothing about the partial state.
FAQ
Can I just wrap the whole thing in a database transaction?
Only if every step lives in the same database and connection. Calls to separate services cannot share a transaction, so you need idempotency and compensation there rather than relying on rollback.
How does the agent know where it stopped after a crash?
It should not guess. It queries each service using the idempotency keys or a workflow id, reads the recorded status, and resumes from the first step that did not complete.
Is retrying a single failed step always safe?
It is safe when the step is idempotent and the server recognizes the same key. Without that guarantee, even a single-step retry can duplicate work, so verify the contract before relying on it.
What should the final report contain?
The reconciled state of every step, which compensations ran, and any work that still needs a human. A bare "failed" message hides the partial state that caused the problem in the first place.
This is part of the Powerduck vs Cursor: API Contract Tools for AI Coding — start there for the full picture of agents acting on APIs.
Top comments (0)