Durable background work,
in plain async .
Waymark lets you write workflows as regular code.
Compile backend logic into deterministic programs and run them on a durable, interruptible VM backed by Postgres.
This lets your logic survive restarts, crashes, and week-long sleeps. All without any broker, inefficient replays, or errors.
Bye bye scaling RabbitMQ.
import asynciofrom waymark import Workflow, action, workflow @actionasync def charge(order_id: int) -> bool: return await payments.charge(order_id) @workflowclass Checkout(Workflow): async def run(self, order_id: int) -> str: for _ in range(3): if await charge(order_id): return "paid" # durable: survives restarts await asyncio.sleep(3600) return "failed"01Your backend logic is simpler (and safer) when it's durable.
Backend logic is complex enough already. You're integrating your own services, 3rd party API calls, objects from your database, etc. It's easy to do that in a simple script, but the second you hit real production infrastructure it becomes a lot more difficult.
How do you deal with load balancing across multiple boxes? How do you keep going even when containers crash and restart, without redoing a non-idempotent action?
Historically you'd need to hand roll connectors, or constrain your logic to a 3rd party language. Durable execution flips that script. You write the whole flow as one ordinary function, and the engine remembers its state. Crashes, restarts, and hour-long waits simply resume where they left off.
02What you get out of the box.
- 01 · No surprise errors
If it compiles, it runs as advertised.
Waymark verifies your workflow when you register it. Nondeterministic calls like datetime.now() or uuid4() are rejected with a recommendation to move them into an @action. If it runs it's good to go.
Compilation lets us have ulta-fast restores after sleeps since we also don't need to hydrate variable state in an interpreted runtime.
- 02 · No double charges
Safe logic even in a crash.
Without waymark, jobs that die halfway risk getting retried from the top. Unless every action has no side-effects, that's a problem. Waymark saves each action's result the moment it finishes. After a restart it resumes at the step that was interrupted.
- 03 · Plain async Python
Real control flow, no DSL.
Workflows support most native logic in your language: loops, conditionals, try/except, gathers to name a few. Actions, of course, can do anything you want.
You worry about writing ordinary async Python, we'll worry about running it as miniature VMs distributed across every core.
- 04 · Just Postgres
No broker, no second system.
Waymark runs on the Postgres you already have. It scales to surprisingly heavy load out of the box and benefits from the same Postgres tuning you already do. No Redis, no hosted queue, no extra service to deploy and babysit.
- 05 · Management dashboard
See every run, live.
A beautiful web dashboard ships in the box. Watch workflows progress in real time and drill into any run to see success and failure. In addition to your logs it's a very powerful tool to see what's happening behind the scenes.
03Add it and go.
$ uv add waymark$ export WAYMARK_DATABASE_URL=...$ uv run waymark-executor→ workers ready, queue a workflow04One engine for every language.
Following the work on GitHub: TypeScript · Go · Rust
05Questions.
- How is this different from Temporal or Vercel Workflows?
- Unlike Waymark, both are replay-based. After a restart they rebuild a workflow's state by running its code again against an event history, so every step before the crash needs to re-populate memory with active vars. Waymark persists just the state it needs at each step and resumes from there. Because nothing replays, determinism is verified when you register the workflow instead of surfacing in production.
- Do I need Redis or a message broker?
- No. Waymark runs on the Postgres you already have, driven by a Rust runtime that handles the queueing and scheduling for you.
- But *technically* my actions could crash half way through and need to be idempotent anyway right??
- Yes - technically true! But because we register when actions start/finish, we leave that choice up to you. If something is risky to re-run naively, set its max_runs to 1 and write an Exception wrapper to manually rollback from the corrupt state.
Make your background jobs
unbreakable.
Plain async Python, durable on the Postgres you already run.
Get the updates.
New features, release notes, and durable-execution deep dives for Waymark. Occasional, no spam.