# Durable Execution for AI Agents: Temporal vs. Inngest vs. LangGraph Checkpointing in Production
Summary: Production durable execution AI agents frequently fail due to rate limits, worker crashes, and unhandled tool exceptions. Naive retries waste LLM tokens and risk duplicated external side-effects. Durable execution solves this state vacuum by converting agent loops into event-sourced, deterministic workflows. This architectural guide compares Temporal, Inngest, and LangGraph checkpointing across five enterprise dimensions, offering production-ready Python and TypeScript patterns with exponential backoff, compensation rollbacks, and zero token replay.
---
The Fragility of Ephemeral Multi-Agent Workflows
Most multi-agent frameworks begin as simple loops in memory. A runtime dispatches prompts to an LLM provider, parses structured tool calls, invokes Python functions or external REST APIs, updates a scratchpad dictionary, and repeats until an exit condition is reached. While functional in local prototypes, running ephemeral agent loops in enterprise production creates severe operational vulnerabilities. When deploying durable execution AI agents at enterprise scale, teams quickly discover that naive memory loops cannot withstand real-world failure modes.
+-------------------------------------------------------------------------------+
| Ephemeral In-Memory Agent Failure |
+-------------------------------------------------------------------------------+
| User Request -> [ LLM Turn 1: 4k tokens ] -> [ Tool Call: Provision Infra ] |
| | |
| Worker Crash / OOM |
| v |
| [ Naive Retry ] -> Re-executes LLM Turn 1 (4k tokens wasted, duplicate spend) |
| -> Re-invokes Tool Call (Duplicate cluster created / billed!) |
+-------------------------------------------------------------------------------+When an unhandled exception strikes—such as an HTTP 429 rate limit from OpenAI, a transient cloud network partition, an Out-Of-Memory (OOM) kill, or a container termination during a blue-green Kubernetes deployment—the in-memory execution context vanishes. Engineering teams typically encounter three crippling failure modes:
1. The Token Replay Tax: A naive retry policy restarts the agent from step zero. If an agent fails on step 7 of an 8-step reasoning cycle, replaying the preceding six steps consumes duplicate prompt and completion tokens, compounding inference costs and adding tens of seconds of latency.
2. Non-Idempotent Side-Effect Duplication: If the agent executed a state-modifying action (such as executing a Stripe charge, updating a CRM record, or provisioning a cloud database) before crashing, an uncoordinated retry risks executing that mutation a second time.
3. Broken Human-in-the-Loop (HITL) Lifecycles: When an agent must pause for hours or days awaiting human managerial approval, holding an in-memory process or WebSocket connection open ties up server memory and fails across worker redeployments.
Entity-Attribute-Value (EAV) Modeling for Durable Agent State
To achieve genuine resilience, agent state must be decoupled from volatile worker memory. Production architectures model agent lifecycles via an Entity-Attribute-Value (EAV) data model mapped onto an append-only event store:
- Entity (`agent_execution_run`): The global execution identity, defined by a deterministic composite key (
tenant_id : workflow_id : run_id). - Attributes (`execution_dimensions`): Discrete runtime facets tracked at every state transition, including
step_index,node_identifier,prompt_hash,tool_call_signature,execution_status, andretry_count. - Values (`deterministic_state_blob`): The immutable payload generated by that transition, including cached LLM JSON completions, token consumption counters, tool response payloads, and compensation pointers.
By persisting each EAV transition deterministically, an orchestration engine can reconstitute an agent's exact mental state up to the nanosecond preceding a crash, eliminating redundant model calls while maintaining strict transaction boundaries.
---
Core Mechanics: How Durable Execution Engines Solve the State Vacuum
Architecting durable execution AI agents requires replacing volatile runtime stacks with event-sourced execution logs. Rather than relying on the process call stack, the runtime records every side-effect, timer, child task, and asynchronous signal into an append-only database.
+-------------------------------------------------------------------------------+
| Durable Execution Replay |
+-------------------------------------------------------------------------------+
| Event History: [WorkflowStarted] -> [ActivityScheduled: LLM_Turn_1] |
| -> [ActivityCompleted: Result_A] |
| |
| Worker Crash Occurs Here -> New Worker Boots -> Replays Event History: |
| 1. Evaluates Workflow code. |
| 2. Reaches Activity LLM_Turn_1 -> Engine matches Result_A in History. |
| 3. Returns Result_A immediately (0 API calls, 0 token spend, 0 ms latency). |
| 4. Resumes new work directly from Activity 2. |
+-------------------------------------------------------------------------------+Deterministic Replay vs. Step Memoization
Engines solve the state vacuum through distinct distributed computing strategies:
- Temporal (Deterministic Event Sourcing): Temporal executes workflow code within a constrained sandbox. When a worker fails and restarts, it re-runs the workflow definition from the top. However, calls to external systems are strictly encapsulated inside Activities. As the workflow replays, Temporal inspects its append-only event history: if an activity invocation matches an already-recorded
ActivityTaskCompletedevent, the engine skips physical execution and immediately returns the cached result. - Inngest (HTTP-Triggered Step Memoization): Inngest structures workflows as functions composed of individual
step.run()blocks. Each step call transmits an execution plan over HTTP to an Inngest orchestration layer. When a step completes, its JSON output is serialized and memoized. Upon subsequent retries or wakeups, Inngest dispatches the previously stored step outputs to the serverless handler, bypassing completed steps entirely. - LangGraph Checkpointing (Graph State Snapshotting): LangGraph implements state persistence at the graph level via checkpointers (
PostgresSaver,SqliteSaver, orMemorySaver). At each super-step of theStateGraph, the checkpointer captures a serialized snapshot of the state channel dictionary along with the node name and execution configuration (thread_id,checkpoint_ns,checkpoint_id). If an execution terminates, the graph can be resumed by passing the targetthread_id.
---
Architectural Comparison: The 5-Dimension Production Resilience Matrix
Choosing between Temporal, Inngest, and native LangGraph checkpointing requires evaluating operational complexity against strict recovery guarantees.
| Production Dimension | Temporal (SDK + Cluster) | Inngest (Event-Driven Steps) | LangGraph Checkpointers (PostgresSaver) |
|---|---|---|---|
| 1. Checkpoint Granularity | Activity-level boundaries. Sub-activity state requires manual heartbeating. | Step-level (step.run()) boundaries via JSON over HTTP. | Node-level super-step boundaries across the graph DAG. |
| 2. Long-Running HITL Pause Costs | Zero idle compute. Workflows enter durable sleep awaiting Signals. | Zero idle compute. Suspends via step.waitForEvent() up to 365 days (run duration restricted by plan tier: 30 days on Free, 90 days on Pro, and up to 1 year on Enterprise). | Zero idle compute when persisted; requires external trigger to resume app.invoke(None, config). |
| 3. Retry Idempotency Overhead | High guarantees. Enforces deterministic execution IDs and activity tokens. | Automatic per-step hashing; requires explicit deduplication keys for custom events. | Developer-managed. Checkpoint saves state, but re-running an interrupted node requires external idempotency keys. |
| 4. Cluster Infra Operational Overhead | High. Requires running Temporal Server (Go), persistence DB (PostgreSQL/Cassandra), and workers. | Zero to Low. Managed cloud tier or lightweight self-hosted Go binary with serverless endpoints. | Low. Requires only an existing PostgreSQL or Redis cluster to back the checkpointer. |
| 5. Token Replay Prevention | Absolute. Completed Activities are never re-executed during replay. | Absolute. Memoized step.run() returns cached JSON immediately. | Strong at node boundaries. Nodes interrupted mid-inference will re-execute the LLM call upon resumption. |
Architectural Trade-off Synthesis
- Temporal excels in complex, multi-tenant enterprise workflows requiring strict distributed transactions, sub-second timer guarantees, cross-service orchestrations, and advanced compensation patterns (Sagas).
- Inngest provides the fastest time-to-production for modern TypeScript/Python engineering teams operating on serverless platforms (e.g., Next.js, AWS Lambda, Cloudflare Workers), eliminating cluster maintenance while delivering step durability.
- LangGraph Native Checkpointing is ideal for intra-agent reasoning loops and dynamic cyclic graphs, but lacks native cluster scheduling, activity timeout management, distributed locking, and cross-microservice transaction boundaries.
---
State Recovery and Rollback Architecture
In mission-critical agent workflows, fault tolerance requires more than restarting after a failure; it requires compensating for partial mutations when unrecoverable errors occur.
The following architecture demonstrates how an orchestrator recovers state, prevents token churn, and executes a Saga compensation rollback when downstream verification fails:
flowchart TD
subgraph ClientLayer ["1. Client Interaction"]
A["Client Agent Request"] --> B["API Gateway / Trigger"]
end
subgraph DurableOrchestrator ["2. Fault-Tolerant Multi-Agent Orchestration"]
B --> C["Initialize Execution Run & State"]
C --> D["Step 1: Execute LangGraph Reasoning Node"]
D -->|Persist Output to Event Store| E{"Reasoning Succeeded?"}
E -->|Yes: Token Replay Prevented| F["Step 2: External Tool Call (Mutate Resource)"]
E -->|Transient API Error| D
F --> G["Worker Crash / Node SIGKILL Occurs"]
G --> H["Orchestrator Detects Timeout & Requeues Task"]
H --> I["Replay Event History (Step 1 Skipped: 0 Tokens)"]
I --> J["Step 2 Retried with Same Idempotency Key"]
J --> K{"Tool Result Verification?"}
K -->|Success| L["Step 3: Long-Running HITL Wait (0 Compute)"]
K -->|Fatal Fault: Downstream Failure| M["Trigger Saga Compensation Rollback"]
M --> N["Compensating Activity: Revert Tool Mutation"]
N --> O["Workflow Terminated with Preserved Audit Log"]
L -->|Approval Event Received| P["Step 4: Commit Final State"]
P --> Q["Workflow Completed Successfully"]
end---
Production Code Implementation: Temporal + Idempotent LangGraph Node with Compensation
The following Python implementation demonstrates a production-grade temporal langgraph agent workflow. It encapsulates a LangGraph node inside a Temporal Activity configured with exponential backoff, heartbeating, and a Saga compensation rollback.
"""
Production Temporal + LangGraph Durable Agent Architecture
Demonstrates idempotent node execution, heartbeating, and Saga compensation.
"""
from datetime import timedelta
import hashlib
import json
from typing import TypedDict, Dict, Any
from dataclasses import dataclass
from temporalio import activity, workflow
from temporalio.common import RetryPolicy
from temporalio.exceptions import ApplicationError
from langgraph.graph import StateGraph, END
# Section 1: Domain Types and LangGraph State Definition
class AgentState(TypedDict):
input_prompt: str
plan: str
tool_name: str
tool_args: Dict[str, Any]
idempotency_key: str
status: str
def agent_planning_node(state: AgentState) -> Dict[str, Any]:
"""Pure reasoning node executing deterministic or LLM prompt decomposition."""
return {
"plan": f"Allocating dedicated database shard for: {state['input_prompt']}",
"tool_name": "provision_database_shard",
"tool_args": {"tier": "enterprise", "storage_gb": 500},
"status": "planned"
}
# Section 2: Temporal Activities: Idempotency, Heartbeats, and Compensations
@dataclass
class NodeExecutionPayload:
state: AgentState
run_id: str
@activity.defn
async def execute_langgraph_node_activity(payload: NodeExecutionPayload) -> AgentState:
"""
Executes a LangGraph step inside an activity. Heartbeats to monitor health.
"""
activity.heartbeat("Executing LangGraph planning node...")
# Generate deterministic idempotency key from run context
raw_signature = f"{payload.run_id}:planning_node:{payload.state['input_prompt']}"
idem_key = hashlib.sha256(raw_signature.encode("utf-8")).hexdigest()
# Execute node logic
node_output = agent_planning_node(payload.state)
updated_state = dict(payload.state)
updated_state.update(node_output)
updated_state["idempotency_key"] = idem_key
return updated_state # type: ignore
@activity.defn
async def provision_resource_activity(payload: Dict[str, Any]) -> Dict[str, Any]:
"""
Simulates external infrastructure mutation with idempotency protection.
"""
activity.heartbeat("Provisioning external resource...")
idem_key = payload.get("idempotency_key", "")
# In production, pass idem_key to external API header
return {
"resource_id": f"shard-{idem_key[:12]}",
"allocated_gb": payload["args"].get("storage_gb", 100),
"status": "active"
}
@activity.defn
async def compensate_provision_resource_activity(payload: Dict[str, Any]) -> None:
"""
Saga compensation activity: reverts external side-effects on downstream failure.
"""
activity.heartbeat("Compensating: Deleting provisioned resource...")
resource_id = payload.get("resource_id")
# Perform clean rollback mutation via API
activity.logger.warning(f"ROLLBACK EXECUTED: Deprovisioned resource {resource_id}")
# Section 3: Temporal Workflow: Fault-Tolerant Orchestration & Saga Pattern
@workflow.defn
class FaultTolerantAgentWorkflow:
@workflow.run
async def run(self, input_prompt: str) -> Dict[str, Any]:
workflow_id = workflow.info().workflow_id
run_id = workflow.info().run_id
initial_state: AgentState = {
"input_prompt": input_prompt,
"plan": "",
"tool_name": "",
"tool_args": {},
"idempotency_key": "",
"status": "initialized"
}
# Exponential backoff retry policy for LLM rate-limits and network faults
llm_retry_policy = RetryPolicy(
initial_interval=timedelta(seconds=2),
backoff_coefficient=2.0,
maximum_interval=timedelta(seconds=30),
maximum_attempts=5,
non_retryable_error_types=["InvalidPromptError"]
)
# Step 1: Execute idempotent LangGraph reasoning step
state: AgentState = await workflow.execute_activity(
execute_langgraph_node_activity,
NodeExecutionPayload(state=initial_state, run_id=run_id),
start_to_close_timeout=timedelta(minutes=3),
heartbeat_timeout=timedelta(seconds=20),
retry_policy=llm_retry_policy
)
# Step 2: Execute mutable tool call with Saga rollback compensation
provisioned: Dict[str, Any] = {}
try:
provisioned = await workflow.execute_activity(
provision_resource_activity,
{
"args": state["tool_args"],
"idempotency_key": state["idempotency_key"]
},
start_to_close_timeout=timedelta(minutes=2),
retry_policy=RetryPolicy(maximum_attempts=3)
)
# Step 3: Downstream validation simulation
if provisioned.get("status") != "active":
raise ApplicationError("Downstream infrastructure health check failed.")
except Exception as exc:
workflow.logger.error(f"Workflow failure encountered. Executing Saga rollback: {exc}")
if provisioned.get("resource_id"):
await workflow.execute_activity(
compensate_provision_resource_activity,
{"resource_id": provisioned["resource_id"]},
start_to_close_timeout=timedelta(minutes=2),
retry_policy=RetryPolicy(maximum_attempts=5)
)
raise exc
return {
"workflow_id": workflow_id,
"status": "completed",
"resource": provisioned
}Architectural Walkthrough
- Heartbeat Timeout Protection: The
heartbeat_timeout=timedelta(seconds=20)ensures that if a worker process executing an LLM call crashes or gets OOM-killed, Temporal notices the missing heartbeat within 20 seconds, reallocates the task to an alternate worker, and prevents deadlocks. - Deterministic Idempotency Hashing: The activity computes a SHA-256 hash using the workflow run ID, node identity, and input prompt. Even under repeated retries, downstream APIs receive identical idempotency tokens.
- Saga Rollback Guarantee: The
try...exceptblock ensures that if subsequent verification fails,compensate_provision_resource_activityexecutes automatically with independent retry policies, preventing orphaned cloud resources.
---
Production Code Implementation: Inngest Step-Based Agent Workflow
For engineering organizations prioritizing TypeScript, serverless runtimes, or event-driven microservices, inngest durable agents provide built-in step memoization and long-running pauses without managing a stateful cluster.
import { Inngest } from "inngest";
export const inngest = new Inngest({ id: "enterprise-agent-core" });
interface AgentEventData {
sessionId: string;
userPrompt: string;
}
/**
* Inngest Durable Execution Function
* Features step memoization, human-in-the-loop pause, and compensation handling.
*/
export const durableAgentExecution = inngest.createFunction(
{
id: "durable-multi-agent-orchestrator",
retries: 4,
cancelOn: [
{
event: "agent.run.cancelled",
match: "data.sessionId",
},
],
},
{ event: "agent.run.requested" },
async ({ event, step }) => {
const { sessionId, userPrompt } = event.data as AgentEventData;
// Step 1: Agent Reasoning & Planning
// Output is memoized into Inngest event store. Retries bypass this block entirely.
const plan = await step.run("agent-reasoning-step", async () => {
// Execute LLM inference call here
return {
action: "provision_storage_volume",
parameters: { sizeGiB: 250, encrypted: true },
idempotencyKey: `idem-${sessionId}-step1`,
};
});
// Step 2: Mutable Side-Effect (External Infrastructure Call)
const provisionResult = await step.run("execute-storage-provisioning", async () => {
const res = await fetch("https://infra.internal.net/v1/storage", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": plan.idempotencyKey,
},
body: JSON.stringify(plan.parameters),
});
if (!res.ok) {
throw new Error(`Provisioning failed with status: ${res.status}`);
}
return (await res.json()) as { volumeId: string; status: string };
});
// Step 3: Long-Running Human-in-the-Loop (HITL) Pause
// Suspends function execution with 0 idle compute consumption for up to 7 days
const humanApproval = await step.waitForEvent("wait-for-lead-approval", {
event: "agent.action.approved",
timeout: "7d",
match: "data.sessionId",
});
// Step 4: Handle Human Rejection or Timeout via Saga Compensation
if (!humanApproval || !humanApproval.data.approved) {
await step.run("compensate-rollback-storage", async () => {
await fetch(`https://infra.internal.net/v1/storage/${provisionResult.volumeId}`, {
method: "DELETE",
});
return { rolledBack: true, volumeId: provisionResult.volumeId };
});
return {
sessionId,
status: "rejected_and_rolled_back",
reason: humanApproval ? "Rejected by supervisor" : "Approval timed out",
};
}
// Step 5: Finalize Execution on Approval
const finalState = await step.run("finalize-agent-action", async () => {
return {
sessionId,
status: "active",
volumeId: provisionResult.volumeId,
approvedBy: humanApproval.data.approverId,
};
});
return finalState;
}
);Inngest Operational Dynamics
Inngest communicates with serverless functions over HTTP. When step.run finishes, the worker returns an HTTP response containing the step result. The Inngest orchestrator records the payload in its journal and immediately triggers the next step. If the serverless worker times out or crashes during Step 2, Inngest triggers a retry; during that retry, Step 1 returns the memoized JSON instantly, consuming zero LLM tokens and zero redundant execution time.
---
Hybrid Architecture: Wrapping LangGraph Graphs with Temporal Orchestrators
When designing fault tolerant multi agent orchestration at scale, teams should not view LangGraph and Temporal as mutually exclusive. Instead, the most resilient enterprise pattern adopts a dual-tier operational model:
+-------------------------------------------------------------------------------+
| Dual-Tier Enterprise Agent Topology |
+-------------------------------------------------------------------------------+
| MACRO-ORCHESTRATION (Temporal Workflow): |
| - Distributed transactions, cross-service Sagas, and multi-tenant security |
| - Long-running timers (days/weeks) and SLA enforcement |
| - Activity retry boundaries with exponential backoff |
| |
| |--> Activity A: [ LangGraph Subgraph Execution (Micro-Reasoning) ] |
| | - Node 1: Decompose Prompt |
| | - Node 2: Cyclic Reflection Loop |
| | - Node 3: Structured Tool Call Generation |
| | |
| |--> Activity B: [ External Payment / Cloud Provisioning API ] |
| |--> Activity C: [ Saga Rollback Activity on Downstream Exception ] |
+-------------------------------------------------------------------------------+- Micro-Reasoning (LangGraph): Operates inside single activity boundaries. It manages cyclic reasoning loops, dynamic branching, structured tool selection, and reflection. Because LangGraph excels at dynamic state transitions, keeping these cycles inside an activity avoids polluting Temporal's event history with hundreds of fine-grained token evaluations.
- Macro-Orchestration (Temporal): Manages cross-service communication, distributed locking, external API mutations, long-running human approvals, and compensation rollbacks.
For engineering teams operating durable execution AI agents in mission-critical environments, integrating these distributed layers often requires specialized infrastructure planning; teams scaling these patterns can partner with our engineering architects via enterprise AI solutions to establish resilient, production-ready agent foundations.
---
Falsifiability & Production Verification: Proving Durability
Architectural claims of durability must be falsifiable through empirical production chaos tests. To verify that an implementation eliminates token replay and avoids orphaned mutations, implement the following testing protocol:
+-------------------------------------------------------------------------------+
| Chaos Injection Verification Flow |
+-------------------------------------------------------------------------------+
| Inject SIGKILL at Token 50 of Step 2 -> Wait for Re-queue Threshold |
| | |
| v |
| Inspect Metrics: |
| 1. Did Prompt Tokens for Step 1 increment? (Expected: 0) |
| 2. Was Idempotency Key reused on Step 2 retry? (Expected: Exact Match) |
| 3. Did Activity Heartbeat alert before timeout? (Expected: Alert Triggered) |
+-------------------------------------------------------------------------------+Observable Primary Evidence Criteria
A durable agent deployment has failed its architectural requirements if any of the following telemetry indicators occur during chaos testing:
1. Token Replay Violation: Total model prompt tokens measured in application metrics exceed $T_{baseline}$ during retries. If an activity with a 4,000-token prompt re-executes upon worker failure, the orchestrator failed to checkpoint step boundaries.
2. Orphaned Mutation Leakage: The count of external infrastructure resources or database records without an associated workflow completion event or compensation event is greater than zero ($N_{orphaned} > 0$).
3. Replay Non-Determinism Panic: In Temporal, any occurrence of NonDeterministicWorkflowError indicates that dynamic code (such as datetime.now() or direct network I/O) was improperly placed in the workflow definition rather than encapsulated in an activity.
4. History Overflow and Termination Violations: Temporal workflow histories that reach the soft warning threshold of 10,240 events or 10 MB indicate history bloat requiring Continue-As-New. Reaching the hard limit of 51,200 events or 50 MB causes the Temporal cluster to forcefully fail and terminate the workflow execution entirely, indicating that high-frequency micro-reasoning cycles were improperly placed directly in the workflow history rather than encapsulated inside an activity.
---
Frequently Asked Questions (FAQ)
Does LangGraph's native PostgresSaver eliminate the need for Temporal or Inngest?
No. PostgresSaver provides point-in-time state checkpointing across graph super-steps, but it does not provide distributed queue management, worker autoscaling, activity heartbeating, cross-service transaction Sagas, or timeout management. In an enterprise system with multiple microservices, LangGraph manages the agent's internal reasoning graph, while Temporal or Inngest manages the distributed infrastructure and external side-effects.
How do durable execution engines handle streaming LLM responses when a worker terminates mid-stream?
If a worker crashes while streaming tokens from an LLM, the unfinished chunk is lost because the surrounding Activity or Step has not completed. However, because durable engines enforce heartbeat timeouts, the orchestrator detects the dead worker, reassigns the activity to a healthy worker, and restarts the stream. While tokens streamed prior to the crash are discarded by the client, using short activity boundaries prevents cascading state corruption.
How do you prevent event history bloat in Temporal when multi-agent workflows exchange large prompts?
Passing large text prompts or image payloads directly through Temporal workflow inputs or activity arguments causes rapid event history bloat, approaching Temporal's 10 MB warning threshold and risking the 50 MB (or 51,200-event) hard termination limit. Production architectures store heavy context payloads, document embeddings, and raw chat logs in an external blob store (such as Amazon S3 or Google Cloud Storage) and pass lightweight content hashes and URI pointers through the workflow state. In addition, multi-turn iterative loops should run encapsulated inside an activity or invoke workflow.continue_as_new() before reaching the 10,240-event threshold.
Under what operational constraints should a team choose Inngest over Temporal?
Choose Inngest when your application runs on serverless infrastructure (Next.js, Vercel, AWS Lambda), when your engineering stack is primarily TypeScript, or when you want durable step execution without operating dedicated Temporal Server clusters and database backends. Choose Temporal when you require self-hosted VPC or on-premises deployment, multi-language polyglot workflows (Python, Go, Java, TypeScript), sub-second timer resolution, and internal data residency or fine-grained governance controls.
---
Sources
- Temporal Documentation: Replay, Workflows, and Determinism
- Inngest Documentation: Durable Execution & Step Functions
- LangGraph Documentation: Persistence and Checkpointing
- Lamport, L. (1978). "Time, Clocks, and the Ordering of Events in a Distributed System." Communications of the ACM, 21(7), 558–565. https://doi.org/10.1145/359545.359563
- Helland, P. (2012). "Idempotence Is Not a Medical Condition: An Essential Property for Reliable Systems." ACM Queue, 10(4), 30–46. https://doi.org/10.1145/2181796.2187821
- Amazon Web Services. "Saga Pattern in Distributed Microservices Transactions." AWS Prescriptive Guidance Architecture Reference.