Durable Functions

Resume long-running workflows without repeating finished steps

Write an agent run or background job as one durable function: Volcano saves step results so work can resume after a crash, a long wait, or human approval.

Durable Functions product screenshot
durable
volcano cloud durable deploy --all[1/1] Deploying order-pipeline...  Runtime: nodejs24.x✓ 1/1 durable function(s) deployment startedvolcano cloud durable start order-pipeline --input '{"order_id":4417}' --name order-4417✓ Execution order-4417 startedvolcano cloud durable executions get order-pipeline 9a8b7c6d-…Status  succeeded   Duration  16m 53s# fifteen of those minutes were a wait, and a wait holds no runtime
Overview

Keep a failed step from restarting the whole job

A tool call fails halfway through an agent run. An import loses its place. An approval takes longer than a request can stay open. Each needs a way to save progress and continue later.

A durable function checkpoints each step. Wrap the handler in durable() and put work inside ctx.step. On resume, Volcano reuses the saved results of finished steps and continues the rest. Code between steps runs again, so keep changing values and external calls inside steps.

Saved progress

Resume with the results you already have

ctx.step saves its result when it finishes. After a crash or wait, the execution reuses that result instead of repeating the work. For a payment or email that should not repeat, pair atMostOnce with retry: false to checkpoint before running it.

  • Retry failed steps with exponential backoff
  • Disable retries for work that should run at most once
  • Process items concurrently with a ctx.map concurrency cap
order-4417, resumed after a failed attempt
step  load-order         succeeded      84 msstep  charge             succeeded     412 mswait  settle             15m           nothing runningmap   pack · 12 items    succeeded     concurrency 5step  dispatch           failed        retrying, attempt 2✓ resumed at dispatch · the charge was not repeated
Human approval

Wait for a person before the next action

ctx.waitForApproval pauses the workflow until someone approves, denies, or the request expires. Review the attached details under Approvals in the dashboard, and the workflow receives the decision and comment. Project access tokens cannot approve requests.

  • Approve from the dashboard, CLI, API, or SDK
  • Set a deadline and handle an expired request
  • Track approval counts by workflow
volcano.dev/dashboard/durable-functions/approvals
An approval request open in the dashboard, with its details, a comment and the approve and deny buttons
Execution history

See where a workflow is waiting

Open an execution to inspect its status, read its result, or send a message it is waiting for. Use the dashboard, CLI, or SDK to follow runs and stop them when needed. Stopping does not undo completed work.

  • List executions by status
  • Inspect open waits and message history
  • 30-day result retention
volcano.dev/dashboard/durable-functions
A running execution with the messages it waits for, a message being sent and its message history
Capabilities

Build workflows around steps, waits, and retries

JavaScript and Python

Write steps, waits, and parallel work on Node.js 22 or 24, or Python 3.13 or 3.14.

Up to 366 days per execution

Keep a workflow open across resumes and waits. Each attempt between checkpoints gets 300 seconds on Hobby or 900 on Superagent.

Retries with backoff

Retry a failed step with increasing delays. Choose the attempts, delays, and errors to retry, or turn retries off.

One execution per name

Use the execution name as an idempotency key: retrying a start returns the existing execution without another execution charge.

Cron schedules on Superagent

Start workflows on a cron schedule. Each tick gets its own execution name, so retrying a tick reuses its execution.

Waiting costs no compute

A suspended execution adds no compute charge. Executions, operations and GB-seconds each have their own monthly allowance.

Code

Write and run a durable workflow

JavaScript
const { durable } = require('@volcano.dev/sdk/durable'); exports.handler = durable(async (input, ctx) => { const order = await ctx.step('load-order', () => loadOrder(input.order_id)); // Checkpointed before it runs and never retried, so an attempt that dies // mid-charge cannot bill the customer twice. const charge = await ctx.step('charge', () => chargeCard(order), { atMostOnce: true, retry: false, }); // Let the charge settle. Nothing runs while the execution waits. await ctx.wait('settle', '15m'); const packed = await ctx.map( 'pack', order.items, (item, itemCtx) => itemCtx.step('pack-item', () => packItem(item)), { concurrency: 5 }, ); packed.throwIfFailed(); // Large orders wait for a person. The request appears under Approvals, and // nothing runs until someone decides or three days pass. if (order.total_cents > 100_000) { const review = await ctx.waitForApproval('ship-order', { title: `Ship order ${order.id}?`, description: 'The total is over the auto-ship limit of $1,000.', details: { order_id: order.id, total: order.total_cents / 100, items: order.items.length, destination: order.destination, }, timeout: '3d', }); // Denied or expired if (!review.approved) { await ctx.step('refund', () => refund(charge.id), { atMostOnce: true, retry: false }); return { order_id: order.id, outcome: 'refunded' }; } } await ctx.step('dispatch', () => dispatch(order, packed.results)); return { order_id: order.id, outcome: 'shipped' }; });
Use cases

What people run as durable functions

one order, three days
mon 09:12  charge        succeededmon 09:12  settle        waited 15mmon 09:27  pack · 3      succeededmon 09:27  ship-order    waiting on a person, nothing runningwed 16:40  ship-order    approved · "stock checked"wed 16:40  dispatch      succeeded · outcome shipped
Platform

Works with the rest of Volcano

Functions and agents
  • Reuse deployed functions

    Call a standard function from a step to reuse existing endpoints in a longer workflow.

Databases and vector
  • Wait for a database change

    Keep job state in Postgres and check it with ctx.waitUntil while your app updates the row.

Authentication
  • Carry the user’s identity

    Start with a signed-in user’s session and object input to receive their verified identity in the handler.

Realtime
  • Show workflow progress live

    Broadcast progress from a step to connected clients so the page updates without polling the execution.

Frequently asked questions

Read the docs
What is a durable function?

A durable function saves step results so a workflow can resume after a crash or wait without repeating completed work. Starting one returns an execution handle immediately while work continues in the background. Unlike a standard function handling one request, an execution can span up to 366 days and 3,000 operations.

Can I write durable workflows in JavaScript or Python?

Yes, write durable workflows in JavaScript on Node.js 22 or 24, or Python 3.13 or 3.14. Both support steps, waits, and parallel work through the Volcano SDK. Ruby applications can start and read executions, but cannot author durable handlers.

Do durable functions cost money while waiting?

A suspended execution adds no compute charge while it waits. Each timed wait still counts as one operation, and the execution holds a concurrent-execution slot until it ends. Step attempts and polling checks also count as operations.

How do I add human approval to an AI agent workflow?

Call ctx.waitForApproval before the action that needs review, then branch on the decision. A person approves or denies the request from the dashboard, CLI, API, or SDK. Set a timeout to handle unanswered requests. Use ctx.waitForMessage instead when a webhook receiver or another function should answer.

How many durable functions can run at once?

A project can have 10 concurrent executions on Hobby or 100 on Superagent, including suspended executions. Each has 128 MB of memory on Hobby or 1 GB on Superagent. Executions, operations, and compute have separate monthly allowances.

Can I test durable functions locally?

Yes, volcano start runs durable functions locally with the same handler and volcano-config.yaml used for deployment. Timed waits resolve immediately unless LOCAL_DURABLE_REAL_TIME=true is set. Approval, message, and callback timeouts always use real time.

Run your first durable workflow

Build, deploy, and scale on Volcano's global platform — free to start, with no infrastructure to manage.

Checkout more features