flow
Compare

Qoqi Flow vs Workflow SDK

Durable without a sandbox, a compiler plugin or a host.

Vercel's Workflow SDK compiles "use workflow" functions into a sandbox that is replayed from an event log: a logical clock, a seeded Math.random, no fetch, no timers, no Node modules. Each run is pinned to the deployment that started it. Qoqi Flow is a whole language that never replays, moves to new code at a stage, and runs anywhere.

Try it in five minutes See the code

No sandbox

Workflow SDK takes Date, Math.random, fetch, setTimeout and Node modules away from workflow code. A Qoqi flow keeps the whole language; only reaching the world needs a step.

Deploy without keeping the old one

A Workflow SDK run needs the deployment that started it to stay up for its whole life, or it must be re-run. Qoqi instances move to new code at a stage.

Not tied to a platform

Built for Vercel, with a Postgres World to self-host. Qoqi Flow is one binary, on any machine, from SQLite to PostgreSQL.

Wait for an answer, or seven days

A hook raced against a sleep, and a route of your own to resume it; or one wait match.

Workflow SDK · wait.ts11 lines
using hook = createHook<Answer>({ token: `expense:${id}` });const answer = await Promise.race([  hook,  sleep("7d").then(() => "expired" as const),]);if (answer === "expired") return { status: "expired" };// and a route of your app, which must check the caller itself:// the token routes the answer, it does not authorize itawait resumeHook(`expense:${id}`, { approved: true, by: "grace" });
Qoqi Flow · wait.qi10 lines
let answer = wait match {    case let a = self.answers.receive(): a;    case time.after(7.days()): null;};if answer == null {    return Outcome.Expired;}// $ qoqi send expense:1 answers '{"approved": true, "by": "grace"}'// or POST /api/instances/expense:1/inboxes/answers, with the daemon's token

Plain code, because nothing replays

Workflow SDK swaps the clock and randomness for replayable stand-ins. Qoqi uses the real ones.

Workflow SDK · sandbox.ts16 lines
export async function renewal(user: User) {  "use workflow";  // Runs in a VM and is replayed: Date is a logical clock,  // Math.random is seeded, and there is no fetch, no setTimeout,  // no Node module. Everything real goes in a "use step" function.  if (Date.now() < user.trialEnds) {    await sleep("2d");  }  const tip = user.tips[Math.floor(Math.random() * user.tips.length)];  await charge(user.id, tip);}async function charge(userId: string, tip: string) {  "use step";  // ...}
Qoqi Flow · plain.qi11 lines
impl Flow<Void> for Renewal {    fn run() {        // The clock and chance are plain code: read once, never again        if time.now() < self.user.trialEnds {            wait time.after(2.days());        }        let tip = self.user.tips[random.below(self.user.tips.len())];        charge(self.user.id, tip);    }}

Side by side

Workflow SDKQoqi Flow
How it comes backReplays its event logIts state thawed, at the line it was at
Workflow codeA sandbox: logical clock, seeded random, no fetch, timers or Node modulesThe whole language
MistakesFound when the bundle loads, or when the call runsFound when it compiles
New code under running workflowsPinned to its deployment, kept up until it endsInstances move at a stage, checked against flow.lock
A step that must never run twiceSteps retriedSettled<T>: never run again; the flow is told Unknown and decides
Crash testingNot providedeveryCrash: the test again, stopped after every write, same ending required
Steps in your own servicesTypeScript; Python in betaAny server that speaks the protocol: a signed HTTP POST. SDKs for Go, PHP, TypeScript
Where it runsVercel; a Postgres World to self-hostAnywhere: one binary

Questions

Is Qoqi Flow ready for production?

It is at v0.1.0. The language, the runtime and the daemon work end to end, and they are tested by crashing them on purpose after every write. What it does not have yet is years in production. Start with a flow that matters, not the one that holds the company up.

How do Workflow SDK's ideas map?

"use workflow": impl Flow. "use step": step fn. createHook and resumeHook: an Inbox field and qoqi send. sleep: wait time.after(...). Promise.race: wait match. FatalError and RetryableError: Result and flow.retry.

Do I have to rewrite my services in Qoqi?

No. Only the flow is Qoqi. A step that needs your code stays in your service: any server that answers Qoqi's protocol, a signed HTTP POST. qoqi gen writes the typed interfaces for Go, PHP and TypeScript; any other language implements the protocol in a page of code.

Where does it run?

Anywhere one binary runs. SQLite on one machine; PostgreSQL or MySQL when several daemons share the work. There is no managed cloud yet.

Also compared

Write the process. Let it wait.

$ curl -fsSL https://qoqi.dev/install.sh | sh

Quickstart GitHub