Top
Best
New

Posted by icyfox 3 days ago

Show HN: Waymark – A workflow engine that compiles jobs instead of replaying(github.com)
Hey everyone! @mozgiii and I have been working on Waymark for the better part of the last year. Waymark’s an open source durable execution library with similar functionality to Temporal or DBOS.

I fell in love with durable execution a few years ago, after working at a company with a heavy pubsub architecture. In comparison to job orchestrators that force a certain task design or special syntax, many of these durable libs lets you write business logic in regular code: `asyncio.gather` to parallelize tasks, `for` loops, conditionals, and let the runtime figure out how to run them efficiently across a cluster of nodes. But most of the current solutions require a heavy broker, runtime hacks to try to ensure deterministic workflow logic, and feel like you have to learn a whole new ecosystem just to get started.

A few unique design decisions in waymark:

— Write jobs in normal code (right now Python but JS coming soon). Instead of running control flow as-is, which requires booting up an interpreter to replay past state, we’ll just compile your code directly. We walk your AST and map your logic to our stackless VM bytecode. We support variables, conditionals, await, sleep, helper functions, loops... basically what you’d expect in workflow logic.

— Since we’re compiling a language subset, we can proactively flag errors in your workflows locally instead of when they run in production.

— It only requires Postgres as the task broker. We’ve spent a lot of time tuning our design to play nicely with its strengths and limitations; our current benchmarks show around 1500 workflows/second but we still have some more optimization work to do here.

— Actions plug-and-play with FastAPI’s dependency injection syntax. So if you already have an ecosystem based on typehinting/DI, it should fit like a glove.

It allows for parallelization of workflows across one machine or a whole cluster. The bulk of the work per node is done by our central Rust core - which distributes workflows to properly saturate all cores. The idea is to be as batteries included as possible with the broker, webapp, and runtime.

What to use it for? It works great as the runner loop controlling agents, but it also works well for basically any task that you need to kick off in the background. It’s largely replaced our way of writing api code. Most frontend actions at this point just push to a simple REST endpoint that kick off a background job.

I spent an embarrassing amount of time making our https://waymark.sh site interactive - it’s probably the best place to learn more. We’re starting with Python support and looking for community input on what language to ship next. Would love to hear your thoughts!

83 points | 46 commentspage 2
kubenest 1 day ago|
looks a lot like temporal
MOZGIII 8 hours ago||
Yep, but also different - no replays
mukeshsinghnyc 23 hours ago||
Agree I asked same question
actionfromafar 1 day ago||
Dotnet CIL + sqlite please?
MOZGIII 21 hours ago|
Well, re sqlite, we're so reliant on postgres that it's hard to imagine sqlite right now. If we'd want to replace postgres with something, I'd rather look into redb and the like first...

As for CIL - we should add it at the "What should we build next?" section on the website :)

dangistan202611 1 day ago||
This looks like something that will break easily with a newer version of Python or even with the use of third-party packages. It seems to couple too tightly with the user's application code.
dangistan202611 1 day ago||
It is too async heavy as per its readme -- this is very off-putting. Not everyone cares for async. There is no usage shown with sync code.
MOZGIII 21 hours ago||
Well, yeah, today the sync code would be awkward to use - possible, but hacky.

With the way our current Python workers are implemented you'd have we'd have to place the sync code onto threads manually.

We could make it happen though, if there is a demand for sync code; we can allow actions to be non-async, and from that run them in sync mode - either with an sdk-level offload to threads off-of async reactor, or on a separate worker pools that run fork-per-action. From the perspective of our VM, we don't really have a difference there...

icyfox 1 day ago|||
MIT licensed, just clarified in the repo.

As for async - background jobs are better modeled as async because of their natural parallelism across nodes imo. Logic in actions themselves are free to be as synchronous as you want. Why the preference?

dangistan202611 1 day ago||
I dispute the "better" assertion. Anyhow, I don't know how familiar or not you are with sync code, but it fully supports background jobs using queues with threads/interpreters/processes. It has been around for decades, far longer than async.

Also, it is okay to run something in the foreground. Not everything needs to run in the background. Sometimes it's better to wait and have assurance of completion.

There is no natural parallelism of async code in Python. The coroutines run in a single thread.

9dev 1 day ago||
I don’t understand these kinds of strong emotions async python triggers in some people. It reminds me of the irrational hate towards systemd in the Linux world.

This is a platform for running workflows distributed on multiple machines, sometimes long-running and with lots of IO. Every single contender in the space advertises the same code, from temporal to hatchet to dagster. Because it makes sense to write it this way.

dangistan202611 1 day ago||
Huh. I hate neither async nor systemd. There is nothing wrong with async, but if my code is sync, what am I to do?

Also, the claim made that something is "better modeled as async" is wholly unqualified.

whilenot-dev 1 day ago||
> if my code is sync, what am I to do?

You'd use asyncio.to_thread[0].

I also don't quite get your complaint... asyncio provides a high-level concurrency primitive with coroutines, and that's a good enough primitive for workflow engines. Even with asyncio you could fork/spawn processes easily with aiomultiprocess[1], if you'd ever want or need to.

What's your use case for having more low-level concurrency primitives (threads/processes) available, having to deal with their synchronization, and running workflows sync?

[0]: https://docs.python.org/3/library/asyncio-task.html#asyncio....

[1]: https://aiomultiprocess.omnilib.dev/en/stable/

dangistan202611 1 day ago||
> having to deal with their synchronization

No such thing is typically necessary. If modeled correctly, the need for synchronization is minimal.

With sync code, tasks run on different threads/interpreters/processes. Message passing via queues is used to communicate which tasks need to run. Many asyncio users seem to wholly unaware of how well it works. Go-lang doesn't even have or need async.

I actually like asyncio; I am just not a religious zealot about it as many here seem to be, also without awareness of the struggles they face with loop-hogging coroutines and multicore parallelism.

_1tan 1 day ago||
Any plans for Java?
KMan2006 1 day ago||
How do you handle code changes for workflows already in flight? Say an instance is sleeping inside a loop for three days and someone deploys a new version of that loop. Is the compiled bytecode pinned per instance, or do you map the old position onto the new code?
MOZGIII 21 hours ago|
TLDR: pinned per instance.

When we compile a workflow into bytecode we get an executable; we store it and assign it an executable id. In-flight workflows are technically VM instances, and they always pin to a concrete executable id. When the VM goes out of residency (i.e. evicted from a node that executes it and unloaded from memory) we will eventually gc the executable bytecode from memory if it's not used by any VM for a while - but it's kept in postgres so VM can be revived.

There are ways to make workflows upgradeable - but don't have this yet. Ideally, we'd want to build it once we have enough feedback on how people want to do this - cause it's really about choosing the trade-offs more than anything else.

drrajvanshi 1 day ago||
[flagged]
vancekai 1 day ago||
[dead]
0xmattclark 1 day ago||
[flagged]
rts-gordon 1 day ago|
[flagged]