Docs

Durable workflows

Multi-step functions whose steps run exactly once and survive crashes, deploys, retries and waits of hours or days.

Updated Oct 17, 2026Open in console

A workflow is an ordinary function that names its steps through context.step. Each finished step is saved with its result, so when something fails — a card network times out, a deploy restarts the server — the run picks up at the step that failed instead of starting over. Steps can also sleep (for minutes or days, at no cost) and wait for a signal such as a human approval or a webhook.

export async function handler(order, { step }) {
  const charge = await step.run('charge', () => chargeCard(order))           // runs once; result saved
  await step.run('receipt', () => sendReceipt(order.email, charge.id))
  await step.sleep('cool-off', '1h')                                         // seconds, '15m', '2h', '3d' or a Date
  const ok = await step.waitForSignal('approval', { timeout: '2d' })         // null after the timeout
  if (!ok?.approved) return { shipped: false }
  await step.run('ship', () => createShipment(order, charge.id))
  return { shipped: true }
}
step.runsleep / waitForSignaltime or signalreturnsStartAPI, CLI, SDK oranother functionCall the functionfinished stepsreturn their savedresultSave the new stepthen call againSleep / waitno compute untilit wakesReturnrun SUCCEEDED withits output

Start, signal and follow a run

sov functions workflows start checkout -i '{"order_id": 7, "email": "ada@example.com"}' --key order-7
sov functions workflows list --status waiting
sov functions workflows get <run-id>                          # status, output and every finished step
sov functions workflows signal <run-id> approval -d '{"approved": true}'
sov functions workflows retry <run-id> --latest               # after fixing a failed step
sov functions workflows cancel <run-id>

Starting twice with the same key returns the first run, so a client can retry the start safely. A signal sent before the run waits for it is kept until it does. Runs belong to the project and environment you start them in. Starting and signalling need the Call functions permission; cancel and retry need Deploy functions.

Rules for workflow code

  • Step names are unique within a run (charge, receipt-1, ship); a loop can build names like email-${i}.
  • The function is called once per step, and finished steps return their saved results, so code between steps must take the same path every time. Put anything random, time-based or external (Date.now(), ids, API calls) inside step.run.
  • A step's result is JSON of at most 256 KB, and a run's saved results at most 4 MB. Keep large data in a document or a file and return its id.
  • A step that throws is retried with backoff (2 s doubling, at most 10 minutes apart) up to the plan's tries; then the run is FAILED. Fix the code and retry it: only the failed step runs again.
  • A run stays on the function version it started with, so a deploy never changes the code under a run halfway. retry --latest moves a failed run to the live version.
  • Each call is one invocation (context.idempotency_key is unique per call). Like a trigger, it runs with the function's own identity (context.caller.role == "workflow"), never the starter's session.
  • Deleting the function cancels its unfinished runs.

Run a workflow locally

sov functions serve gives functions a context.step that runs every step inline in one call: sleeps are skipped and waitForSignal returns null, or the data you send in the X-Sovaria-Signals header.

curl -X POST http://127.0.0.1:8787/functions/v1/checkout \
  -H 'X-Sovaria-Signals: {"approval": {"approved": true}}' -d '{"order_id": 7}'

Plans

FreeProUltraTeamEnterprise
Unfinished runs per project101,0005,00020,000100,000
Steps per run252005001,0002,000
Longest sleep or signal wait1 day7 days30 days90 days365 days
Finished runs kept1 day7 days14 days30 days30 days
Tries per step38101215

Each step counts as one invocation and its compute; sleeping and waiting cost nothing. See Plan limits.

Something unclear or missing on this page?Tell us