TAOS
← White Papers
AI Engineering

The Saga Pattern for AI Agents: Rollback When Your LLM Gets It Wrong

May 1, 2026Taos Team
Saga PatternRollbackArchitecture

The Saga Pattern for AI Agents: Rollback When Your LLM Gets It Wrong

Category: AI Engineering | Reading time: 6 min


AI Agents Fail. The Question Is How.

An AI agent that never fails doesn't exist in production. Networks go down, APIs return unexpected data, LLMs misinterpret context, downstream systems are temporarily unavailable. Failure is not an edge case — it's a design requirement.

For single-step operations, failure handling is straightforward: catch the error, return a result, log it. For multi-step AI workflows — where each step builds on the previous one and modifies state in multiple systems — failure handling is a distributed systems problem.

The saga pattern, well-established in microservices architecture, is the standard answer. Applied to AI agents, it provides systematic, automatic rollback when any step in a multi-step workflow fails.


Why You Can't Just Retry

The instinct when an AI agent step fails is to retry it. This works for some failures (transient network errors) and breaks for others:

Idempotency violations. If step 3 succeeded but step 4 failed, retrying the entire workflow from step 1 may duplicate the step 3 action — creating two payment records, two ERP entries, two notifications.

State dependencies. Step 4 may have depended on state created by step 3. If the retry skips to step 4, it may not have the context it needs. If it re-runs step 3, it may create a duplicate.

Time-sensitive operations. Some steps must not be re-run — a payment notification sent, a batch job triggered, a regulatory report submitted.

Retry logic handles transient failures. The saga pattern handles the broader class of partial completion failures.


The Saga Pattern: Compensation for Every Step

The core idea is simple: every step that modifies state registers a compensation function — the action that undoes it. When a failure occurs, the saga executor runs the compensation functions in reverse order (LIFO), cleaning up all completed state.

In Taos, this is built into the kernel:

# After each step completes, register compensation immediately

# Step: Create payment record
payment = await payment_service.create(invoice_id, amount, vendor_id)
taos.register_compensation(
    step_name="createPayment",
    compensate_fn=lambda: payment_service.void(payment.id)
)

# Step: Update ERP ledger
ledger_entry = await erp.record_payment(payment.id, amount, gl_account)
taos.register_compensation(
    step_name="updateERP",
    compensate_fn=lambda: erp.reverse_entry(ledger_entry.id)
)

# Step: Notify treasury
notification = await treasury.notify_payment(payment.id, amount)
taos.register_compensation(
    step_name="notifyTreasury",
    compensate_fn=lambda: treasury.cancel_notification(notification.id)
)

If any subsequent step fails, taos.run_compensation() runs:

COMPENSATION_STARTED
  → notifyTreasury:  treasury.cancel_notification(notification_id)   ✓
  → updateERP:       erp.reverse_entry(ledger_entry_id)              ✓
  → createPayment:   payment_service.void(payment_id)                ✓
COMPENSATION_COMPLETED — state clean

The system is back to its pre-workflow baseline. No orphaned records. No duplicate entries. Clean.


The LLM Failure Case

Here's where saga compensation is specifically valuable for AI agents: the LLM itself can fail partway through a workflow.

An LLM-based agent might:

  • Misidentify a vendor, causing step 2 to fetch the wrong vendor data
  • Hallucinate an invoice amount that's different from the actual invoice
  • Decide mid-workflow to call a different tool than expected
  • Produce malformed output that a downstream step can't parse

In all of these cases, the failure might not occur until the problem propagates several steps forward. The saga compensation doesn't care why the failure occurred — it only cares that it did. Every completed step gets compensated, regardless of whether the failure was a network error or an LLM mistake.


Designing Compensation Functions: The Hard Cases

Not every compensation is trivial. Here are common patterns and their compensation approaches:

Payment voiding:

compensate_fn=lambda: await db.execute(
    "UPDATE payments SET status='voided', voided_at=NOW() WHERE id=$1",
    payment_id
)

ERP reversal:

compensate_fn=lambda: await erp.create_reversal_entry(
    original_entry_id=ledger_entry.id,
    reason="workflow_compensation",
    reference=taos.execution_id,
)

Notification cancellation (if already sent): If the notification was already delivered, cancellation is impossible. This becomes a checkpoint — compensation halts and triggers human escalation:

taos.register_compensation(
    "notifyTreasury",
    compensate_fn=lambda: escalation_service.create(
        message=f"Payment notification {notification.id} already sent — manual cancellation required",
        urgency="high",
    ),
    is_checkpoint=True  # stops automated compensation here
)

External API calls: If your workflow called a bank API and the payment is already in the bank's system, compensation means calling the bank's cancellation API — or raising a human escalation if the cancellation window has passed.


The Audit Trail of Compensation

Every compensation action is recorded in the Taos audit log. After a compensated workflow, the full trace is available:

[09:12:00] STEP_COMPLETED   extractInvoice     invoice=INV-8821
[09:12:01] STEP_COMPLETED   lookupVendor       vendor=V-441
[09:12:02] STEP_COMPLETED   evaluatePolicy     action=allow
[09:12:03] STEP_COMPLETED   createPayment      payment=PAY-9921
[09:12:04] STEP_COMPLETED   updateERP          entry=ERP-0041
[09:12:05] STEP_FAILED      notifyTreasury     error=connection_refused
[09:12:05] COMPENSATION_STARTED
[09:12:05] STEP_COMPENSATED updateERP          status=reversed
[09:12:05] STEP_COMPENSATED createPayment      status=voided
[09:12:05] STEP_COMPENSATED lookupVendor       status=no_state (no-op)
[09:12:05] STEP_COMPENSATED extractInvoice     status=reset_to_pending
[09:12:05] COMPENSATION_COMPLETED

When the operations team investigates the failed workflow, they have the complete picture: what happened, what failed, and exactly how the system recovered. No mystery, no missing data.


Testing Compensation

Saga compensation should be explicitly tested. Taos makes this easy with the simulate_erp_failure parameter in the payment demo:

result = await run_governed_payment(
    invoice_id="TEST-INV-001",
    simulate_erp_failure=True,  # Injects failure at the ERP step
)

assert result["compensated"] == True
assert result["compensation_log"][0]["step"] == "updateERP"
assert result["compensation_log"][1]["step"] == "createPayment"
# Verify no payment record in DB
assert await db.fetch_payment(result.get("payment_id")) is None

Test your compensation. It's the safety net you don't want to discover is broken in production.


The Bottom Line

AI agents that modify state in multiple systems must have compensation logic. The saga pattern makes this systematic: every step registers its rollback, the kernel runs it automatically on failure, and the audit trail captures the complete recovery sequence.

Your LLM will sometimes get things wrong. Your kernel should always clean up correctly.


Tags: saga pattern, AI agent rollback, compensation, distributed workflows, LangGraph, AI safety, payment agent