Python
The Python SDK is currently in beta. APIs and behavior may change.
You can build durable workflows in Python using the vercel-workflow SDK. Your workflow code can pause, resume, and maintain state, just like the JavaScript and TypeScript Workflow SDK.
Getting started
Add the vercel-workflow package and workflow entrypoint to pyproject.toml:
The workflow entrypoint uses the module:object format and points to the exported Workflows registry.
Workflows
A workflow is a stateful function that coordinates multi-step logic over time. Create a Workflows instance and use the @wf.workflow decorator to mark a function as durable:
share_sandboxes=True reuses a sandbox across workflow runs for faster startup. Module globals are shared between those runs, so workflow code should not mutate global state.
Export the registry from the workflow package and import the module containing your workflow so its definitions are registered:
Under the hood, the workflow compiles into a route that orchestrates execution. All inputs and outputs are recorded in an event log. If a deploy or crash happens, the system replays execution deterministically from where it stopped.
Steps
A step is a stateless function that runs a unit of durable work inside a workflow. Use @wf.step to mark a function as a step:
Each step executes separately from the workflow orchestrator. While the step executes, the workflow suspends without consuming resources. When the step completes, the workflow resumes automatically where it left off.
Cancellable steps
Pass cancellable=True when a workflow may need to stop a running step. This makes normal Python asyncio cancellation apply to the step: if a task awaiting the step is cancelled in the workflow, then the task running the step will be cancelled as well. For example, asyncio.timeout() cancels the step if it does not finish within the allotted time:
Cancellation is a request, so the workflow waits for the step to terminate before continuing. If the step suppresses its cancellation, it can still return normally.
Starting a workflow
Call workflow.start() from server-side code to start a workflow. It returns a Run that you can use to identify the run, check its status, and wait for its result:
Starting a workflow only waits until the run has been created and queued. Await return_value() to wait for the workflow to finish, or save its run_id and recreate the handle later with workflow.Run(run_id).
Sleep
Sleep pauses a workflow for a specified duration without consuming compute resources:
The parameter accepts four forms:
| Form | Description | Example |
|---|---|---|
str | Human-readable duration string | "2 days", "1w", "1h 30m" |
int or float | Seconds from now | 5 (5 seconds) |
datetime.timedelta | Duration from now | timedelta(days=7) |
datetime.datetime | Absolute wake-up time (must be timezone-aware) | datetime(2025, 1, 1, tzinfo=UTC) |
The string form accepts one or more <value><unit> pairs. Supported units:
| Duration | Unit |
|---|---|
| Milliseconds | ms |
| Seconds | s, second, seconds |
| Minutes | m, minute, minutes |
| Hours | h, hour, hours |
| Days | d, day, days |
| Weeks | w, week, weeks |
sleep() must be called from the workflow body, not from inside a step. Calling it from a step raises a RuntimeError.
The sleep consumes no resources. The workflow resumes automatically when the time expires.
asyncio.sleep() may also be used to sleep.
Deterministic workflow helpers
Workflow bodies must be fully deterministic, and so are not allowed to perform operations like reading the system clock or generating randomness using the default generator. Deterministic replacements are provided for use in workflow bodies:
workflow.now()is a deterministic substitute fordatetime.datetime.now. It returns the time that the last workflow event occurred at.workflow.time_ns()is a deterministic substitute fortime.time_ns.workflow.random()returns arandom.Randominstance with a seed based on the run id.
These helpers can only be called from a workflow body. Steps should use the normal system functions.
Hooks
A hook lets a workflow wait for external events such as user actions, webhooks, or third-party API responses.
Define a hook model with Pydantic and workflow.BaseHook:
Resume the workflow when data arrives:
When a hook receives data, the workflow resumes automatically. You don't need polling, message queues, or manual state management.
Streaming
Steps can stream progress while a workflow is running. Streams are associated with workflow runs, can be acquired from inside a workflow or inside a step, and can be passed to workflows and steps as arguments. If a type is specified when getting the stream (or on the type annotation of a step or workflow it is passed to), then Pydantic will be used to encode and decode the type.
Read and validate the typed values from the returned Run as they arrive:
Streams are not closed automatically. It can be manually closed when it is finished so that readers know to terminate.
readable and get_writable also take a namespace parameter, allowing each run to have many distinct streams.
Next steps
- Learn more about the Foundations.
- Check Errors if you encounter issues.
- Explore the API Reference.