Quickstart
This walks through the smallest possible Waymark program: one workflow, two actions, run end-to-end against a local Postgres. The full example app is what you should crib from for a real deployment - this is just the shape.
Install
Waymark ships as a single Python package. Install it with whichever package manager you prefer:
uv add waymark
Two binaries land on your PATH:
waymark-start-workers- the worker pool that executes actions.waymark-bridge- the gRPC bridge between your Python process and the Rust runtime. The Python SDK boots one of these automatically the first time youawait workflow.run(...), so you usually don't run it by hand.
You'll also need a Postgres database. Anything 14+ works; for local development a Docker container is fine:
docker run --rm -p 5432:5432 \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=waymark \
postgres:16
Define a workflow
Create workflows.py. Two actions, one workflow:
import asyncio
from waymark import Workflow, action, workflow
@action
async def compute_factorial(n: int) -> int:
total = 1
for value in range(2, n + 1):
total *= value
return total
@action
async def compute_fibonacci(n: int) -> int:
previous, current = 0, 1
for _ in range(n):
previous, current = current, previous + current
return previous
@action
async def summarize(*, n: int, factorial_value: int, fibonacci_value: int) -> str:
return f"{n}! = {factorial_value}; fib({n}) = {fibonacci_value}"
@workflow
class ParallelMathWorkflow(Workflow):
async def run(self, number: int) -> str:
# Fan out: factorial and fibonacci run in parallel.
factorial_value, fibonacci_value = await asyncio.gather(
compute_factorial(number),
compute_fibonacci(number),
return_exceptions=True,
)
# Fan in: a single summary action returns the result.
return await summarize(
n=number,
factorial_value=factorial_value,
fibonacci_value=fibonacci_value,
)
There's nothing magic here. @action marks distributed work - the units
that get sent to a worker, retried on failure, and timed out if they
overrun. @workflow marks the durable control flow - the part that gets
parsed into a DAG. asyncio.gather is recognized by the compiler as a
parallel fan-out; the await inside actions is just regular await.
Boot the worker pool
Point the workers at your database and at the module where the actions live:
export WAYMARK_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/waymark
export WAYMARK_USER_MODULE=workflows # or "yourpkg.workflows"
uv run waymark-start-workers
WAYMARK_USER_MODULE tells each worker which module to preload, so it has
the action handlers registered before it starts pulling from the queue.
Kick off a run
From any Python process - a FastAPI route, a script, a notebook:
import asyncio
from workflows import ParallelMathWorkflow
async def main() -> None:
workflow = ParallelMathWorkflow()
result = await workflow.run(7)
print(result)
asyncio.run(main())
The first call boots a singleton waymark-bridge inside the process and
queues the workflow into Postgres. The bridge waits for completion and
returns the action's result. From here you can plug workflow.run(...)
straight into a FastAPI handler:
from fastapi import FastAPI
from workflows import ParallelMathWorkflow
app = FastAPI()
@app.post("/compute/{number}")
async def compute(number: int) -> str:
return await ParallelMathWorkflow().run(number)
Where to look next
- The example app
bundles a FastAPI UI, Postgres, and the worker daemon as a single
docker compose up. It's a complete shape for what production deployment looks like. - Python authoring patterns covers the
Python-specific surface - pydantic for inputs and outputs,
Dependsfor dependency injection, and how the worker discovers your modules. - Workflows & Actions walks through the split between durable control flow and distributed work in more depth.
- Control Flow covers what AST patterns the
compiler supports - loops, conditionals, parallel fan-out, durable
sleep,
try/except. - Retries & Timeouts covers how to configure resilience when actions flake.
- Configuration is the full reference for the
WAYMARK_*environment variables you can set.