The Saga Pattern for AI Agents: Rollback When Your LLM Gets It Wrong
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