Skip to main content

Workflow

vercel.workflow provides Vercel Workflows primitives: Workflows, workflow registration, step registration, durable sleeps, hooks, and start.

pip install vercel-workflow

Basic Workflow

from vercel.workflow import Workflows, sleep, start

app = Workflows()


@app.step
async def charge_customer(customer_id: str) -> None:
    ...


@app.workflow
async def renew_subscription(customer_id: str) -> None:
    await sleep("1h")
    await charge_customer(customer_id)


async def main() -> None:
    run = await start(renew_subscription, "cus_123")

app.workflow registers async workflow functions. app.step registers async steps that can be called only from inside a workflow. sleep() creates a durable wait in a workflow run.

Queue namespaces

Pass a namespace to a workflow registry to isolate its messages on a dedicated queue topic:

workflows = Workflows(namespace="billing")

Hooks

from dataclasses import dataclass
from vercel.workflow import BaseHook, Workflows

app = Workflows()


@dataclass
class Approval(BaseHook):
    approved: bool


@app.workflow
async def wait_for_approval() -> bool:
    approval = await Approval.wait()
    return bool(approval and approval.approved)

BaseHook supports dataclasses and Pydantic models for external resume events.

Streaming

Every run has a stream a step can write to while it runs, so a client sees progress without waiting for the run to finish:

from vercel.workflow import Workflows, get_writable

app = Workflows()


@app.step
async def summarize(*, document: str) -> str:
    writable = get_writable()
    summary = []
    async for token in llm.stream(document):
        await writable.write(token)
        summary.append(token)
    return "".join(summary)


@app.step
async def done() -> None:
    await get_writable().close()


@app.workflow
async def analyze(*, document: str) -> str:
    summary = await summarize(document=document)
    await done()
    return summary

A workflow body can call get_writable() too and pass the result to its steps, which take it as a WorkflowWritable and write to it:

@app.step
async def summarize(*, document: str, out: WorkflowWritable) -> str:
    await out.write("starting")
    ...


@app.workflow
async def analyze(*, document: str) -> str:
    out = get_writable()
    return await summarize(document=document, out=out)

Only a step can write. Calling write() on what the workflow body holds raises.

Chunks are values, not just bytes: anything the payload format carries (see below) can be written, and a reader gets it back. A bytes chunk arrives on the TypeScript side as a Uint8Array, so a consumer there can pipe the stream straight into a Response.

Three things are worth knowing:

  • Nothing closes a stream for you. Not the end of a step, not the end of the run — the stream spans steps, and a closed stream cannot be reopened. A reader of a stream nobody closes waits until the run expires, so close it from the last step that has something to say.
  • A step is not complete until its chunks are. write() returns once the chunk is buffered, and the step handler flushes before recording the step, so "the step finished" implies "everything it streamed is readable". Call await writable.drain() if you need that guarantee earlier.
  • Retries re-stream. A step that fails halfway has already written what it wrote, and the retry writes it again. Keep chunks idempotent, or stream from a step you are willing to see repeated.

await writable.write_from(source) forwards an async iterable in one call, and get_writable(namespace="logs") gives the run a second, independent stream.

async with get_writable() as writable: closes the stream on the way out — on the clean path only, so a step that raises leaves the stream open for its retry.

Reading it back

run.readable() yields the values as they are written, and ends when a step closes the stream:

run = await start(analyze, document=text)

async for chunk in run.readable():
    print(chunk)

run.readable_bytes() is the same thing narrowed to bytes, which is what an HTTP body wants — hand it to a streaming response as-is.

Pass start_index to resume: a positive index picks up exactly where a client left off, a negative one reads that many chunks back from the end. Only the positive form survives a dropped connection, because a negative index resolves against wherever the tail happened to be when it connected.

async for chunk in run.readable(start_index=last_seen + 1):
    ...

A read reconnects on its own when the transport drops, which it will: the server ends a long read at its own time limit. Resuming is exact, so nothing is duplicated or skipped.

run.stream_info() gives the last chunk index and whether the stream is closed (tail_index is -1 before anything is written), run.list_streams() lists every stream the run has, and read_stream(run_id, name) reads one by name.

The same stream is readable from the TypeScript SDK (run.readable), the dashboard, and workflow inspect stream <id> --run=<run-id>.

Serializing your own types

Workflow inputs, step results and hook payloads travel in the devalue format @workflow/core uses, which carries datetime, bytes, set and repeated references natively. Decimal, UUID, date, time, timedelta and Path are registered on top of that; anything else needs a registration:

import enum

from vercel.workflow import serializable


@serializable
class Point:
    def __init__(self, x: int, y: int) -> None:
        self.x, self.y = x, y

    def _workflow_serialize(self) -> dict[str, int]:
        return {"x": self.x, "y": self.y}

    @classmethod
    def _workflow_deserialize(cls, data: dict[str, int]) -> "Point":
        return cls(**data)


@serializable          # an Enum needs no methods
class Tier(enum.Enum):
    PRO = "pro"

register_serializable() is the function form, for classes you cannot decorate.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vercel_workflow-0.9.0.tar.gz (103.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vercel_workflow-0.9.0-py3-none-any.whl (112.7 kB view details)

Uploaded Python 3

File details

Details for the file vercel_workflow-0.9.0.tar.gz.

File metadata

  • Download URL: vercel_workflow-0.9.0.tar.gz
  • Upload date:
  • Size: 103.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.4 {"installer":{"name":"uv","version":"0.12.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for vercel_workflow-0.9.0.tar.gz
Algorithm Hash digest
SHA256 88f3483e2ea3595e02db48741af6de8e545dd574146453295d66a5a6a06d44da
MD5 61e9095382b9126d289c1d76c894eab2
BLAKE2b-256 df91e852e0803d45c06c0f493d1b96fe8b1e6a7246f971889d47182e7722b547

See more details on using hashes here.

File details

Details for the file vercel_workflow-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: vercel_workflow-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 112.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.4 {"installer":{"name":"uv","version":"0.12.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for vercel_workflow-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9da5ee97f0460e87ec185b107166c66e57d93e11d82e6953a22ed994888e93af
MD5 0269b0ab2719870b11246c1df8009b27
BLAKE2b-256 a89f84892f5e2bc4d4d44a2823d1fd5dc1f72fe91e9921c419631d4fd56df71c

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.9.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page