# Workflows > Source: `src/content/docs/actors/workflows.mdx` > Canonical URL: https://rivet.dev/docs/actors/workflows > Description: Build durable, replayable run loops in Rivet Actors with steps, queue waits, timers, and rollback. --- Use workflows for durable, multi-step execution with replay safety. ## What are workflows? A workflow is a durable, replayable run handler for a Rivet Actor. - Survives restarts: workflow progress is saved automatically. - Re-runs safely: replay follows the same recorded steps. - Event-driven: workflows can pause for queue messages, then continue. ## Getting started ### Simple workflow Use this when you need a short multi-step sequence. ### Loops This is the recommended workflow shape for most actor workloads. - Use a queue wait inside the loop to receive the next unit of work. - Keep actor state changes in a single workflow loop. - This gives you one durable workflow that manages all actor progress. ### Setup & teardown Use this when the workflow should initialize resources, process queued commands, then clean up. ## Features ### Queue Use this for fire-and-forget commands where the client does not need a reply. Use the `Loops` example above as the baseline pattern. ### Request/response (using queue) Use this when the caller needs a response from queued processing. ### Timers Use queue messages as the trigger source, then sleep durably inside the workflow. ### Join Use `join` when several independent tasks can run in parallel. ### Race Use `race` when you need first-winner behavior. ### Timeouts Use step timeouts and retries for slow or flaky dependencies. Step timeouts are critical by default and fail immediately. Set `retryOnTimeout: true` if a timeout should retry like any other error using `maxRetries`. Workflows use roll-forward semantics everywhere. When a step throws, any `state` or `vars` mutations it made before failing are never rolled back, whether the step retries or the failure is caught by `tryStep` or `try`. The next attempt observes whatever the failed attempt already wrote, so write steps idempotently: check before you increment, or move the mutation after the fallible work. ### Handling terminal failures as data Use `tryStep` when a step failure should produce data instead of failing the whole workflow. Use `try` when you want to recover from terminal `step`, `join`, or `race` failures inside a named block. ```ts async function runPaymentFlow(ctx: any) { return await ctx.try("payment-flow", async (blockCtx: any) => { const auth = await blockCtx.step("authorize", async (blockCtx) => authorizeOrder("order-123"), ); const capture = await blockCtx.step("capture", async (blockCtx) => captureOrder("order-123"), ); return { auth, capture }; }); } async function authorizeOrder(orderId: string): Promise { return `auth-${orderId}`; } async function captureOrder(orderId: string): Promise { return `capture-${orderId}`; } ``` - `tryStep` and `try` only catch terminal failures. Retry backoff, sleeps, queue waits, eviction, and history divergence still rethrow. - Catching a failure does not undo it. `state` and `vars` mutations made before the failure remain visible after `tryStep` or `try` returns, so use explicit compensating steps when a caught failure needs cleanup. - `RollbackError` is not caught by default. Pass `catch: ["rollback"]` when you want rollback failures returned as data. ### Error hooks Use `onError` when you want a best-effort notification for workflow failures. - Step failures include the attempt number, retry counts, whether the step will retry, and the next retry delay. - Workflow failures also include terminal errors outside steps, such as rollback failures or code/history mismatches. - The hook is observational. It is not part of workflow replay, so use it for logging, metrics, or updating non-critical actor state. - This is also a good place to forward workflow failures to Sentry or another error reporting pipeline. ### Rollback Use rollback checkpoints before steps that have compensating actions. ## Patterns ### Store workflow progress in state + broadcast Store progress in `state` so replay and recovery always restore it. Broadcast state changes so clients can render progress in realtime. ### Cron (queue-driven) Rivet scheduling triggers actions. For cron-like workflows, use a small scheduled action as a bridge that enqueues work, then process that work in the workflow loop. These are common workflow shapes used in production systems. ### Queue-driven worker Use this when external systems enqueue work and the actor should process each item durably. ### Setup & teardown Use this when you need one-time initialization before a long-lived loop, plus cleanup when the actor stops sleeping or is destroyed. ### Human approval gate Use this when an operation must pause for a user or system decision before continuing. ### Fan-out / fan-in (join) Use this when independent work items can run in parallel and you need a single merged result. ### Batch drainer Use this when throughput matters and handling one message at a time is too expensive. ### Coordinator -> worker RPC Use this when one actor orchestrates work by calling actions on other actors. ### Request/response over queue (async RPC) Use this when you want decoupled actor-to-actor communication with durable waits and explicit completion. ### Scatter-gather across actors Use this when multiple actors can process independent parts of a request in parallel, then return a merged response. ### Timeout + fallback actor Use this when a primary actor call might be slow or unavailable and you need a deterministic fallback path. ### Cross-actor saga (compensating actions) Use this when a workflow spans multiple actors and each side effect may need compensation. ### Signal-driven control loop Use this when workflow progress should be triggered by commands/events instead of fixed polling intervals. ### Poll + backoff loop Use this when an external dependency has variable availability and retries should slow down after failures. ### Child worker orchestration Use this when one workflow coordinates many child workers (actors or worker workflows) and manages their lifecycle. ### Bounded drain + concurrency cap Use this when inbound work can spike and you need predictable per-iteration limits. ### Versioned workflow evolution Use this when workflow structure changes across deployments and old histories must still replay. ### Version gates with `getVersion` Use `ctx.getVersion(name, latest)` to branch behavior when you change a workflow's logic while old instances are still in flight. It returns the version this instance is pinned to at that point: - A fresh instance resolves to `latest`. - An instance that already executed past this point under older code resolves to `1` (the implicit floor). The resolved version is recorded in history, so replays are deterministic and each instance stays on the branch it started on. Inside a loop, every iteration resolves independently, so in-flight iterations finish on the old branch while new iterations pick up `latest`. `latest` must be an integer `>= 1`, and the gate name must be unique within its scope like any other entry. Once every old instance has drained, retire the gate by replacing the call with `ctx.removed(name, "version_check")`. ### Checkpoint-friendly loop design Use this when you need reliable replay and resume semantics across crashes and restarts. ## Migrations - Keep workflow entry names stable once deployed. - If an old entry was removed or renamed, call `ctx.removed(name, originalType)`. - To change behavior at a point while old instances are still running, gate it with `ctx.getVersion(name, latest)` (see [Version gates](#version-gates-with-getversion)). - This keeps replay compatible across deployments. ## Step-only access to actor APIs `state`, `vars`, `db`, `client()`, and connection/event APIs are only valid inside `ctx.step(...)` callbacks. Use non-step workflow code for orchestration only: queue waits, sleeps, loops, joins, races, and rollback boundaries. Keep actor-local side effects in steps. ## Debugging - `GET /inspector/workflow-history` returns workflow history status for an actor. - Response includes `isWorkflowEnabled` and `history`. - In non-dev mode, inspector endpoints require authorization. ## Recommendations - Prefer queue-driven loops for long-lived workflows. - Structure long-lived workflows with setup and teardown around the main loop. - Keep actor state changes and side effects inside steps. - Store workflow progress in `state` and broadcast updates as progress changes. - Use timeouts and rollback for external side effects. - Write step bodies idempotently. `state` and `vars` mutations from a failed attempt are never rolled back, whether the step retries or `tryStep`/`try` catches the failure. _Source doc path: /docs/actors/workflows_