Saga Pattern skill for Codex

Multi-service transactions that undo themselves. Codex designs sagas by classifying every step as compensatable, pivot or retriable, writes compensations as real business actions, and persists saga state in Temporal, Step Functions or a database state machine so progress survives crashes. Participants are idempotent on the saga key, timeouts lead to compensation, semantic locks hide half-finished work, and fault-injection tests fail each step in turn.

  • Architecture
  • $saga-pattern
  • 185 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- saga-pattern

Installs to ~/.agents/skills/saga-pattern/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $saga-pattern, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Implements distributed business transactions as sagas - orchestration or choreography, compensating actions, pivot and retriable steps, idempotent participants, timeouts and isolation countermeasures such as semantic locks - using Temporal, AWS Step Functions or a persisted state machine. Use when an operation spans several services or external APIs (order, payment, inventory, shipping), when someone proposes two-phase commit across services, when partial failures leave data inconsistent, or when designing rollback for a multi-step workflow.

Use it when

  • One business operation spans several services or external APIs
  • Partial failures leave reservations, charges or orders inconsistent
  • Someone proposes two-phase commit across services
  • A long-running workflow needs timeouts and rollback

Use something else when

How to check the result

Fault-injection tests fail each step in turn and every run ends fully compensated or fully completed, with duplicate replies and a mid-saga restart causing no double effects.

The mistake it prevents

Keeping saga progress in memory or a request thread, so a deploy or crash mid-flow leaves reservations and payment authorisations stranded with no record.

~/.agents/skills/saga-pattern/SKILL.md

--- name: saga-pattern description: Implements distributed business transactions as sagas - orchestration or choreography, compensating actions, pivot and retriable steps, idempotent… license: MIT ---

A saga is right when every step either completes or is undone by a defined compensation, the saga’s state survives crashes and redeploys, every participant tolerates duplicate requests, and users and support can see where a stuck saga is and why. Without those four properties, a saga is just a sequence of calls that fails halfway.

When a saga is the answer

SituationApproach
Steps span services/APIs with separate databasesSaga
All data in one databaseLocal transaction; no saga
Steps can tolerate eventual consistency with compensationsSaga
Strict atomic visibility required across systems (rare)Rethink boundaries; 2PC/XA across services is brittle and usually unavailable on cloud services
Long-running process with human steps or waits of hours/daysSaga on a durable workflow engine

Orchestration or choreography

OrchestrationChoreography
ControlOne orchestrator tells participants what to doParticipants react to each other’s events
VisibilityState in one place; easy to answer “where is order 812?”Spread across services; needs tracing
CouplingOrchestrator knows all stepsEach service knows the events it reacts to
Fits4+ steps, branching, timeouts, compensations2-3 steps, simple linear flows

Default: orchestration for anything with compensations. Choreographed sagas with compensations become an implicit state machine nobody can see.

Design the steps

Classify each step (Richardson’s taxonomy):

TypeMeaningRule
CompensatableCan be undone by a compensating actionPlace before the pivot
PivotPoint of no return; after it, the saga must finishExactly one; often the payment capture or an external commitment
RetriableGuaranteed to succeed eventually with retriesPlace after the pivot; must be idempotent

Example, order placement:

#StepTypeCompensation
1Create order (PENDING)CompensatableMark order REJECTED
2Reserve inventoryCompensatableRelease reservation
3Authorise paymentCompensatableVoid authorisation
4Capture paymentPivot- (refund is a business process, not a rollback)
5Confirm reservation, approve orderRetriable-
6Request shipmentRetriable-

Compensations are business actions, not database rollbacks: “release reservation”, “void authorisation”, “send cancellation email”. They must be idempotent and must succeed eventually (retry forever with backoff and alerting).

Implementation options

OptionPick when
Temporal (or similar durable execution)Complex sagas, long waits, code-first; team can run or buy Temporal
AWS Step FunctionsAWS shop; visual state machine; Catch blocks route to compensations
Persisted state machine in your DB + outboxFew sagas, no new infrastructure, moderate complexity
Choreography with events2-3 linear steps, no compensations beyond one

Temporal (Python SDK)

from datetime import timedelta
from temporalio import workflow
from temporalio.common import RetryPolicy

with workflow.unsafe.imports_passed_through():
    from .activities import (reserve_inventory, release_inventory, authorize_payment,
                             void_authorization, capture_payment, approve_order, reject_order)

STEP = dict(start_to_close_timeout=timedelta(seconds=15),
            retry_policy=RetryPolicy(maximum_attempts=5, backoff_coefficient=2.0))
