TUTORIALS 11 min read

Compensating Transactions for AI Agents: Undo Multi-Step Failures Safely

AI agents cannot wrap SaaS calls in one database transaction. A saga with explicit compensations can contain partial failure without pretending every action is reversible.

By EgoistAI ·
Compensating Transactions for AI Agents: Undo Multi-Step Failures Safely

An agent creates a customer record, reserves inventory, schedules a courier, and then fails to charge the payment method. There is no global rollback button. The customer exists, the stock is unavailable, and a driver may already be on the way.

Compensating transactions for AI agents treat the workflow as a saga: each successful forward step registers a deliberate recovery action. If a later step fails, the orchestrator runs those actions according to policy. Compensation is not time travel. It creates a new business event that reduces or explains the effects of the first one.

Model effects before writing the prompt

List every external mutation and classify its reversibility:

  • reversible: release an unshipped reservation;
  • reversible with cost: cancel a booking with a fee;
  • semantically compensable: issue a refund after a settled charge;
  • irreversible: an email was read, a public post was seen, a physical item shipped;
  • human decision required: legal filing, account closure, or high-value transfer.

This classification belongs in the tool registry. A language model should not invent rollback semantics from the tool name. “Delete customer” may violate retention rules and is not necessarily the inverse of “create customer.”

For irreversible steps, reorder the workflow so they happen after validation and approval. When that is impossible, design containment: send a correction, freeze downstream execution, open an incident, or route to a human.

Define forward and compensation contracts

Each step should return the identifiers required to compensate it. Store the exact forward arguments, result, timestamps, policy version, and compensation status.

interface SagaStep<F, R, C> {
  name: string;
  runForward(input: F, key: string): Promise<R>;
  buildCompensation(input: F, result: R): C;
  runCompensation(input: C, key: string): Promise<void>;
}

The compensation key must also be idempotent. A crashed recovery worker may retry releaseInventory; the second attempt should observe that the reservation is already released rather than releasing another customer’s stock.

Register compensation as soon as the forward step commits, not at the end of the workflow. A process can die between those moments.

Persist a saga state machine

Do not keep the plan only in model context. Persist states such as running, compensating, needs_review, compensated, and completed. Each step should have its own forward and compensation state.

{
  "sagaId": "order_7J2",
  "state": "compensating",
  "steps": [
    {"name":"customer","forward":"done","compensation":"not_required"},
    {"name":"inventory","forward":"done","compensation":"done"},
    {"name":"courier","forward":"done","compensation":"retrying"},
    {"name":"payment","forward":"failed_terminal"}
  ]
}

Use optimistic concurrency or a lease so two workers do not advance the same saga simultaneously. Store transitions before emitting work through an outbox. On restart, the orchestrator reads durable state instead of asking the model to reconstruct what probably happened.

Choose compensation order deliberately

Reverse order is a useful default because later steps often depend on earlier resources. It is not universal. A courier cancellation may need to happen before an inventory release even if the calls were created in another order. Some compensations can run in parallel; others require a dependency graph.

Represent dependencies explicitly:

const recovery = {
  cancelCourier: [],
  refundPayment: [],
  releaseInventory: ["cancelCourier"],
  closeTemporaryOrder: ["refundPayment", "releaseInventory"]
};

Never let the model improvise this graph during an incident. The model can explain or select among approved policies, while deterministic orchestration enforces ordering.

Decide when to compensate

Not every failure should trigger rollback. A rate limit or temporary outage may justify retrying the forward path. A validation error or denied authorization is terminal. An ambiguous timeout requires reconciliation before either retry or compensation.

Create a failure taxonomy:

  • retryable: exponential backoff within a deadline;
  • ambiguous: query provider state with a correlation ID;
  • terminal_compensate: begin the registered recovery plan;
  • terminal_hold: pause and request human review;
  • irreversible_incident: contain and notify.

Put limits on both forward retries and compensation retries. Infinite recovery loops can consume money and hold resources indefinitely.

Preserve business truth

A refund does not erase a payment. Store both events. A cancellation does not mean a booking never existed. Audit logs should show who or what initiated the forward action, the reason for compensation, approvals, external IDs, and final outcome.

This matters for customer support and accounting. If the user sees a temporary charge, the product should say that a refund is pending rather than claiming the charge was rolled back. Language must match the actual business state.

Add human checkpoints for costly recovery

Automatic compensation is appropriate when the inverse is routine and bounded. Escalate when fees exceed a threshold, inventory has shipped, a public artifact has meaningful reach, or multiple compensations fail.

Bind the approval to the recovery plan and maximum cost. The reviewer needs a concise snapshot: completed steps, external identifiers, proposed actions, irreversible consequences, and deadline. Do not dump raw model traces as the decision interface.

Test the saga as a failure matrix

For a workflow with four mutations, fail after every step. Then fail every compensation independently. Test process restarts, duplicate events, delayed provider webhooks, out-of-order messages, and a human resuming after the lease expires.

Useful invariants include:

  • no resource remains reserved after a fully compensated order;
  • every external mutation has a durable correlation ID;
  • compensation never applies to another tenant’s resource;
  • completed sagas cannot return to running;
  • irreversible effects always create a visible incident state.

Monitor sagas by age and state. A low error rate can hide a few expensive workflows stuck in compensating for days.

Common mistakes

The most dangerous mistake is defining compensation as the opposite API verb without checking semantics. Others include registering recovery too late, letting the model hold the only copy of IDs, refunding before confirming whether a charge settled, and hiding partial success behind a generic “failed” status.

Avoid compensating for an operation that never committed. Reconciliation should establish the provider state first. Also avoid deleting audit evidence in an attempt to make the system appear atomic.

The takeaway

AI agents operate across systems that cannot share one transaction. Make partial failure a first-class state: define compensations before execution, persist them with each committed step, enforce idempotency, and escalate irreversible cases. A good saga does not promise that nothing went wrong. It ensures the system knows exactly what happened and what safe recovery remains.

Share this article

> Want more like this?

Get the best AI insights delivered weekly.

By subscribing, you agree to our Privacy Policy. You can unsubscribe at any time.

> Related Articles

Tags

AI agentssaga patterncompensating transactionsworkflow orchestrationfailure recoverydistributed systems

> Stay in the loop

Weekly AI tools & insights.