Skip to main content
Agent Fails Midway: Implementing the Saga Pattern

Agent Fails Midway: Implementing the Saga Pattern

AI Integration
••5 min read•By Daily Miranda Pardo

Your order processing agent works. It runs three steps in sequence: updates the CRM status, creates the invoice in the billing system, and sends the confirmation email to the client. Nine times out of ten, flawless.

On the tenth run, the email step fails. Your SMTP provider returns a 503.

The CRM now shows the order as "confirmed". The invoice exists in the billing system. The customer never received anything. Three systems with states that don't match — and no visible error in your agent's logs.

This is the distributed transaction problem in AI agents, and it hits production before you expect it.

Why agents create consistency problems

In a relational database, you have BEGIN TRANSACTION and ROLLBACK. If something fails, everything reverts to the prior state. AI agents have nothing of the sort.

Each tool call is an independent operation against an external system. The CRM has its API. The billing system has its own. So does the SMTP provider. When an agent executes these steps sequentially, there is no native mechanism to undo the earlier ones if the last one fails.

The most common pattern in teams implementing agents for the first time:

// What most teams do — and what breaks in production
async function processOrder(orderId: string) {
  await crmTool.updateStatus(orderId, "confirmed"); // step 1
  await billingTool.createInvoice(orderId);          // step 2
  await emailTool.sendConfirmation(orderId);         // step 3 → fails
  // If step 3 throws, steps 1 and 2 are already written to external systems
}

The error gets caught, logged, maybe retried. But nobody undoes the earlier steps.

The Saga pattern: explicit compensations

The Saga pattern comes from distributed systems. The concept is straightforward: every step that can write data also defines a compensation operation that undoes that effect.

It is not an automatic rollback. It is a function you define for each step: "if anything fails after me, run this to revert to the previous state."

Basic structure in TypeScript:

type SagaStep<T> = {
  execute: () => Promise<T>;
  compensate: () => Promise<void>;
  description: string;
};

async function runSaga<T>(steps: SagaStep<T>[]): Promise<T[]> {
  const executed: SagaStep<T>[] = [];
  const results: T[] = [];

  for (const step of steps) {
    try {
      const result = await step.execute();
      results.push(result);
      executed.push(step);
    } catch (err) {
      // Run compensations in reverse order
      for (const done of [...executed].reverse()) {
        try {
          await done.compensate();
        } catch (compErr) {
          // Log compensation failure — requires human attention
          console.error(`Compensation failed for: ${done.description}`, compErr);
        }
      }
      throw err; // Re-throw the original error
    }
  }

  return results;
}

Applied to the order case

With this pattern, each agent step looks like this:

const orderSaga: SagaStep<unknown>[] = [
  {
    description: "CRM: update order status",
    execute: () => crmTool.updateStatus(orderId, "confirmed"),
    compensate: () => crmTool.updateStatus(orderId, "pending"),
  },
  {
    description: "Billing: create invoice",
    execute: () => billingTool.createInvoice(orderId),
    compensate: () => billingTool.cancelInvoice(orderId),
  },
  {
    description: "Email: send confirmation",
    execute: () => emailTool.sendConfirmation(orderId),
    compensate: async () => {
      // Email already sent cannot be unsent,
      // but if it was never sent, there is nothing to compensate
    },
  },
];

try {
  await runSaga(orderSaga);
} catch (err) {
  // Compensations already ran automatically
  // System is left in a consistent state
  await alertingTool.notifyOps(orderId, err);
}

If the email step fails, runSaga automatically runs billingTool.cancelInvoice() then crmTool.updateStatus(orderId, "pending"). All three systems revert to their prior state.

What most teams ignore: idempotent compensations

A common trap: compensations can fail too. If cancelInvoice fails because the billing system is down, you have the original problem plus a new one.

Two rules for robust compensations:

Mandatory idempotency. Running cancelInvoice(orderId) twice must produce the same result as running it once. Use unique operation IDs and check state before acting:

compensate: async () => {
  const invoice = await billingTool.getInvoice(orderId);
  if (invoice.status !== "cancelled") {
    await billingTool.cancelInvoice(orderId);
  }
  // Already cancelled — do nothing, idempotent
},

Log failed compensations. When a compensation fails, the system needs human intervention. That log is the audit trail that lets you resolve the issue manually. Without it, the incident is invisible.

The AI integration service includes this kind of compensation logging as part of the production contract for any agent operating across business systems.

When to apply the Saga pattern

Not every agent needs it. Apply Saga when:

  • The agent writes to more than one external system in a single execution
  • The steps are not naturally reversible (an invoice created, an email sent)
  • Inconsistent state has real consequences: a customer gets charged twice, operators see bad data, inventory goes out of sync

If your agent only reads data, or only writes to one system, you can skip it without issue.

The failure that costs the most

Teams that arrive with this problem have been noticing "anomalies" in the data for weeks: confirmed orders without invoices, invoices with no matching order, customers calling about confirmations that never arrived.

The original failure goes undetected for days or weeks because the agent never throws a visible error. The inconsistent data silently accumulates until someone manually cross-references it.

Implementing Saga upfront takes two or three hours. Implementing it after a data incident takes weeks of manual cleanup and history review.

If you're building an agent that touches multiple systems in your business and want an architecture that carries no technical debt from day one, let's talk directly.

You can also see how the AI integration for businesses service is structured — the Saga pattern is part of the base design for any agent operating on production systems.

Share article

Repetitive processes in your business?

Download the free AI Automation Map — the 5 most time-consuming processes and how to fix them.

No spam. Just the PDF. Unsubscribe anytime.

Written by Daily Miranda Pardo

I help businesses automate processes, build AI agents and connect intelligent systems.