Durable Execution — built in
Your database is transactional. The world is not — and a rollback does not undo the payment you took. So every call is classified, and the ones that leave the platform run exactly once, across a crash and across a deploy. There is no retry policy to write.
01
Why any of this matters
A rollback undoes your rows. It undoes nothing else.
Not the payment you took, not the email you sent, not the row you wrote in somebody else's system, and not the model call you were billed for. The moment your code reaches outside, a crash leaves you in a state your database cannot describe — and cannot repair.
And it will crash, because a deploy is a crash, a scale-down is a crash and an OOM is a crash — they happen on a Tuesday afternoon rather than in a disaster. The failures are not abstract either: a second charge, two identical emails to the same customer hours apart, a refund issued twice, an approval that waited three days and then executed against code that no longer exists.
02
The compiler already knows which calls leave the platform
A workflow body can run more than once — that is how a crash is survived: work that did not commit is done again. Re-running arithmetic is harmless. Re-running a payment is a second charge.
So every call that leaves the platform is wrapped in a durable step, per call, with nothing marked. A resume finds the first call already recorded, skips it, and picks up at the second — the ordinary code between them re-runs freely, because it is a computation over results that are already written down.
And the verdict is not folklore. It is in the resolved model, with the route it took:
$ osy model --json # in demo/agent-expenses "name": "ReadReceipt", "effects": ["Llm.Extract", "UnitOfWork.Commit"], "durability": "External", "durabilityVia": "Llm.Extract"
| durability | what it means | on a resume |
|---|---|---|
Deterministic | the same inputs give the same answer | re-run, freely |
Nondeterministic | it reads a clock, a random number, the database | re-run, but the value it produced is recorded where it must not move |
External | it leaves the platform — a model, an HTTP call, a mail | not re-run. The recorded result is reused |
03
When "do not run it twice" is not enough
Some work has to be UNDONE. Stock was reserved, a card was charged, and then the courier hand-off failed — the two committed steps have to come back. That is a saga, and it is written as a block with a lifetime rather than as a chain of callbacks.
workflow FulfillmentSaga { Tracks = Fulfillment.Status; Autostart = true; Initial = Building; state Building { enter { 1 var trace = Workflow.Once("make-trace", () => new Trace { Fulfillment = this.Item, Log = "" }); Log.Information("▶ fulfillment {Reference}: starting saga", this.Item.Reference); 2 await using var saga = Workflow.BeginSaga(); // dispose-without-Complete unwinds the committed steps, in reverse try { Log.Information(" ✓ step 1: reserving stock"); var reservation = Workflow.Once("make-reservation", () => new Reservation { Fulfillment = this.Item }); 3 await saga.Run("reservation", reservation, () => ReleaseStock(reservation, trace)); // step 1 + its undo, coupled Log.Information(" ✓ step 2: charging the card"); var charge = Workflow.Once("make-charge", () => new Charge { Fulfillment = this.Item }); await saga.Run("charge", charge, () => RefundCharge(charge, trace)); // step 2 + its undo 4 saga.OnUnwind(() => ReleaseHold(trace)); // a compensation with NO forward step (a loyalty hold booked inline) Log.Information(" ✓ step 3: dispatching the courier"); var dispatch = Workflow.Once("make-dispatch", () => new Dispatch { Fulfillment = this.Item }); await saga.Run("dispatch", dispatch); // step 3 — no undo; this child FAILS 5 saga.Complete(); // never reached goto Fulfilled; 6 } catch (WorkflowError e) { Log.Information("✗ fulfillment {Reference}: a step failed — rolling back committed steps in reverse", this.Item.Reference); goto Aborted; // the goto unwinds through the await-using dispose → H, C, S } } } terminal success Fulfilled { } terminal error Aborted { Message = "fulfillment aborted — reservation and charge compensated"; }
An explicit memo. Creating this row is not an outbound call, so nothing would have made it a step — Once is how you say "whatever else re-runs, this happened already".
The saga is a SCOPE. Leaving it without calling Complete() unwinds every step that committed, in reverse. await using is the ordinary C# shape, doing the ordinary C# thing.
The undo is coupled to the step that needs it, at the point the step is written. Not in a rollback function at the bottom of the file that has to be kept in step with the forward path.
A compensation with no forward step — something committed inline that still has to come back. It unwinds in the same order as the rest.
The only thing that keeps the work. Reaching the end of the block without it is a rollback, so the failure path is the default and the success path is the one you have to say out loud.
The failing child raises. The goto leaves the scope, which disposes the saga, which unwinds — refunding the charge, then releasing the stock, in reverse.
04
What happens when you change it
Durability, and the questions people actually ask about it
saga.Complete()Where to go next
The state, the slots, and the run this all happens inside.
`Once`, `BeginSaga`, `Run` — one page each.
The app on this page, one command away.