FOREVER = dict(start_to_close_timeout=timedelta(seconds=15),
               retry_policy=RetryPolicy(maximum_interval=timedelta(minutes=5)))  # unlimited attempts

@workflow.defn
class PlaceOrderSaga:
    @workflow.run
    async def run(self, order_id: str) -> str:
        compensations = []
        try:
            await workflow.execute_activity(reserve_inventory, order_id, **STEP)
            compensations.append(release_inventory)
            await workflow.execute_activity(authorize_payment, order_id, **STEP)
            compensations.append(void_authorization)
        except Exception:
            for undo in reversed(compensations):
                await workflow.execute_activity(undo, order_id, **FOREVER)
            await workflow.execute_activity(reject_order, order_id, **FOREVER)
            return "rejected"
        await workflow.execute_activity(capture_payment, order_id, **FOREVER)   # pivot
        await workflow.execute_activity(approve_order, order_id, **FOREVER)    # retriable
        return "approved"

Activities receive the same order_id on every retry and must use it as the idempotency key toward the participant (e.g. the payment provider’s idempotency key).

Persisted state machine (no workflow engine)

CREATE TABLE order_sagas (
  saga_id      uuid PRIMARY KEY,
  order_id     text NOT NULL UNIQUE,
  state        text NOT NULL,      -- STARTED, INVENTORY_RESERVED, PAYMENT_AUTHORIZED, COMPENSATING, DONE, FAILED
  step_attempt int  NOT NULL DEFAULT 0,
  next_run_at  timestamptz NOT NULL DEFAULT now(),
  last_error   text,
  version      int  NOT NULL DEFAULT 0,
  updated_at   timestamptz NOT NULL DEFAULT now()
);
  • A worker picks due sagas with FOR UPDATE SKIP LOCKED, performs the next step, and updates state with an optimistic version check, all with commands sent via the outbox.
  • Participant replies (events or HTTP responses) advance the state; unknown or duplicate replies are ignored based on state.
  • A timeout per state moves the saga to COMPENSATING when a participant never answers.

Isolation countermeasures

Sagas lack isolation: other requests see intermediate states. Choose explicitly:

AnomalyCountermeasure
Others act on a not-yet-final orderSemantic lock: state PENDING; other operations reject or wait on pending records
Lost update between saga and a user editReread value / version check before the final step
Inventory oversold while saga runsReserve (decrement available) as step 2, not at the end
Reports show half-done sagasExclude pending states, or show them as pending

Workflow

- [ ] 1. Read AGENTS.md and existing messaging/workflow code; confirm a saga is needed
- [ ] 2. List steps; classify compensatable / pivot / retriable; define compensations
- [ ] 3. Choose orchestration engine or persisted state machine (table above)
- [ ] 4. Make every participant endpoint idempotent with the saga or order ID as key
- [ ] 5. Add per-step timeouts and the path from timeout to compensation
- [ ] 6. Add semantic lock states visible to the rest of the system
- [ ] 7. Expose saga state for support; alert on sagas stuck past their SLA
- [ ] 8. Test failure at every step, duplicate replies, and crash/restart mid-saga

Gotchas

  • Compensation is not “undo the database write”. Emails were sent and webhooks fired; the compensation must be a meaningful business action (cancellation email, refund).
  • A participant timing out does not mean it failed. Compensate only with idempotent actions that also handle “the original request actually succeeded later”.
  • Steps after the pivot must not fail permanently. If a step after capture can be rejected by business rules, move that check before the pivot.
  • Hand-rolled sagas without persisted state lose progress on deploy. If the saga lives in memory or a request thread, it is not a saga.
  • Do not trigger real payments, refunds or emails while testing against production systems; use sandbox credentials and ask the user before any live-money test.

Verification

  • Fault-injection tests: fail each step in turn; the system ends in a consistent final state (all compensations ran, or saga completed).
  • Duplicate every participant reply and command; no double reservation or charge.
  • Kill the worker or orchestrator mid-saga; it resumes from persisted state.
  • A dashboard or query lists sagas by state with age; stuck ones alert.

Output contract

**Saga:** PlaceOrder (orchestrated, Temporal | Step Functions | DB state machine)
**Steps:** | # | Step | Participant | Type | Compensation | Timeout |
**Pivot:** capture payment
**Idempotency keys:** order_id per participant call
**Isolation:** semantic lock via order.status = PENDING
**Observability:** state query/dashboard <link>, alert when age > <SLA>
**Tests:** failure at each step, duplicates, restart mid-saga

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Saga Pattern"
  short_description: "Multi-service transactions that undo themselves"
  default_prompt: "$saga-pattern Our checkout calls inventory, payments and shipping and sometimes leaves orders half done - design a saga for it"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem