Jaal vs enterprise orchestrators
Durable execution engines, workflow schedulers, process automation and integration platforms. Each product checked against its own documentation on 26 October 2026, sources below. Where we could not find a feature in a product's docs we write not documented: that means we did not find it, not that it is missing. For Jaal we say what is built and what is planned. Corrections are welcome.
| Jaal | Temporal | Restate | Inngest | Golem | Resonate | DBOS | Hatchet | Trigger.dev | Conductor | Cadence | Dapr Workflow | Step Functions | Lambda durable | Azure Durable | Cloudflare Workflows | Vercel Workflow | Prefect | Airflow | LangGraph | Ray | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Score, out of 25 | 14½ | 13 | 10 | 8½ | 12 | 8 | 9½ | 9 | 6½ | 10 | 8½ | 8½ | 10½ | 11 | 10½ | 8 | 7 | 8½ | 8½ | 9 | 8½ |
| Plain code: no workflow constructs to write (no activities, steps, decorators, DAGs, BPMN or JSON) | ✓1 | ✗2 | ✗3 | ✗4 | ✗5 | ✗6 | ✗7 | ✗8 | ✗9 | ✗10 | ✗11 | ✗12 | ✗13 | ✗14 | ✗15 | ✗16 | ✗17 | ✗18 | ✗19 | ✗20 | ✗21 |
| Independent calls run in parallel without futures, Promise.all or parallel nodes | ✓ | ✗22 | ✗23 | ✗24 | ✗25 | ✗26 | ✗27 | ✗28 | ✗29 | ✗30 | ✗31 | ✗32 | ✗33 | ✗34 | ✗35 | ✗36 | ✗37 | ✗38 | ✗39 | ✗40 | |
| The execution graph is derived from ordinary code (calls, if, for), not from edges or a definition you write | ✓ | ✗41 | ✗42 | ✗43 | ✗44 | ✓45 | ✗46 | ✓47 | ✗48 | ✗49 | ✗50 | ✗51 | ✗52 | ✗53 | ✓54 | ✓55 | ✗56 | ✓57 | ✓58 | ✓59 | ✓60 |
| Editing the graph rewrites the code | ✓61 | ✗62 | ✗63 | ✗64 | ✗65 | ✗66 | ✗67 | ✗68 | ✗69 | ✓70 | ✗71 | ✗72 | ✓73 | ✗74 | ✗75 | ✗76 | ✗77 | ✗78 | ✗79 | ✗80 | ✗81 |
| A version's identity is derived from its content, and running work stays on it | ✓ | ✗82 | ✓83 | ✗84 | ✓85 | ✗86 | ✓ | ✓87 | ✗88 | ✗89 | ✓90 | ✓91 | ✗92 | ✓93 | ✓94 | ✗95 | |||||
| After a crash, it resumes from the last state: no replay of the code from the start | ✓96 | ✗97 | ✗98 | ✗99 | ✓100 | ✗101 | ✗102 | ✗103 | ✗104 | ✓ | ✗105 | ✗106 | ✓107 | ✗108 | ✗109 | ✗110 | ✗111 | ✓112 | |||
| Compensation built in, not a saga you hand-write | ✓ | ✗113 | ✗114 | ✓ | ✓ | ✗115 | ✗116 | ✗117 | ✓ | ✗118 | ✓ | ||||||||||
| A deploy that would break the contract is refused by the platform, by name | ✓ | ✓119 | ✗120 | ✗121 | |||||||||||||||||
| Compensation declared by the called service; the caller writes no undo code | ✓ | ✗122 | ✓123 | ✗124 | ✓125 | ||||||||||||||||
| A call to another service reads as a plain method call: no stubs, context clients or step wrappers | ✓ | ✓126 | ✓127 | ✗128 | ✓129 | ✗130 | ✓131 | ✗132 | ✗133 | ✗134 | ✓135 | ✗136 | ✗137 | ✗138 | ✓139 | ✗140 | ✓141 | ✓142 | ✗143 | ✓144 | ✗145 |
| A streamed response cut by a crash continues where it left off: no tokens regenerated | WIP146 | ✓147 | ✓148 | ✓149 | ✓150 | ✗151 | ✓152 | ✓153 | ✓154 | ||||||||||||
| No data races in the parallel version, proven by a compiler before the run (not lint rules or run-time checks) | WIP155 | ✓156 | ✗157 | ✗158 | ✗159 | ✗160 | ✗161 | ✗162 | ✗163 | ✗164 | ✓165 | ✓166 | ✓167 | ✗168 | ✗169 | ✗170 | ✗171 | ✗172 | ✗173 | ||
| Non-deterministic calls (clock, random, any library) recorded automatically: no determinism rules | ✓ | ✓174 | ✗175 | ✗176 | ✓ | ✗177 | ✗178 | ✗179 | ✗180 | ✗181 | ✗182 | ✗183 | ✗184 | ✓185 | ✗186 | ||||||
| A conversation already running finishes on the new code, with no version markers in code | ✓ | ✓187 | ✗188 | ✓ | ✓189 | ✗190 | ✗191 | ✗192 | ✓193 | ✓194 | ✗195 | ✓196 | ✓197 | ✗198 | ✓199 | ✓200 | |||||
| Runtime out of the box: engine, process management and scaling ship with it; no server, worker fleet and database to assemble | ✗201 | ✓202 | ✓203 | ✓204 | ✓ | ✓205 | ✓206 | ✓207 | ✓208 | ✓209 | ✗210 | ✗211 | ✓ | ✓ | ✓ | ✓ | ✓212 | ✓213 | ✗214 | ✓215 | ✓216 |
| New code swapped into running work in place, no side-by-side deployment | ✓ | ✓ | ✓217 | ✓ | ✓ | ✗218 | ✗219 | ✗220 | ✓ | ✓ | ✗221 | ✓222 | ✓ | ✗223 | ✓ | ✓ | |||||
| Running work stays on the version it started on when new code is deployed | ✓ | ✓ | ✓ | ✗224 | ✓ | ✓ | ✓ | ✓ | ✗225 | ✗226 | ✓ | ✓227 | ✓228 | ✓ | ✓229 | ✗230 | |||||
| Execution history is an append-only log | ✓ | ✓ | ✓ | ✓ | ✗231 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓232 | |||||||||
| Languages you write the workflow in | |||||||||||||||||||||
| Write the workflow itself in Java | ✓ | ✓ | ✓ | ✓233 | ✗234 | ✓235 | ✓ | ✗236 | ✗237 | ✓238 | ✓ | ✓ | ✓239 | ✓ | ✓ | ✗240 | ✗241 | ✗242 | ✓243 | ✓244 | ✓245 |
| Write the workflow itself in Python | ✗246 | ✓ | ✓ | ✓ | ✗247 | ✓ | ✓ | ✓ | ✓248 | ✓249 | ✓ | ✓ | ✓250 | ✓ | ✓ | ✓251 | ✓252 | ✓ | ✓ | ✓ | ✓ |
| Write the workflow itself in TypeScript | ✗253 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓254 | ✗255 | ✓ | ✓256 | ✓ | ✓ | ✓ | ✓ | ✗257 | ✗258 | ✓ | ✗259 |
| Write the workflow itself in Go | ✗260 | ✓ | ✓ | ✓ | ✓261 | ✓262 | ✓ | ✓ | ✗263 | ✓264 | ✓ | ✓ | ✓265 | ✓ | ✗266 | ✗267 | ✗268 | ✗269 | ✓270 | ✓271 | ✗272 |
| Write the workflow itself in Rust | ✗273 | ✓ | ✓ | ✗274 | ✓ | ✓275 | ✗276 | ✗277 | ✗278 | ✓279 | ✗280 | ✗281 | ✓282 | ✗283 | ✗284 | ✗285 | ✗286 | ✗287 | ✗288 | ✗289 | ✗290 |
| Write the workflow itself in C/C++ | ✗291 | ✗292 | ✗293 | ✗294 | ✗295 | ✗296 | ✗297 | ✗298 | ✗299 | ✗300 | ✗301 | ✗302 | ✓303 | ✗304 | ✗305 | ✗306 | ✗307 | ✗308 | ✗309 | ✗310 | ✓311 |
| Write the workflow itself in C#/.NET | ✗312 | ✓ | ✗313 | ✗314 | ✗315 | ✗316 | ✗317 | ✗318 | ✗319 | ✓320 | ✗321 | ✓ | ✓322 | ✓ | ✓ | ✗323 | ✗324 | ✗325 | ✗326 | ✗327 | ✗328 |
✓ documented (1 point) ✓ partly, see its note (½) blank: not found in its docs (½) ✗ its own docs show it is not there, see its note (0). Checked against each product's documentation on 26 October 2026. Every Jaal tick is built today; WIP and the red Jaal marks are not built yet (0).
The notes behind each grey and red mark (328)
- Jaal: A service extends Service
, and a parallel loop takes one annotation (@Parallel) on its index. - Temporal: You write deterministic Workflows plus Activities with its SDK. source
- Restate: You write handlers, virtual objects and workflows, wrapping work in ctx.run. source
- Inngest: You write steps with step.run and the other step functions. source
- Golem: Durability needs no wrappers, but parallel work is written with worker clients, create_promise and await_promise. source
- Resonate: You write generator functions that call context.run. source
- DBOS: You annotate workflows and steps. source
- Hatchet: You define tasks, DAG workflows and durable tasks. source
- Trigger.dev: You define tasks with task() and call triggerAndWait. source
- Conductor: Workflows are JSON definitions. source
- Cadence: You write deterministic workflows plus activities with its SDK. source
- Dapr Workflow: You write workflows plus activities with its SDK. source
- Step Functions: Workflows are JSON state machines (Amazon States Language). source
- Lambda durable: You write DurableContext steps, waits and invokes. source
- Azure Durable: You write orchestrator, activity and entity functions. source
- Cloudflare Workflows: You write step.do and step.sleep calls. source
- Vercel Workflow: You mark code with the "use workflow" and "use step" directives. source
- Prefect: You decorate code with @flow and @task. source
- Airflow: You define Python DAGs. source
- LangGraph: You build a graph of nodes and edges, or use @entrypoint and @task. source
- Ray: You write @ray.remote tasks and actors. source
- Temporal: Parallel work is written with futures, Promise.all or asyncio.gather. source
- Restate: Parallel work is written with DurableFuture.all or Select. source
- Inngest: Parallel steps are written with Promise.all. source
- Golem: Parallel work fans out to child agents explicitly. source
- Resonate: Parallel work is written with beginRun / beginRpc futures. source
- DBOS: Parallel work goes through durable queues. source
- Hatchet: Parallel work is declared as DAG dependencies or child tasks. source
- Trigger.dev: Parallel work is written with batchTriggerAndWait. source
- Conductor: Parallel work is a FORK_JOIN declared in the definition. source
- Dapr Workflow: Parallel work is written with when_all / Task.WhenAll. source
- Step Functions: Parallel work is a Parallel or Map state. source
- Lambda durable: Parallel work is written with context.parallel or context.map. source
- Azure Durable: Parallel work is an explicit fan-out / fan-in (Task.WhenAll). source
- Cloudflare Workflows: Parallel steps are written with Promise.all. source
- Vercel Workflow: Parallel steps are written with Promise.all. source
- Prefect: Parallel work is written with .submit() and task runners. source
- Airflow: Parallel work is declared as DAG dependencies. source
- LangGraph: Parallel work is declared with fan-out edges and Send. source
- Ray: Parallel work is written with .remote() futures. source
- Temporal: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Restate: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Inngest: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Golem: Inspection is by CLI and the operation log; no graph view is documented. source
- Resonate: A CLI draws the call graph of a run as it grows, not of the code. source
- DBOS: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Hatchet: Draws the edges you declare with parents=[...]; durable tasks have no graph. source
- Trigger.dev: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Conductor: The workflow is a JSON definition; the UI draws that definition. source
- Cadence: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Dapr Workflow: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Step Functions: The workflow is an ASL definition; the console draws that definition. source
- Lambda durable: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Azure Durable: The dashboard plots the flow of one run, not of the code. source
- Cloudflare Workflows: The dashboard draws the parsed workflow code (steps, Promise.all, if, loops); in beta, TypeScript only. source
- Vercel Workflow: Its UI shows a run's history (timeline, events or traces), not a graph of the code. source
- Prefect: The UI graph is built from a run; a pre-run diagram is a local dry run of one path. source
- Airflow: Draws the dependencies you declare between tasks (a >> b). source
- LangGraph: Draws the nodes and edges you declare with add_node and add_edge. source
- Ray: A local image of a compiled DAG you built with bind(); the dashboard has no graph. source
- Jaal: One graph edit is built: pick steps and make them a sequence, with undo; the code is rewritten to match.
- Temporal: Its UI acts on runs (signal, reset, terminate); it does not edit code. source
- Restate: No graph editor is documented. source
- Inngest: No graph editor is documented. source
- Golem: No graph editor is documented. source
- Resonate: No graph editor is documented. source
- DBOS: From the steps view you can only fork a run from a step. source
- Hatchet: No graph editor is documented. source
- Trigger.dev: No graph editor is documented. source
- Conductor: The paid Orkes builder edits the JSON definition, not code; the open-source UI only views. source
- Cadence: No graph editor is documented. source
- Dapr Workflow: No graph editor is documented. source
- Step Functions: Workflow Studio edits the ASL definition, not application code. source
- Lambda durable: The console has a code editor but no graph editor. source
- Azure Durable: The dashboard manages runs; it does not edit code. source
- Cloudflare Workflows: The diagram is read-only. source
- Vercel Workflow: No graph editor is documented. source
- Prefect: No graph editor is documented. source
- Airflow: The Graph view only views; DAGs are edited in code. source
- LangGraph: Studio runs and debugs a graph; no feature writes graph edits back into code. source
- Ray: No graph editor is documented. source
- Temporal: A version is a Build ID supplied in the worker's deployment options. source
- Restate: Running requests stay on their version automatically, but a version is the endpoint you deploy and register, not derived from the code. source
- Inngest: There are no versions: a resumed run uses the current code. source
- Golem: Agents stay on their revision, but the docs do not say a revision is derived from content. source
- Resonate: A version is a developer option, 1 by default. source
- Trigger.dev: Each deploy makes a version and a run never changes version, but the identity is a date and counter. source
- Cadence: Changes are guarded with GetVersion markers the developer numbers. source
- Dapr Workflow: Versioning is by patching and developer-named workflow versions. source
- Step Functions: Executions stay on a published version, but publishing is explicit and the number is a counter. source
- Lambda durable: Executions can be pinned to a published version, but you publish and invoke it yourself. source
- Azure Durable: Orchestration versioning uses a version string the developer sets. source
- Vercel Workflow: Runs are pinned to the deployment that starts them, identified by deployment ID, not by content. source
- Airflow: With a Git bundle a run keeps its commit; the default local bundle has no versioning. source
- LangGraph: There are no versions: the latest graph applies to every thread. source
- Jaal: A run resumes from its last recorded step; a conversation is rebuilt by re-running its settled turns, each call answered from the record.
- Temporal: Recovery replays the workflow code against the event history. source
- Restate: Recovery is replay of the handler, skipping journaled steps. source
- Inngest: A run resumes by replaying the handler with saved step results. source
- Golem: Recovery replays the oplog; opt-in snapshots shorten the replay. source
- Resonate: After a crash, execution starts from the top of the function. source
- DBOS: An interrupted workflow is restarted by calling it again with its inputs. source
- Hatchet: The durable event log is replayed to rebuild state. source
- Trigger.dev: A retried task starts from the beginning, with cached subtask results. source
- Cadence: Workflow code is re-executed to rebuild state. source
- Dapr Workflow: On replay the workflow runs again from the beginning. source
- Step Functions: A failed run can be redriven from the failed step, by hand. source
- Lambda durable: On replay the code runs from the beginning, skipping completed checkpoints. source
- Azure Durable: The orchestrator re-executes the whole function from the start. source
- Cloudflare Workflows: Code outside steps runs again after hibernation. source
- Vercel Workflow: Workflow code is re-run, using the event log to reach its spot. source
- LangGraph: Graph API nodes re-run from the start of the interrupted node; the Functional API replays from the top. source
- Temporal: Compensation is a documented saga pattern written in workflow code. source
- Restate: Sagas are implemented in user code. source
- Dapr Workflow: Compensation is a documented pattern written in the workflow. source
- Step Functions: Compensation is a pattern built from Catch fields. source
- Azure Durable: Compensation is written in a catch block. source
- Vercel Workflow: Compensation is a saga recipe written in the workflow. source
- Restate: Registration refuses removed or renamed handlers, but checks no input or output types, and a flag overrides it. source
- Dapr Workflow: Changing a workflow's name, input or output deploys and can fail at runtime. source
- Azure Durable: An activity signature change deploys and can break orchestrators that use it. source
- Restate: Sagas are written in the calling handler, adding compensations step by step. source
- Golem: An operation carries its own undo, but the caller must open the transaction. source
- Conductor: The workflow author configures a failure workflow. source
- Prefect: A task declares its rollback hook, but the flow must open a transaction. source
- Temporal: The call reads like a function, but on a handle built first with proxyActivities in the workflow. source
- Restate: Calls go through a typed client taken from the context: ctx.serviceClient(...).handler(...). source
- Inngest: Another function is called through step.invoke with an ID and options. source
- Golem: Methods are called directly, on a client obtained first with get(...). source
- Resonate: Every durable call goes through ctx.run or ctx.rpc. source
- DBOS: A step is called as a plain function, but it is a step in the same program, not another service. source
- Hatchet: A child is spawned with a generic run() on a task object. source
- Trigger.dev: A child is called with triggerAndWait and returns a result wrapper to unwrap. source
- Conductor: Tasks are referenced by registered names in a JSON definition. source
- Cadence: Java calls a method on a stub made with newActivityStub; Go uses ExecuteActivity(ctx, fn). source
- Dapr Workflow: Activities are called through the context: CallActivityAsync and its kin. source
- Step Functions: Task states call services through a Resource ARN in JSON. source
- Lambda durable: Service calls are wrapped in steps on the DurableContext. source
- Azure Durable: The default is CallActivityAsync(nameof(...)); typed calls come from a source generator in preview. source
- Cloudflare Workflows: Work is written as step.do(name, fn). source
- Vercel Workflow: A step is called with a plain await, but it is a step in the same program, not another service. source
- Prefect: A task is called like a normal function, but it is a task in the same flow, not another service. source
- Airflow: TaskFlow calls look like functions but build DAG dependencies behind the scenes. source
- LangGraph: A @task call returns a future read with .result(); the Graph API is nodes and edges. source
- Ray: A remote function is called with .remote() and read with ray.get. source
- Jaal: Work in progress: the recorded stream and its resume point are designed, not built.
- Temporal: Workflow Streams (preview) resume a subscriber from its offset; a retried activity streams its events again. source
- Inngest: The browser can reattach, but a step that fails and retries streams fresh data. source
- Golem: Recorded chunks replay, but an unfinished stream is resumed by asking the model to continue, so the rest is regenerated. source
- DBOS: Writes survive a restart, but a retried step can write its output again. source
- Hatchet: Events published before a consumer starts are dropped; no resume is described. source
- Trigger.dev: A lost connection resumes from the last chunk; a task crash or retry is not covered. source
- Vercel Workflow: Clients reconnect from a start index; a step crashing mid-stream is not described. source
- LangGraph: A dropped connection resumes with Last-Event-ID; a server crash is not covered. source
- Jaal: Rust's compiler checks the parallel versions Jaal derives for its test suite (38 of 39 accepted); checking every program before it runs is next.
- Temporal: An optional Go-only checker, workflowcheck, can help; other SDKs check at run time. source
- Restate: Its docs warn that calls between exclusive handlers may deadlock; avoiding it is the developer's job. source
- Inngest: The parallel-steps guide lists interleaving and race caveats for the developer; no tool check. source
- Golem: The rule is never to have two agents await each other; no check is described. source
- Resonate: Determinism is left to the author, with no static check. source
- DBOS: The only check is a run-time non-determinism error. source
- Trigger.dev: The docs tell developers which concurrent calls to avoid. source
- Cadence: Workflow code must use Cadence channels and selectors; no official checker. source
- Dapr Workflow: Determinism is a rule for authors, with no analyzer. source
- Step Functions: Parallel branches get copies of their input, so workflow state cannot race; the work in tasks is unchecked. source
- Lambda durable: A Go-only linter, durablelint, flags issues and can be silenced. source
- Azure Durable: A C#-only Roslyn analyzer warns at build time; its warnings can be suppressed. source
- Cloudflare Workflows: Concurrency over steps is written guidance only. source
- Vercel Workflow: Race losers keep running and need idempotency keys; correctness is left to testing. source
- Prefect: Tasks run in threads, so any global state must be thread-safe; the developer's job. source
- Airflow: Best practices set rules for the developer (idempotent tasks, no local files); no check. source
- LangGraph: Conflicting parallel writes are refused only while the graph runs. source
- Ray: Don't share state through global variables; wrap it in an actor. A rule, not a check. source
- Temporal: Only in the TypeScript sandbox are Math.random and Date made deterministic; other effects must be Activities. source
- Restate: Non-deterministic work must be wrapped in run(). source
- Inngest: Non-deterministic logic must go inside step.run(). source
- Resonate: Non-deterministic calls go in a leaf function run via ctx.run. source
- DBOS: A workflow function must be deterministic. source
- Hatchet: Durable-task code must be deterministic; avoid clock and random reads. source
- Cadence: Workflow code must use Cadence APIs for time and must be deterministic. source
- Dapr Workflow: Workflow code must be deterministic. source
- Lambda durable: Every non-deterministic call must be wrapped in a step. source
- Azure Durable: Orchestrator code must not use time APIs or random values directly. source
- Cloudflare Workflows: Non-deterministic logic must go inside step.do. source
- Vercel Workflow: Math.random and Date are fixed automatically; any other effect must be a step. source
- LangGraph: Random numbers and clock reads must be kept inside tasks. source
- Temporal: Running workflows continue on new code only for replay-compatible changes; otherwise patching is needed. source
- Restate: Requests start and end on the same version. source
- Golem: An update replays the agent on the new revision, but must be triggered and falls back if replay diverges. source
- DBOS: Only workflows whose version matches the current application are recovered. source
- Trigger.dev: A run never changes version. source
- Conductor: Running executions keep the definition snapshot taken at start. source
- Cadence: Continues on new code only for compatible changes; otherwise GetVersion is needed. source
- Dapr Workflow: Continues on new code only while it stays deterministic; otherwise patching. source
- Step Functions: A redriven execution uses the same definition; an update needs a new execution. source
- Lambda durable: Unpinned executions continue only if steps are not changed in ways that break replay. source
- Azure Durable: Running orchestrations replay on new code; breaking changes need version branching. source
- Vercel Workflow: Runs are pinned to the deployment that starts them. source
- Airflow: Only with the unversioned local bundle do remaining tasks use the latest code. source
- LangGraph: Old threads resume on the new graph, but renames break them and changes need a version stamp. source
- Jaal: Not yet: the host agent (process manager) and deploy are built, but scaling is ruled (10-24) and not built, and you install Postgres.
- Temporal: You host and scale your own workers, even on Temporal Cloud; self-hosting adds a server cluster and a database. source
- Restate: The server is one binary with no database, but you deploy and run your own services. source
- Inngest: The engine is one binary or a cloud; your function code runs in your own app or host. source
- Resonate: One command with SQLite for development; Postgres and your own processes in production. source
- DBOS: A library in your app plus Postgres; you host and scale the app, or use DBOS Cloud. source
- Hatchet: An engine image plus Postgres, or Hatchet Cloud; you always run the workers. source
- Trigger.dev: Fully managed in its own cloud; self-hosting needs Postgres, Redis and runners. source
- Conductor: A server plus a database and a lock service, or Orkes Cloud; your task workers are yours. source
- Cadence: You run frontend, history and matching services plus Cassandra, MySQL or Postgres. source
- Dapr Workflow: You configure an actor state store and host the app (and a control plane on Kubernetes). source
- Vercel Workflow: Automatic on Vercel; elsewhere you supply a storage and queue backend such as Postgres. source
- Prefect: A server, a database and workers you run; Prefect Cloud's managed pools come close. source
- Airflow: You assemble a metadata database, scheduler, API server, DAG processor and executor. source
- LangGraph: Fully managed on LangSmith Cloud; self-hosting needs Postgres, Redis and a license. source
- Ray: Ships head, workers and an autoscaler, but you provision the cluster. source
- Restate: Forcing an existing registration is for local development; production versions are new deployments. source
- DBOS: Only workflows of the current version are recovered. source
- Trigger.dev: A run never changes version, even after new deploys. source
- Conductor: Running executions keep their start-time snapshot. source
- Step Functions: An updated definition needs a new execution. source
- Lambda durable: Unpinned executions may resume on changed code, but the docs recommend pinning. source
- Vercel Workflow: Workflows keep running on the deployment they were created on. source
- Inngest: A resumed run uses the current code. source
- Cadence: Running workflows replay on the updated code. source
- Dapr Workflow: Code updates apply to running instances. source
- Lambda durable: Executions are pinned when you invoke a version or alias. source
- Azure Durable: Version settings can match work to versions; by default code changes affect running work. source
- Airflow: With a versioned bundle a run keeps its code; the default local bundle does not. source
- LangGraph: The latest graph applies to every thread. source
- Resonate: Its blog describes un-sequenced promises rather than a sequenced log. source
- LangGraph: Checkpoints accumulate per thread, but are snapshots, not described as a log. source
- Inngest: Only a Kotlin SDK in its GitHub org, not on the SDK list; its Java example says coming soon. source
- Golem: Scala is listed; Java is not. source
- Resonate: Labelled pre-release. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: An experimental task SDK: task logic only; DAGs are always Python. source
- LangGraph: A community port only, not in the vendor's docs. source
- Ray: The Java APIs are experimental and community-supported. source
- Jaal: Not built yet. A Python orchestrator turns green once it compiles and runs end to end.
- Golem: Not on its language list. source
- Trigger.dev: A TypeScript task can run Python scripts; the task itself stays TypeScript. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Python Workflows are in open beta. source
- Vercel Workflow: Python is labelled beta. source
- Jaal: Not built yet.
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Golem: Go is listed only in v1.6, still a release candidate. source
- Resonate: Labelled early release. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: An experimental task SDK: task logic only; DAGs are always Python. source
- LangGraph: A community port only, not in the vendor's docs. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Inngest: Not on its language list. source
- Resonate: Labelled early development. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Dapr Workflow: Not on its language list. source
- Step Functions: Only as Lambda tasks; the workflow is Amazon States Language. source
- Lambda durable: Not on its language list. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Temporal: Not on its language list. source
- Restate: Not on its language list. source
- Inngest: Not on its language list. source
- Golem: Not on its language list. source
- Resonate: Not on its language list. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: Not on its language list. source
- Cadence: Not on its language list. source
- Dapr Workflow: Not on its language list. source
- Step Functions: Only as Lambda tasks; the workflow is Amazon States Language. source
- Lambda durable: Not on its language list. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: The C++ APIs are experimental and community-supported. source
- Jaal: Not built yet.
- Restate: Not on its language list. source
- Inngest: Not on its language list. source
- Golem: Not on its language list. source
- Resonate: Not on its language list. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: Not on its language list. source
The notes behind each grey and red mark (275)
- Temporal: You write deterministic Workflows plus Activities with its SDK. source
- Restate: You write handlers, virtual objects and workflows, wrapping work in ctx.run. source
- Inngest: You write steps with step.run and the other step functions. source
- Golem: Durability needs no wrappers, but parallel work is written with worker clients, create_promise and await_promise. source
- Resonate: You write generator functions that call context.run. source
- DBOS: You annotate workflows and steps. source
- Hatchet: You define tasks, DAG workflows and durable tasks. source
- Trigger.dev: You define tasks with task() and call triggerAndWait. source
- Conductor: Workflows are JSON definitions. source
- Cadence: You write deterministic workflows plus activities with its SDK. source
- Dapr Workflow: You write workflows plus activities with its SDK. source
- Step Functions: Workflows are JSON state machines (Amazon States Language). source
- Lambda durable: You write DurableContext steps, waits and invokes. source
- Azure Durable: You write orchestrator, activity and entity functions. source
- Cloudflare Workflows: You write step.do and step.sleep calls. source
- Vercel Workflow: You mark code with the "use workflow" and "use step" directives. source
- Prefect: You decorate code with @flow and @task. source
- Airflow: You define Python DAGs. source
- LangGraph: You build a graph of nodes and edges, or use @entrypoint and @task. source
- Ray: You write @ray.remote tasks and actors. source
- Temporal: Parallel work is written with futures, Promise.all or asyncio.gather. source
- Restate: Parallel work is written with DurableFuture.all or Select. source
- Inngest: Parallel steps are written with Promise.all. source
- Golem: Parallel work fans out to child agents explicitly. source
- Resonate: Parallel work is written with beginRun / beginRpc futures. source
- DBOS: Parallel work goes through durable queues. source
- Hatchet: Parallel work is declared as DAG dependencies or child tasks. source
- Trigger.dev: Parallel work is written with batchTriggerAndWait. source
- Conductor: Parallel work is a FORK_JOIN declared in the definition. source
- Dapr Workflow: Parallel work is written with when_all / Task.WhenAll. source
- Step Functions: Parallel work is a Parallel or Map state. source
- Lambda durable: Parallel work is written with context.parallel or context.map. source
- Azure Durable: Parallel work is an explicit fan-out / fan-in (Task.WhenAll). source
- Cloudflare Workflows: Parallel steps are written with Promise.all. source
- Vercel Workflow: Parallel steps are written with Promise.all. source
- Prefect: Parallel work is written with .submit() and task runners. source
- Airflow: Parallel work is declared as DAG dependencies. source
- LangGraph: Parallel work is declared with fan-out edges and Send. source
- Ray: Parallel work is written with .remote() futures. source
- Temporal: A version is a Build ID supplied in the worker's deployment options. source
- Restate: Running requests stay on their version automatically, but a version is the endpoint you deploy and register, not derived from the code. source
- Inngest: There are no versions: a resumed run uses the current code. source
- Golem: Agents stay on their revision, but the docs do not say a revision is derived from content. source
- Resonate: A version is a developer option, 1 by default. source
- Trigger.dev: Each deploy makes a version and a run never changes version, but the identity is a date and counter. source
- Cadence: Changes are guarded with GetVersion markers the developer numbers. source
- Dapr Workflow: Versioning is by patching and developer-named workflow versions. source
- Step Functions: Executions stay on a published version, but publishing is explicit and the number is a counter. source
- Lambda durable: Executions can be pinned to a published version, but you publish and invoke it yourself. source
- Azure Durable: Orchestration versioning uses a version string the developer sets. source
- Vercel Workflow: Runs are pinned to the deployment that starts them, identified by deployment ID, not by content. source
- Airflow: With a Git bundle a run keeps its commit; the default local bundle has no versioning. source
- LangGraph: There are no versions: the latest graph applies to every thread. source
- Temporal: Recovery replays the workflow code against the event history. source
- Restate: Recovery is replay of the handler, skipping journaled steps. source
- Inngest: A run resumes by replaying the handler with saved step results. source
- Golem: Recovery replays the oplog; opt-in snapshots shorten the replay. source
- Resonate: After a crash, execution starts from the top of the function. source
- DBOS: An interrupted workflow is restarted by calling it again with its inputs. source
- Hatchet: The durable event log is replayed to rebuild state. source
- Trigger.dev: A retried task starts from the beginning, with cached subtask results. source
- Cadence: Workflow code is re-executed to rebuild state. source
- Dapr Workflow: On replay the workflow runs again from the beginning. source
- Step Functions: A failed run can be redriven from the failed step, by hand. source
- Lambda durable: On replay the code runs from the beginning, skipping completed checkpoints. source
- Azure Durable: The orchestrator re-executes the whole function from the start. source
- Cloudflare Workflows: Code outside steps runs again after hibernation. source
- Vercel Workflow: Workflow code is re-run, using the event log to reach its spot. source
- LangGraph: Graph API nodes re-run from the start of the interrupted node; the Functional API replays from the top. source
- Temporal: Compensation is a documented saga pattern written in workflow code. source
- Restate: Sagas are implemented in user code. source
- Dapr Workflow: Compensation is a documented pattern written in the workflow. source
- Step Functions: Compensation is a pattern built from Catch fields. source
- Azure Durable: Compensation is written in a catch block. source
- Vercel Workflow: Compensation is a saga recipe written in the workflow. source
- Restate: Registration refuses removed or renamed handlers, but checks no input or output types, and a flag overrides it. source
- Dapr Workflow: Changing a workflow's name, input or output deploys and can fail at runtime. source
- Azure Durable: An activity signature change deploys and can break orchestrators that use it. source
- Restate: Sagas are written in the calling handler, adding compensations step by step. source
- Golem: An operation carries its own undo, but the caller must open the transaction. source
- Conductor: The workflow author configures a failure workflow. source
- Prefect: A task declares its rollback hook, but the flow must open a transaction. source
- Temporal: The call reads like a function, but on a handle built first with proxyActivities in the workflow. source
- Restate: Calls go through a typed client taken from the context: ctx.serviceClient(...).handler(...). source
- Inngest: Another function is called through step.invoke with an ID and options. source
- Golem: Methods are called directly, on a client obtained first with get(...). source
- Resonate: Every durable call goes through ctx.run or ctx.rpc. source
- DBOS: A step is called as a plain function, but it is a step in the same program, not another service. source
- Hatchet: A child is spawned with a generic run() on a task object. source
- Trigger.dev: A child is called with triggerAndWait and returns a result wrapper to unwrap. source
- Conductor: Tasks are referenced by registered names in a JSON definition. source
- Cadence: Java calls a method on a stub made with newActivityStub; Go uses ExecuteActivity(ctx, fn). source
- Dapr Workflow: Activities are called through the context: CallActivityAsync and its kin. source
- Step Functions: Task states call services through a Resource ARN in JSON. source
- Lambda durable: Service calls are wrapped in steps on the DurableContext. source
- Azure Durable: The default is CallActivityAsync(nameof(...)); typed calls come from a source generator in preview. source
- Cloudflare Workflows: Work is written as step.do(name, fn). source
- Vercel Workflow: A step is called with a plain await, but it is a step in the same program, not another service. source
- Prefect: A task is called like a normal function, but it is a task in the same flow, not another service. source
- Airflow: TaskFlow calls look like functions but build DAG dependencies behind the scenes. source
- LangGraph: A @task call returns a future read with .result(); the Graph API is nodes and edges. source
- Ray: A remote function is called with .remote() and read with ray.get. source
- Jaal: Work in progress: the recorded stream and its resume point are designed, not built.
- Temporal: Workflow Streams (preview) resume a subscriber from its offset; a retried activity streams its events again. source
- Inngest: The browser can reattach, but a step that fails and retries streams fresh data. source
- DBOS: Writes survive a restart, but a retried step can write its output again. source
- Hatchet: Events published before a consumer starts are dropped; no resume is described. source
- Trigger.dev: A lost connection resumes from the last chunk; a task crash or retry is not covered. source
- Vercel Workflow: Clients reconnect from a start index; a step crashing mid-stream is not described. source
- LangGraph: A dropped connection resumes with Last-Event-ID; a server crash is not covered. source
- Temporal: An optional Go-only checker, workflowcheck, can help; other SDKs check at run time. source
- Resonate: Determinism is left to the author, with no static check. source
- DBOS: The only check is a run-time non-determinism error. source
- Trigger.dev: The docs tell developers which concurrent calls to avoid. source
- Cadence: Workflow code must use Cadence channels and selectors; no official checker. source
- Dapr Workflow: Determinism is a rule for authors, with no analyzer. source
- Lambda durable: A Go-only linter, durablelint, flags issues and can be silenced. source
- Azure Durable: A C#-only Roslyn analyzer warns at build time; its warnings can be suppressed. source
- Cloudflare Workflows: Concurrency over steps is written guidance only. source
- LangGraph: Conflicting parallel writes are refused only while the graph runs. source
- Temporal: Only in the TypeScript sandbox are Math.random and Date made deterministic; other effects must be Activities. source
- Restate: Non-deterministic work must be wrapped in run(). source
- Inngest: Non-deterministic logic must go inside step.run(). source
- Resonate: Non-deterministic calls go in a leaf function run via ctx.run. source
- DBOS: A workflow function must be deterministic. source
- Hatchet: Durable-task code must be deterministic; avoid clock and random reads. source
- Cadence: Workflow code must use Cadence APIs for time and must be deterministic. source
- Dapr Workflow: Workflow code must be deterministic. source
- Lambda durable: Every non-deterministic call must be wrapped in a step. source
- Azure Durable: Orchestrator code must not use time APIs or random values directly. source
- Cloudflare Workflows: Non-deterministic logic must go inside step.do. source
- Vercel Workflow: Math.random and Date are fixed automatically; any other effect must be a step. source
- LangGraph: Random numbers and clock reads must be kept inside tasks. source
- Temporal: Running workflows continue on new code only for replay-compatible changes; otherwise patching is needed. source
- Restate: Requests start and end on the same version. source
- Golem: An update replays the agent on the new revision, but must be triggered and falls back if replay diverges. source
- DBOS: Only workflows whose version matches the current application are recovered. source
- Trigger.dev: A run never changes version. source
- Conductor: Running executions keep the definition snapshot taken at start. source
- Cadence: Continues on new code only for compatible changes; otherwise GetVersion is needed. source
- Dapr Workflow: Continues on new code only while it stays deterministic; otherwise patching. source
- Step Functions: A redriven execution uses the same definition; an update needs a new execution. source
- Lambda durable: Unpinned executions continue only if steps are not changed in ways that break replay. source
- Azure Durable: Running orchestrations replay on new code; breaking changes need version branching. source
- Vercel Workflow: Runs are pinned to the deployment that starts them. source
- Airflow: Only with the unversioned local bundle do remaining tasks use the latest code. source
- LangGraph: Old threads resume on the new graph, but renames break them and changes need a version stamp. source
- Jaal: The host agent (process manager) and deploy are built; scaling by instances is ruled (10-24) but not built; you install Postgres.
- Temporal: You host and scale your own workers, even on Temporal Cloud; self-hosting adds a server cluster and a database. source
- Restate: The server is one binary with no database, but you deploy and run your own services. source
- Inngest: The engine is one binary or a cloud; your function code runs in your own app or host. source
- Resonate: One command with SQLite for development; Postgres and your own processes in production. source
- DBOS: A library in your app plus Postgres; you host and scale the app, or use DBOS Cloud. source
- Hatchet: An engine image plus Postgres, or Hatchet Cloud; you always run the workers. source
- Trigger.dev: Fully managed in its own cloud; self-hosting needs Postgres, Redis and runners. source
- Conductor: A server plus a database and a lock service, or Orkes Cloud; your task workers are yours. source
- Cadence: You run frontend, history and matching services plus Cassandra, MySQL or Postgres. source
- Dapr Workflow: You configure an actor state store and host the app (and a control plane on Kubernetes). source
- Vercel Workflow: Automatic on Vercel; elsewhere you supply a storage and queue backend such as Postgres. source
- Prefect: A server, a database and workers you run; Prefect Cloud's managed pools come close. source
- Airflow: You assemble a metadata database, scheduler, API server, DAG processor and executor. source
- LangGraph: Fully managed on LangSmith Cloud; self-hosting needs Postgres, Redis and a license. source
- Ray: Ships head, workers and an autoscaler, but you provision the cluster. source
- Restate: Forcing an existing registration is for local development; production versions are new deployments. source
- DBOS: Only workflows of the current version are recovered. source
- Trigger.dev: A run never changes version, even after new deploys. source
- Conductor: Running executions keep their start-time snapshot. source
- Step Functions: An updated definition needs a new execution. source
- Lambda durable: Unpinned executions may resume on changed code, but the docs recommend pinning. source
- Vercel Workflow: Workflows keep running on the deployment they were created on. source
- Inngest: A resumed run uses the current code. source
- Cadence: Running workflows replay on the updated code. source
- Dapr Workflow: Code updates apply to running instances. source
- Lambda durable: Executions are pinned when you invoke a version or alias. source
- Azure Durable: Version settings can match work to versions; by default code changes affect running work. source
- Airflow: With a versioned bundle a run keeps its code; the default local bundle does not. source
- LangGraph: The latest graph applies to every thread. source
- Resonate: Its blog describes un-sequenced promises rather than a sequenced log. source
- LangGraph: Checkpoints accumulate per thread, but are snapshots, not described as a log. source
- Inngest: Only a Kotlin SDK in its GitHub org, not on the SDK list; its Java example says coming soon. source
- Golem: Scala is listed; Java is not. source
- Resonate: Labelled pre-release. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: An experimental task SDK: task logic only; DAGs are always Python. source
- LangGraph: A community port only, not in the vendor's docs. source
- Ray: The Java APIs are experimental and community-supported. source
- Jaal: Not built yet. A Python orchestrator turns green once it compiles and runs end to end.
- Golem: Not on its language list. source
- Trigger.dev: A TypeScript task can run Python scripts; the task itself stays TypeScript. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Python Workflows are in open beta. source
- Vercel Workflow: Python is labelled beta. source
- Jaal: Not built yet.
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Golem: Go is listed only in v1.6, still a release candidate. source
- Resonate: Labelled early release. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: An experimental task SDK: task logic only; DAGs are always Python. source
- LangGraph: A community port only, not in the vendor's docs. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Inngest: Not on its language list. source
- Resonate: Labelled early development. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Dapr Workflow: Not on its language list. source
- Step Functions: Only as Lambda tasks; the workflow is Amazon States Language. source
- Lambda durable: Not on its language list. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Temporal: Not on its language list. source
- Restate: Not on its language list. source
- Inngest: Not on its language list. source
- Golem: Not on its language list. source
- Resonate: Not on its language list. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: Not on its language list. source
- Cadence: Not on its language list. source
- Dapr Workflow: Not on its language list. source
- Step Functions: Only as Lambda tasks; the workflow is Amazon States Language. source
- Lambda durable: Not on its language list. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: The C++ APIs are experimental and community-supported. source
- Jaal: Not built yet.
- Restate: Not on its language list. source
- Inngest: Not on its language list. source
- Golem: Not on its language list. source
- Resonate: Not on its language list. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: Not on its language list. source
The notes behind each grey and red mark (275)
- Temporal: You write deterministic Workflows plus Activities with its SDK. source
- Restate: You write handlers, virtual objects and workflows, wrapping work in ctx.run. source
- Inngest: You write steps with step.run and the other step functions. source
- Golem: Durability needs no wrappers, but parallel work is written with worker clients, create_promise and await_promise. source
- Resonate: You write generator functions that call context.run. source
- DBOS: You annotate workflows and steps. source
- Hatchet: You define tasks, DAG workflows and durable tasks. source
- Trigger.dev: You define tasks with task() and call triggerAndWait. source
- Conductor: Workflows are JSON definitions. source
- Cadence: You write deterministic workflows plus activities with its SDK. source
- Dapr Workflow: You write workflows plus activities with its SDK. source
- Step Functions: Workflows are JSON state machines (Amazon States Language). source
- Lambda durable: You write DurableContext steps, waits and invokes. source
- Azure Durable: You write orchestrator, activity and entity functions. source
- Cloudflare Workflows: You write step.do and step.sleep calls. source
- Vercel Workflow: You mark code with the "use workflow" and "use step" directives. source
- Prefect: You decorate code with @flow and @task. source
- Airflow: You define Python DAGs. source
- LangGraph: You build a graph of nodes and edges, or use @entrypoint and @task. source
- Ray: You write @ray.remote tasks and actors. source
- Temporal: Parallel work is written with futures, Promise.all or asyncio.gather. source
- Restate: Parallel work is written with DurableFuture.all or Select. source
- Inngest: Parallel steps are written with Promise.all. source
- Golem: Parallel work fans out to child agents explicitly. source
- Resonate: Parallel work is written with beginRun / beginRpc futures. source
- DBOS: Parallel work goes through durable queues. source
- Hatchet: Parallel work is declared as DAG dependencies or child tasks. source
- Trigger.dev: Parallel work is written with batchTriggerAndWait. source
- Conductor: Parallel work is a FORK_JOIN declared in the definition. source
- Dapr Workflow: Parallel work is written with when_all / Task.WhenAll. source
- Step Functions: Parallel work is a Parallel or Map state. source
- Lambda durable: Parallel work is written with context.parallel or context.map. source
- Azure Durable: Parallel work is an explicit fan-out / fan-in (Task.WhenAll). source
- Cloudflare Workflows: Parallel steps are written with Promise.all. source
- Vercel Workflow: Parallel steps are written with Promise.all. source
- Prefect: Parallel work is written with .submit() and task runners. source
- Airflow: Parallel work is declared as DAG dependencies. source
- LangGraph: Parallel work is declared with fan-out edges and Send. source
- Ray: Parallel work is written with .remote() futures. source
- Temporal: A version is a Build ID supplied in the worker's deployment options. source
- Restate: Running requests stay on their version automatically, but a version is the endpoint you deploy and register, not derived from the code. source
- Inngest: There are no versions: a resumed run uses the current code. source
- Golem: Agents stay on their revision, but the docs do not say a revision is derived from content. source
- Resonate: A version is a developer option, 1 by default. source
- Trigger.dev: Each deploy makes a version and a run never changes version, but the identity is a date and counter. source
- Cadence: Changes are guarded with GetVersion markers the developer numbers. source
- Dapr Workflow: Versioning is by patching and developer-named workflow versions. source
- Step Functions: Executions stay on a published version, but publishing is explicit and the number is a counter. source
- Lambda durable: Executions can be pinned to a published version, but you publish and invoke it yourself. source
- Azure Durable: Orchestration versioning uses a version string the developer sets. source
- Vercel Workflow: Runs are pinned to the deployment that starts them, identified by deployment ID, not by content. source
- Airflow: With a Git bundle a run keeps its commit; the default local bundle has no versioning. source
- LangGraph: There are no versions: the latest graph applies to every thread. source
- Temporal: Recovery replays the workflow code against the event history. source
- Restate: Recovery is replay of the handler, skipping journaled steps. source
- Inngest: A run resumes by replaying the handler with saved step results. source
- Golem: Recovery replays the oplog; opt-in snapshots shorten the replay. source
- Resonate: After a crash, execution starts from the top of the function. source
- DBOS: An interrupted workflow is restarted by calling it again with its inputs. source
- Hatchet: The durable event log is replayed to rebuild state. source
- Trigger.dev: A retried task starts from the beginning, with cached subtask results. source
- Cadence: Workflow code is re-executed to rebuild state. source
- Dapr Workflow: On replay the workflow runs again from the beginning. source
- Step Functions: A failed run can be redriven from the failed step, by hand. source
- Lambda durable: On replay the code runs from the beginning, skipping completed checkpoints. source
- Azure Durable: The orchestrator re-executes the whole function from the start. source
- Cloudflare Workflows: Code outside steps runs again after hibernation. source
- Vercel Workflow: Workflow code is re-run, using the event log to reach its spot. source
- LangGraph: Graph API nodes re-run from the start of the interrupted node; the Functional API replays from the top. source
- Temporal: Compensation is a documented saga pattern written in workflow code. source
- Restate: Sagas are implemented in user code. source
- Dapr Workflow: Compensation is a documented pattern written in the workflow. source
- Step Functions: Compensation is a pattern built from Catch fields. source
- Azure Durable: Compensation is written in a catch block. source
- Vercel Workflow: Compensation is a saga recipe written in the workflow. source
- Restate: Registration refuses removed or renamed handlers, but checks no input or output types, and a flag overrides it. source
- Dapr Workflow: Changing a workflow's name, input or output deploys and can fail at runtime. source
- Azure Durable: An activity signature change deploys and can break orchestrators that use it. source
- Restate: Sagas are written in the calling handler, adding compensations step by step. source
- Golem: An operation carries its own undo, but the caller must open the transaction. source
- Conductor: The workflow author configures a failure workflow. source
- Prefect: A task declares its rollback hook, but the flow must open a transaction. source
- Temporal: The call reads like a function, but on a handle built first with proxyActivities in the workflow. source
- Restate: Calls go through a typed client taken from the context: ctx.serviceClient(...).handler(...). source
- Inngest: Another function is called through step.invoke with an ID and options. source
- Golem: Methods are called directly, on a client obtained first with get(...). source
- Resonate: Every durable call goes through ctx.run or ctx.rpc. source
- DBOS: A step is called as a plain function, but it is a step in the same program, not another service. source
- Hatchet: A child is spawned with a generic run() on a task object. source
- Trigger.dev: A child is called with triggerAndWait and returns a result wrapper to unwrap. source
- Conductor: Tasks are referenced by registered names in a JSON definition. source
- Cadence: Java calls a method on a stub made with newActivityStub; Go uses ExecuteActivity(ctx, fn). source
- Dapr Workflow: Activities are called through the context: CallActivityAsync and its kin. source
- Step Functions: Task states call services through a Resource ARN in JSON. source
- Lambda durable: Service calls are wrapped in steps on the DurableContext. source
- Azure Durable: The default is CallActivityAsync(nameof(...)); typed calls come from a source generator in preview. source
- Cloudflare Workflows: Work is written as step.do(name, fn). source
- Vercel Workflow: A step is called with a plain await, but it is a step in the same program, not another service. source
- Prefect: A task is called like a normal function, but it is a task in the same flow, not another service. source
- Airflow: TaskFlow calls look like functions but build DAG dependencies behind the scenes. source
- LangGraph: A @task call returns a future read with .result(); the Graph API is nodes and edges. source
- Ray: A remote function is called with .remote() and read with ray.get. source
- Jaal: Work in progress: the recorded stream and its resume point are designed, not built.
- Temporal: Workflow Streams (preview) resume a subscriber from its offset; a retried activity streams its events again. source
- Inngest: The browser can reattach, but a step that fails and retries streams fresh data. source
- DBOS: Writes survive a restart, but a retried step can write its output again. source
- Hatchet: Events published before a consumer starts are dropped; no resume is described. source
- Trigger.dev: A lost connection resumes from the last chunk; a task crash or retry is not covered. source
- Vercel Workflow: Clients reconnect from a start index; a step crashing mid-stream is not described. source
- LangGraph: A dropped connection resumes with Last-Event-ID; a server crash is not covered. source
- Temporal: An optional Go-only checker, workflowcheck, can help; other SDKs check at run time. source
- Resonate: Determinism is left to the author, with no static check. source
- DBOS: The only check is a run-time non-determinism error. source
- Trigger.dev: The docs tell developers which concurrent calls to avoid. source
- Cadence: Workflow code must use Cadence channels and selectors; no official checker. source
- Dapr Workflow: Determinism is a rule for authors, with no analyzer. source
- Lambda durable: A Go-only linter, durablelint, flags issues and can be silenced. source
- Azure Durable: A C#-only Roslyn analyzer warns at build time; its warnings can be suppressed. source
- Cloudflare Workflows: Concurrency over steps is written guidance only. source
- LangGraph: Conflicting parallel writes are refused only while the graph runs. source
- Temporal: Only in the TypeScript sandbox are Math.random and Date made deterministic; other effects must be Activities. source
- Restate: Non-deterministic work must be wrapped in run(). source
- Inngest: Non-deterministic logic must go inside step.run(). source
- Resonate: Non-deterministic calls go in a leaf function run via ctx.run. source
- DBOS: A workflow function must be deterministic. source
- Hatchet: Durable-task code must be deterministic; avoid clock and random reads. source
- Cadence: Workflow code must use Cadence APIs for time and must be deterministic. source
- Dapr Workflow: Workflow code must be deterministic. source
- Lambda durable: Every non-deterministic call must be wrapped in a step. source
- Azure Durable: Orchestrator code must not use time APIs or random values directly. source
- Cloudflare Workflows: Non-deterministic logic must go inside step.do. source
- Vercel Workflow: Math.random and Date are fixed automatically; any other effect must be a step. source
- LangGraph: Random numbers and clock reads must be kept inside tasks. source
- Temporal: Running workflows continue on new code only for replay-compatible changes; otherwise patching is needed. source
- Restate: Requests start and end on the same version. source
- Golem: An update replays the agent on the new revision, but must be triggered and falls back if replay diverges. source
- DBOS: Only workflows whose version matches the current application are recovered. source
- Trigger.dev: A run never changes version. source
- Conductor: Running executions keep the definition snapshot taken at start. source
- Cadence: Continues on new code only for compatible changes; otherwise GetVersion is needed. source
- Dapr Workflow: Continues on new code only while it stays deterministic; otherwise patching. source
- Step Functions: A redriven execution uses the same definition; an update needs a new execution. source
- Lambda durable: Unpinned executions continue only if steps are not changed in ways that break replay. source
- Azure Durable: Running orchestrations replay on new code; breaking changes need version branching. source
- Vercel Workflow: Runs are pinned to the deployment that starts them. source
- Airflow: Only with the unversioned local bundle do remaining tasks use the latest code. source
- LangGraph: Old threads resume on the new graph, but renames break them and changes need a version stamp. source
- Jaal: The host agent (process manager) and deploy are built; scaling by instances is ruled (10-24) but not built; you install Postgres.
- Temporal: You host and scale your own workers, even on Temporal Cloud; self-hosting adds a server cluster and a database. source
- Restate: The server is one binary with no database, but you deploy and run your own services. source
- Inngest: The engine is one binary or a cloud; your function code runs in your own app or host. source
- Resonate: One command with SQLite for development; Postgres and your own processes in production. source
- DBOS: A library in your app plus Postgres; you host and scale the app, or use DBOS Cloud. source
- Hatchet: An engine image plus Postgres, or Hatchet Cloud; you always run the workers. source
- Trigger.dev: Fully managed in its own cloud; self-hosting needs Postgres, Redis and runners. source
- Conductor: A server plus a database and a lock service, or Orkes Cloud; your task workers are yours. source
- Cadence: You run frontend, history and matching services plus Cassandra, MySQL or Postgres. source
- Dapr Workflow: You configure an actor state store and host the app (and a control plane on Kubernetes). source
- Vercel Workflow: Automatic on Vercel; elsewhere you supply a storage and queue backend such as Postgres. source
- Prefect: A server, a database and workers you run; Prefect Cloud's managed pools come close. source
- Airflow: You assemble a metadata database, scheduler, API server, DAG processor and executor. source
- LangGraph: Fully managed on LangSmith Cloud; self-hosting needs Postgres, Redis and a license. source
- Ray: Ships head, workers and an autoscaler, but you provision the cluster. source
- Restate: Forcing an existing registration is for local development; production versions are new deployments. source
- DBOS: Only workflows of the current version are recovered. source
- Trigger.dev: A run never changes version, even after new deploys. source
- Conductor: Running executions keep their start-time snapshot. source
- Step Functions: An updated definition needs a new execution. source
- Lambda durable: Unpinned executions may resume on changed code, but the docs recommend pinning. source
- Vercel Workflow: Workflows keep running on the deployment they were created on. source
- Inngest: A resumed run uses the current code. source
- Cadence: Running workflows replay on the updated code. source
- Dapr Workflow: Code updates apply to running instances. source
- Lambda durable: Executions are pinned when you invoke a version or alias. source
- Azure Durable: Version settings can match work to versions; by default code changes affect running work. source
- Airflow: With a versioned bundle a run keeps its code; the default local bundle does not. source
- LangGraph: The latest graph applies to every thread. source
- Resonate: Its blog describes un-sequenced promises rather than a sequenced log. source
- LangGraph: Checkpoints accumulate per thread, but are snapshots, not described as a log. source
- Inngest: Only a Kotlin SDK in its GitHub org, not on the SDK list; its Java example says coming soon. source
- Golem: Scala is listed; Java is not. source
- Resonate: Labelled pre-release. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: An experimental task SDK: task logic only; DAGs are always Python. source
- LangGraph: A community port only, not in the vendor's docs. source
- Ray: The Java APIs are experimental and community-supported. source
- Jaal: Not built yet. A Python orchestrator turns green once it compiles and runs end to end.
- Golem: Not on its language list. source
- Trigger.dev: A TypeScript task can run Python scripts; the task itself stays TypeScript. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Python Workflows are in open beta. source
- Vercel Workflow: Python is labelled beta. source
- Jaal: Not built yet.
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Golem: Go is listed only in v1.6, still a release candidate. source
- Resonate: Labelled early release. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: An experimental task SDK: task logic only; DAGs are always Python. source
- LangGraph: A community port only, not in the vendor's docs. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Inngest: Not on its language list. source
- Resonate: Labelled early development. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Dapr Workflow: Not on its language list. source
- Step Functions: Only as Lambda tasks; the workflow is Amazon States Language. source
- Lambda durable: Not on its language list. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: Not on its language list. source
- Jaal: Not built yet.
- Temporal: Not on its language list. source
- Restate: Not on its language list. source
- Inngest: Not on its language list. source
- Golem: Not on its language list. source
- Resonate: Not on its language list. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: Not on its language list. source
- Cadence: Not on its language list. source
- Dapr Workflow: Not on its language list. source
- Step Functions: Only as Lambda tasks; the workflow is Amazon States Language. source
- Lambda durable: Not on its language list. source
- Azure Durable: Not on its language list. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: The C++ APIs are experimental and community-supported. source
- Jaal: Not built yet.
- Restate: Not on its language list. source
- Inngest: Not on its language list. source
- Golem: Not on its language list. source
- Resonate: Not on its language list. source
- DBOS: Not on its language list. source
- Hatchet: Not on its language list. source
- Trigger.dev: Not on its language list. source
- Conductor: The SDK builds a JSON definition the server interprets; branches and loops are DSL tasks, not if and for. source
- Cadence: Not on its language list. source
- Step Functions: CDK in this language only generates the JSON state machine; the workflow is Amazon States Language. source
- Cloudflare Workflows: Not on its language list. source
- Vercel Workflow: Not on its language list. source
- Prefect: Not on its language list. source
- Airflow: Not on its language list. source
- LangGraph: Not on its language list. source
- Ray: Not on its language list. source
The notes behind each grey and red mark (163)
- Temporal: You write deterministic Workflows plus Activities with its SDK. source
- Restate: You write handlers, virtual objects and workflows, wrapping work in ctx.run. source
- Inngest: You write steps with step.run and the other step functions. source
- Golem: Durability needs no wrappers, but parallel work is written with worker clients, create_promise and await_promise. source
- Resonate: You write generator functions that call context.run. source
- DBOS: You annotate workflows and steps. source
- Hatchet: You define tasks, DAG workflows and durable tasks. source
- Trigger.dev: You define tasks with task() and call triggerAndWait. source
- Conductor: Workflows are JSON definitions. source
- Cadence: You write deterministic workflows plus activities with its SDK. source
- Dapr Workflow: You write workflows plus activities with its SDK. source
- Step Functions: Workflows are JSON state machines (Amazon States Language). source
- Lambda durable: You write DurableContext steps, waits and invokes. source
- Azure Durable: You write orchestrator, activity and entity functions. source
- Cloudflare Workflows: You write step.do and step.sleep calls. source
- Vercel Workflow: You mark code with the "use workflow" and "use step" directives. source
- Prefect: You decorate code with @flow and @task. source
- Airflow: You define Python DAGs. source
- LangGraph: You build a graph of nodes and edges, or use @entrypoint and @task. source
- Ray: You write @ray.remote tasks and actors. source
- Temporal: Parallel work is written with futures, Promise.all or asyncio.gather. source
- Restate: Parallel work is written with DurableFuture.all or Select. source
- Inngest: Parallel steps are written with Promise.all. source
- Golem: Parallel work fans out to child agents explicitly. source
- Resonate: Parallel work is written with beginRun / beginRpc futures. source
- DBOS: Parallel work goes through durable queues. source
- Hatchet: Parallel work is declared as DAG dependencies or child tasks. source
- Trigger.dev: Parallel work is written with batchTriggerAndWait. source
- Conductor: Parallel work is a FORK_JOIN declared in the definition. source
- Dapr Workflow: Parallel work is written with when_all / Task.WhenAll. source
- Step Functions: Parallel work is a Parallel or Map state. source
- Lambda durable: Parallel work is written with context.parallel or context.map. source
- Azure Durable: Parallel work is an explicit fan-out / fan-in (Task.WhenAll). source
- Cloudflare Workflows: Parallel steps are written with Promise.all. source
- Vercel Workflow: Parallel steps are written with Promise.all. source
- Prefect: Parallel work is written with .submit() and task runners. source
- Airflow: Parallel work is declared as DAG dependencies. source
- LangGraph: Parallel work is declared with fan-out edges and Send. source
- Ray: Parallel work is written with .remote() futures. source
- Temporal: A version is a Build ID supplied in the worker's deployment options. source
- Restate: Running requests stay on their version automatically, but a version is the endpoint you deploy and register, not derived from the code. source
- Inngest: There are no versions: a resumed run uses the current code. source
- Golem: Agents stay on their revision, but the docs do not say a revision is derived from content. source
- Resonate: A version is a developer option, 1 by default. source
- Trigger.dev: Each deploy makes a version and a run never changes version, but the identity is a date and counter. source
- Cadence: Changes are guarded with GetVersion markers the developer numbers. source
- Dapr Workflow: Versioning is by patching and developer-named workflow versions. source
- Step Functions: Executions stay on a published version, but publishing is explicit and the number is a counter. source
- Lambda durable: Executions can be pinned to a published version, but you publish and invoke it yourself. source
- Azure Durable: Orchestration versioning uses a version string the developer sets. source
- Vercel Workflow: Runs are pinned to the deployment that starts them, identified by deployment ID, not by content. source
- Airflow: With a Git bundle a run keeps its commit; the default local bundle has no versioning. source
- LangGraph: There are no versions: the latest graph applies to every thread. source
- Temporal: Recovery replays the workflow code against the event history. source
- Restate: Recovery is replay of the handler, skipping journaled steps. source
- Inngest: A run resumes by replaying the handler with saved step results. source
- Golem: Recovery replays the oplog; opt-in snapshots shorten the replay. source
- Resonate: After a crash, execution starts from the top of the function. source
- DBOS: An interrupted workflow is restarted by calling it again with its inputs. source
- Hatchet: The durable event log is replayed to rebuild state. source
- Trigger.dev: A retried task starts from the beginning, with cached subtask results. source
- Cadence: Workflow code is re-executed to rebuild state. source
- Dapr Workflow: On replay the workflow runs again from the beginning. source
- Step Functions: A failed run can be redriven from the failed step, by hand. source
- Lambda durable: On replay the code runs from the beginning, skipping completed checkpoints. source
- Azure Durable: The orchestrator re-executes the whole function from the start. source
- Cloudflare Workflows: Code outside steps runs again after hibernation. source
- Vercel Workflow: Workflow code is re-run, using the event log to reach its spot. source
- LangGraph: Graph API nodes re-run from the start of the interrupted node; the Functional API replays from the top. source
- Temporal: Compensation is a documented saga pattern written in workflow code. source
- Restate: Sagas are implemented in user code. source
- Dapr Workflow: Compensation is a documented pattern written in the workflow. source
- Step Functions: Compensation is a pattern built from Catch fields. source
- Azure Durable: Compensation is written in a catch block. source
- Vercel Workflow: Compensation is a saga recipe written in the workflow. source
- Restate: Registration refuses removed or renamed handlers, but checks no input or output types, and a flag overrides it. source
- Dapr Workflow: Changing a workflow's name, input or output deploys and can fail at runtime. source
- Azure Durable: An activity signature change deploys and can break orchestrators that use it. source
- Restate: Sagas are written in the calling handler, adding compensations step by step. source
- Golem: An operation carries its own undo, but the caller must open the transaction. source
- Conductor: The workflow author configures a failure workflow. source
- Prefect: A task declares its rollback hook, but the flow must open a transaction. source
- Temporal: The call reads like a function, but on a handle built first with proxyActivities in the workflow. source
- Restate: Calls go through a typed client taken from the context: ctx.serviceClient(...).handler(...). source
- Inngest: Another function is called through step.invoke with an ID and options. source
- Golem: Methods are called directly, on a client obtained first with get(...). source
- Resonate: Every durable call goes through ctx.run or ctx.rpc. source
- DBOS: A step is called as a plain function, but it is a step in the same program, not another service. source
- Hatchet: A child is spawned with a generic run() on a task object. source
- Trigger.dev: A child is called with triggerAndWait and returns a result wrapper to unwrap. source
- Conductor: Tasks are referenced by registered names in a JSON definition. source
- Cadence: Java calls a method on a stub made with newActivityStub; Go uses ExecuteActivity(ctx, fn). source
- Dapr Workflow: Activities are called through the context: CallActivityAsync and its kin. source
- Step Functions: Task states call services through a Resource ARN in JSON. source
- Lambda durable: Service calls are wrapped in steps on the DurableContext. source
- Azure Durable: The default is CallActivityAsync(nameof(...)); typed calls come from a source generator in preview. source
- Cloudflare Workflows: Work is written as step.do(name, fn). source
- Vercel Workflow: A step is called with a plain await, but it is a step in the same program, not another service. source
- Prefect: A task is called like a normal function, but it is a task in the same flow, not another service. source
- Airflow: TaskFlow calls look like functions but build DAG dependencies behind the scenes. source
- LangGraph: A @task call returns a future read with .result(); the Graph API is nodes and edges. source
- Ray: A remote function is called with .remote() and read with ray.get. source
- Jaal: Work in progress: the recorded stream and its resume point are designed, not built.
- Temporal: Workflow Streams (preview) resume a subscriber from its offset; a retried activity streams its events again. source
- Inngest: The browser can reattach, but a step that fails and retries streams fresh data. source
- DBOS: Writes survive a restart, but a retried step can write its output again. source
- Hatchet: Events published before a consumer starts are dropped; no resume is described. source
- Trigger.dev: A lost connection resumes from the last chunk; a task crash or retry is not covered. source
- Vercel Workflow: Clients reconnect from a start index; a step crashing mid-stream is not described. source
- LangGraph: A dropped connection resumes with Last-Event-ID; a server crash is not covered. source
- Temporal: An optional Go-only checker, workflowcheck, can help; other SDKs check at run time. source
- Resonate: Determinism is left to the author, with no static check. source
- DBOS: The only check is a run-time non-determinism error. source
- Trigger.dev: The docs tell developers which concurrent calls to avoid. source
- Cadence: Workflow code must use Cadence channels and selectors; no official checker. source
- Dapr Workflow: Determinism is a rule for authors, with no analyzer. source
- Lambda durable: A Go-only linter, durablelint, flags issues and can be silenced. source
- Azure Durable: A C#-only Roslyn analyzer warns at build time; its warnings can be suppressed. source
- Cloudflare Workflows: Concurrency over steps is written guidance only. source
- LangGraph: Conflicting parallel writes are refused only while the graph runs. source
- Temporal: Only in the TypeScript sandbox are Math.random and Date made deterministic; other effects must be Activities. source
- Restate: Non-deterministic work must be wrapped in run(). source
- Inngest: Non-deterministic logic must go inside step.run(). source
- Resonate: Non-deterministic calls go in a leaf function run via ctx.run. source
- DBOS: A workflow function must be deterministic. source
- Hatchet: Durable-task code must be deterministic; avoid clock and random reads. source
- Cadence: Workflow code must use Cadence APIs for time and must be deterministic. source
- Dapr Workflow: Workflow code must be deterministic. source
- Lambda durable: Every non-deterministic call must be wrapped in a step. source
- Azure Durable: Orchestrator code must not use time APIs or random values directly. source
- Cloudflare Workflows: Non-deterministic logic must go inside step.do. source
- Vercel Workflow: Math.random and Date are fixed automatically; any other effect must be a step. source
- LangGraph: Random numbers and clock reads must be kept inside tasks. source
- Temporal: Running workflows continue on new code only for replay-compatible changes; otherwise patching is needed. source
- Restate: Requests start and end on the same version. source
- Golem: An update replays the agent on the new revision, but must be triggered and falls back if replay diverges. source
- DBOS: Only workflows whose version matches the current application are recovered. source
- Trigger.dev: A run never changes version. source
- Conductor: Running executions keep the definition snapshot taken at start. source
- Cadence: Continues on new code only for compatible changes; otherwise GetVersion is needed. source
- Dapr Workflow: Continues on new code only while it stays deterministic; otherwise patching. source
- Step Functions: A redriven execution uses the same definition; an update needs a new execution. source
- Lambda durable: Unpinned executions continue only if steps are not changed in ways that break replay. source
- Azure Durable: Running orchestrations replay on new code; breaking changes need version branching. source
- Vercel Workflow: Runs are pinned to the deployment that starts them. source
- Airflow: Only with the unversioned local bundle do remaining tasks use the latest code. source
- LangGraph: Old threads resume on the new graph, but renames break them and changes need a version stamp. source
- Restate: Forcing an existing registration is for local development; production versions are new deployments. source
- DBOS: Only workflows of the current version are recovered. source
- Trigger.dev: A run never changes version, even after new deploys. source
- Conductor: Running executions keep their start-time snapshot. source
- Step Functions: An updated definition needs a new execution. source
- Lambda durable: Unpinned executions may resume on changed code, but the docs recommend pinning. source
- Vercel Workflow: Workflows keep running on the deployment they were created on. source
- Inngest: A resumed run uses the current code. source
- Cadence: Running workflows replay on the updated code. source
- Dapr Workflow: Code updates apply to running instances. source
- Lambda durable: Executions are pinned when you invoke a version or alias. source
- Azure Durable: Version settings can match work to versions; by default code changes affect running work. source
- Airflow: With a versioned bundle a run keeps its code; the default local bundle does not. source
- LangGraph: The latest graph applies to every thread. source
- Resonate: Its blog describes un-sequenced promises rather than a sequenced log. source
- LangGraph: Checkpoints accumulate per thread, but are snapshots, not described as a log. source
Write a parallel loop with an async call
The task: call a service once for every element of input, all calls at once, results in output in order. Each snippet is the pattern the product’s own docs show for parallel work, with the names changed to match. Why we think hand-written async is the wrong default: the loop you meant to write.
| Product | The code you write | What you add to a plain loop |
|---|---|---|
| Jaaljava | | One annotation, @Parallel, on the index, and the count declared before the loop. The compiler checks that each iteration writes only its own cell and refuses the mark by name if not. No await, no futures, no Promise.all. built today |
| Temporaltypescript | | Promise.all and a map; invoke comes from proxyActivities with a timeoutHow this was adaptedDocs example is processInParallel(items) with items.map(process) + Promise.all; renamed only. |
| Restatetypescript | | collect promises in a loop and await RestatePromise.all; invoke is a handler on a service reached via ctx.serviceClientHow this was adaptedDocs use a for...of loop pushing ctx.serviceClient(fanOutWorker).runSubtask(subtask); must use RestatePromise.all, not Promise.all. |
| Inngesttypescript | | Promise.all and a map; each call wrapped in step.run with a unique, stable step idHow this was adaptedDocs example maps chunks to step.run(`summarize-chunk-${index}`, ...) inside Promise.all; trigger syntax per the v4 SDK shown on that page. |
| Golemtypescript | | a second agent type (defineAgent + implement) for the worker, one agent instance per element via Worker.client.get({id}), then Promise.allHow this was adaptedDocs say a single agent handles invocations sequentially, so parallelism requires one worker agent instance per item. Docs method is process({ data }); renamed to invoke. URL is versioned (v1.6). |
| Resonatetypescript | | a generator function; ctx.beginRun per element to start it, then yield* each future to collectHow this was adaptedDocs example spawns four fixed calls (const emailFuture = yield* ctx.beginRun(sendEmail, event); ...) and fans in with yield* on each; the loop form here is an adaptation of that pattern, not shown verbatim. |
| DBOStypescript | | register invoke as a step and the body as a workflow; Promise.allSettled over a map, then unwrap the resultsHow this was adaptedDocs show Promise.allSettled([step1(..), step2(..), ...]) and say steps must start in a deterministic order; they advise allSettled over Promise.all. registerStep is from the step tutorial. The unwrap line is mine. |
| Hatchettypescript | | invoke must itself be a hatchet.task (a child task); push child.run() promises and await Promise.allHow this was adaptedDocs parent loops for (let i = 0; i < n; i++) promises.push(child.run({ N: i })) then Promise.all; ctx.bulkRunChildren is the documented bulk alternative. Fetched via docs.hatchet.run/llms/v1/child-spawning.md. |
| Trigger.devtypescript | | invoke must be its own task(); one batchTriggerAndWait with an array of {payload}, then check run.ok and read run.output per runHow this was adaptedDocs forbid wrapping triggerAndWait in Promise.all and say to use batchTriggerAndWait. Docs examples pass a literal array; the .map to build it is mine. Docs do not state explicitly that results.runs is in input order. |
| Conductorjson | | a FORK_JOIN_DYNAMIC task naming the worker task plus a mandatory JOIN; invoke is a separately registered worker (SIMPLE) taskHow this was adaptedDocs and Orkes examples set forkTaskInputs to a literal array; the ${workflow.input.input} expression form is an adaptation. JOIN output is a map keyed by each fork's taskReferenceName, not an ordered array; Conductor adds __index to each fork input for re-ordering. forkTaskType appears in the Orkes reference (https://orkes.io/content/reference-docs/operators/dynamic-fork). |
| Cadencego | | ctx carrying ActivityOptions (WithActivityOptions); two loops, one to start futures, one to Get themHow this was adaptedDocs state this pattern in prose (repeatedly call workflow.ExecuteActivity, store the futures, call Get later) and show a single-call code sample; the loops are an adaptation. Bounded alternative: workflow.NewBatchFuture (https://cadenceworkflow.io/docs/go-client/batch-future). Server limit of 1024 pending activities per workflow by default. |
| Dapr Workflowtypescript | | a generator function; push ctx.callActivity tasks in a loop, then yield ctx.whenAll; invoke registered as an activity on the WorkflowRuntimeHow this was adaptedDocs JS tab first fetches workItems with an activity; here input is the workflow input. The JS SDK's workflow support is newer than the .NET/Python/Java/Go SDKs; check its stability label. |
| AWS Step Functionsjson | | a Map state with ItemsPath/ResultPath and an ItemProcessor sub-state-machine whose Task calls the invoke LambdaHow this was adaptedInline mode runs at most 40 iterations concurrently (MaxConcurrency 0 = as many as possible); larger fan-outs need Distributed mode. Map output is a JSON array with one item per iteration. "Payload.$": "$" is my addition to pass the item explicitly (docs example omits Payload). |
| AWS Lambda durable functionstypescript | | wrap the handler in withDurableExecution; context.map with a name and a per-item callback that runs ctx.stepHow this was adaptedgetResults() returns succeeded items' results in input order (failed items are dropped from it; inspect the BatchResult). maxConcurrency default is unlimited. |
| Azure Durable Functionstypescript | | a generator orchestrator; map to context.df.callActivity tasks, then yield context.df.Task.all; invoke registered with df.app.activityHow this was adaptedDocs example maps over a hard-coded cities array; getInput() line is my adaptation for the input. |
| Cloudflare Workflowstypescript | | Promise.all and a map; each call wrapped in step.do with a deterministic, unique nameHow this was adaptedDocs show Promise.all over two literal step.do calls and separately allow dynamically named steps (one per list entry) when deterministic; the map form combines the two. Results must be JSON-serializable. |
| Vercel Workflowtypescript | | "use workflow" / "use step" directives; Promise.all and a mapHow this was adaptedDocs show Promise.all(friends.map((friend, i) => sendRSVPEmail(friend, webhooks[i]))) in a "use workflow" function; product name used here as listed. |
| Prefectpython | | @task/@flow decorators; task.map returns futures, .result() collects themHow this was adaptedPrefect has no TypeScript SDK. Docs example asserts [6, 7, 8] for inputs [1, 2, 3], implying input order; order is not stated in prose. Real parallelism depends on the flow's task runner (ThreadPoolTaskRunner default). |
| Apache Airflowpython | | @task decorators inside a DAG; .expand(x=...) creates one mapped task instance per element, and a downstream task receives the lazy sequenceHow this was adaptedinput must be a literal list or an upstream task's output (XCom), not a runtime Python variable. Docs give no explicit ordering statement for the collected lazy sequence (it follows map_index in practice). Mapped instances run in parallel subject to executor/pool limits. |
| LangGraphtypescript | | wrap invoke in task() and the body in entrypoint() with a checkpointer; Promise.all and a mapHow this was adaptedThis is the Functional API. In the Graph API the fan-out is addConditionalEdges returning state.items.map(x => new Send("invoke", { x })) with a reducer on state; that form does not preserve order (https://docs.langchain.com/oss/javascript/langgraph/graph-api). |
| Raypython | | @ray.remote decorator; .remote() in a list comprehension, then ray.get on the listHow this was adaptedDocs example: futures = [square.remote(i) for i in range(4)]; print(ray.get(futures)) -> [0, 1, 4, 9] (input order). Ray tasks are not durable workflows; no replay on driver failure. |
Two calls in parallel, a third that needs both
A payment: score the order for fraud and check the customer’s credit at the same time, then authorize with both answers. Not a loop and not a straight line: two branches that join. Each snippet follows the pattern the product’s own docs show, with the names changed to match. Where the docs document a restriction that makes this shape awkward, it is marked.
| Product | The code you write | What you add, and what limits it |
|---|---|---|
| Jaaljava | | Nothing. The compiler sees that score and check do not depend on each other, runs them in parallel, and starts authorize when both answers are back. built today |
| Temporaltypescript | | Each call becomes an activity (separate activities file, registered on a worker) reached through proxyActivities with a timeout; the developer groups the two branches with Promise.all. Limits: None found for this shape. General: workflow code must be deterministic (sandboxed, replayed); per-execution limits (2,000 pending activities, 51,200 history events, 4 MB gRPC message) only matter at wide fan-out. Grouping is by hand (Promise.all). How this was adaptedDocs example is Promise.all over a homogeneous items.map(process); adapted to two different activities destructured from one proxyActivities call (the activity-basics page shows Promise.all([act2, act3]) of different activities). |
| Restatetypescript | | Each call wrapped in a journaled ctx.run (named); start both before awaiting and combine with RestatePromise.all, not plain Promise.all. Limits: None found that blocks the shape. Docs direct coordination through RestatePromise combinators (they record completion order for deterministic replay); calls run concurrently only if started before being awaited. Grouping is by hand. How this was adaptedCombined the docs' ctx.run concurrency example and RestatePromise.all example; service/handler wrapper is the standard SDK shape. Could equally be ctx.serviceClient(...) calls to other Restate services. |
| Inngesttypescript | | Each call wrapped in step.run with a stable step ID; function bound to an event trigger; Promise.all for the two branches. Limits: None that blocks the shape. Documented behaviors: step IDs should be stable; on serverless hosts each parallel step runs as its own execution and the function is re-invoked with all results; a bare Promise.race waits for all steps unless wrapped in group.parallel(); limits of 1,000 steps, 4 MiB per step output, 32 MiB run state. Grouping is by hand. How this was adaptedWritten from the docs' Promise.all-over-step.run example (TS SDK v4 createFunction form with triggers in the config); names adapted. |
| Golemtypescript | | Each service becomes its own agent (defineAgent with zod id/method schemas, implemented separately); the orchestrator obtains clients via .client.get(id) and groups the two RPCs with Promise.all. Restriction: 'Golem agents process invocations sequentially - a single agent cannot run work in parallel' (parallel-workers guide). Concurrency exists only across distinct agent instances, so fraud and credit must be separate agents (or instances); two calls to the same instance serialize. Awaiting RPC cycles (A awaits B awaits A) deadlock. Child agents persist after the run and must be deleted if unwanted. Docs are mid-transition (v1.5 @agent/Worker.get vs current defineAgent/.client.get). How this was adaptedAssembled from two pages: Promise.all over child-agent calls (v1.5 parallel-workers guide, @agent + Worker.get(i) syntax) and the current client syntax Counter.client.get({...}) with object-shaped method input (call-another-agent guide). The agent definitions, zod schemas and method-body wrapper are omitted; exact current method-implementation signature not shown in docs. Assembled from two versions of the docs; not checked as one program. |
| Resonatetypescript | | Generator function with yield*; ctx.beginRun for each branch (ctx.run would block), then yield* on each promise; each called function takes a Context first. Limits: None found for the shape. ctx.beginRun is same-process; to run a branch on another worker use ctx.beginRpc with ctx.options({ target }). Nondeterminism must go through ctx.date/ctx.math. Generator functions default to no retries. Grouping is by hand (begin/await ordering). How this was adaptedDocs page describes beginRun + yield* on the returned promise; snippet written from that description with diamond names. |
| DBOStypescript | | Each call wrapped in DBOS.runStep with a name; the workflow is registered with DBOS.registerWorkflow; the two branches use Promise.allSettled plus manual unwrapping of status/reason/value. Restriction: 'Avoid using Promise.all because of how it handles errors and rejections' - use Promise.allSettled; concurrent steps are valid only if started in a deterministic order before awaiting. A branch that is itself a chain of two or more steps (e.g. fraud.score then fraud.explain) is NOT allowed inline - the docs say to start child workflows with startWorkflow and await their handles. The one-step-per-branch diamond is fine. Grouping is by hand. How this was adaptedCombined the docs' Promise.allSettled valid example with the DBOS.runStep(..., {name}) and DBOS.registerWorkflow forms; the rejection unwrapping is the developer's own code. |
| Hatchettypescript | | Each call is a declared task on a workflow; the join is expressed by parents: [fraud, credit]; outputs read back with ctx.parentOutput(task); tasks run on registered workers. Limits: None found for the shape - it is a native DAG: parallelism follows from the declared parents edges, not hand grouping. Constraints: the DAG must be declared up front and be acyclic; a child runs only after all parents succeed; conditions limited to parent-based skip_if/cancel_if (CEL). Each task is a separately scheduled run (queue hop per step). How this was adaptedAdapted the docs' two-task DAG (toLower -> reverse) to two parents; outputs returned as objects to match the docs' examples (docs do not state whether bare scalars are allowed). |
| Trigger.devtypescript | | Each branch must become its own task (separate run); the parent fans out with batch.triggerByTaskAndWait and unwraps runs[i].ok / .output; the dependent call runs inline in the parent. Restriction: triggerAndWait docs say 'Don't use this in parallel, e.g. with Promise.all()' - so the natural Promise.all form is forbidden. batch.triggerByTaskAndWait DOES accept two different tasks, typed per position (runs[0].output typed as childTask1's output), so the diamond is expressible, but only by promoting each branch to a separately triggered task run and reading results positionally with ok-checks. Task-wait calls must be inside a task. Grouping is by hand (the batch array). How this was adaptedDocs' batch.triggerByTaskAndWait example uses childTask1/childTask2 with object payloads; adapted names/payloads. Import path assumes SDK v4 (@trigger.dev/sdk); v3 used @trigger.dev/sdk/v3. authorize is a plain call in the parent (could be a third task via triggerAndWait). |
| Conductorjson | | A FORK_JOIN with one inner list per branch, a mandatory JOIN listing the task refs in joinOn, ${ref.output.x} wiring into the third task, plus a registered task definition and a polling worker for each SIMPLE task. Restriction: 'The Fork task must be followed by a Join'; joinOn must name the refs to wait for - if omitted the Join proceeds without waiting (join-task page); the Fork itself has no output, so the dependent task reads each branch by ${taskRef.output.key} (worker outputs are key/value maps). Grouping is by hand (you author the fork block); the DAG is not derived from data references. How this was adaptedShape follows the docs' notification fork/join example; SIMPLE task names, input wiring and output keys (risk, limit) adapted. joinOn semantics taken from the conductor-oss join-task page. |
| Cadencego | | ActivityOptions with timeouts on the context; each call is a registered activity; start both with ExecuteActivity (returns a Future) and call Get on each later, with an error check per Future. Limits: None found for the shape. Docs caution only that very large fan-outs can exceed the per-workflow pending-activity limit (use Batch Future). Workflow code must be deterministic (replay). Grouping is by hand (start-then-Get ordering). How this was adaptedDocs show the options struct with cadence.ActivityOptions/cadence.WithActivityOptions aliases (older import) and describe the 'call ExecuteActivity repeatedly, Get later' pattern in prose; the code is assembled from those. |
| Dapr Workflowtypescript | | Activities as functions taking a WorkflowActivityContext and a single input; a generator workflow using yield ctx.callActivity and yield ctx.whenAll; registration of the workflow and each activity on a WorkflowRuntime. Limits: No shape-specific restriction found. Durable-Task style: the orchestrator replays from the top, so it must be deterministic and must use ctx.whenAll (a Task combinator) rather than Promise.all; an activity takes one input, so authorize receives one object. Grouping is by hand. Note: the official SDK table does not clearly mark Workflow for JavaScript (Go, .NET, Python, Java are the marked ones). How this was adaptedAdapted from the docs' JavaScript fan-out/fan-in tab (homogeneous ctx.callActivity map + ctx.whenAll) to two different activities; activity/registration shapes from the same tab. JS workflow SDK maturity not stated on the docs. |
| AWS Step Functionsasl json | | A Parallel state with one self-contained sub-state-machine per branch, ResultPath to keep the original input, and JSONPath Parameters that pick each branch result by array index; each call is a separately deployed Lambda (or service integration). Restriction: the Parallel state's output is 'an array with one element for each branch', so the dependent state addresses results positionally ($.checks[0], $.checks[1]); without ResultPath the array replaces the input and order is lost. Each branch must be self-contained (no transitions in or out). If any branch fails the whole Parallel fails and all branches stop (Lambdas already running keep running). Grouping is by hand (you author the Parallel block). How this was adaptedBuilt on the docs' two-branch Parallel example (LookupAddress/LookupPhone); ResultPath + Parameters (JSONPath mode) are standard ASL fields listed on the page; ARNs are placeholders. |
| AWS Lambda durable functionstypescript | | Handler wrapped in withDurableExecution; each call wrapped in a named ctx.step; the two branches go through context.parallel (each branch a function of a child DurableContext); BatchResult unwrapped with throwIfError/getResults. Restriction: context.parallel returns a BatchResult whose getResults() is positional and typed with a single result type - the reference says 'All branches must return the same type' (Go offers ParallelMixed for different types), so in TS two heterogeneous branches need a union type or a cast. Branch failures are captured in the BatchResult and only rethrown by throwIfError(). Whether raw Promise.all over context.step is safe is not documented; the documented route is parallel/map. Durable operations cannot be nested inside a step callback. Grouping is by hand. How this was adaptedAdapted from the reference's three-branch context.parallel example (homogeneous string results); the cast for heterogeneous results is my adaptation. |
| Azure Durable Functionstypescript | | Generator orchestrator with yield; each call registered as a named activity and invoked by string name with one input; parallel branches via context.df.Task.all. Restriction: 'orchestrator functions should use context.df.Task.all and context.df.Task.any instead of Promise.all and Promise.race'; the orchestrator is replayed from the start and must be deterministic; callActivity passes a single input, so authorize takes one object. Grouping is by hand. How this was adaptedDocs example (Node v4 model) is a homogeneous fan-out over files with Task.all; adapted to two different activities. |
| Cloudflare Workflowstypescript | | A WorkflowEntrypoint class with run(event, step); each call wrapped in step.do with a name; Promise.all for the branches; plus a wrangler binding for the workflow. Limits: None that blocks the shape. Rules: step names are the cache key and must be deterministic (no timestamps/randomness in names); state must come only from step return values (which must be serializable); Promise.race/any over step.do should be wrapped in one outer step.do (does not apply to Promise.all). Rules also say not to call separate services in one step - consistent with three steps. Grouping is by hand. How this was adaptedRules page shows Promise.all over two step.do calls (the cat-image example); class skeleton from the Workers API page; names adapted. |
| Vercel Workflowtypescript | | "use step" directive on each call's function and "use workflow" on the body (compiled by the Workflow SDK); Promise.all for the branches. Limits: None found for the shape. Documented: step inputs/outputs must be serializable; Promise.all rejects on first failure (use allSettled for partial); Promise.race does not cancel losers; very wide fan-out should be chunked; split runs before they grow past a few thousand events. Grouping is by hand. How this was adaptedDocs example is Promise.all over three declared step functions (fetchUser/fetchOrders/fetchPreferences) with declare stubs; the "use step" bodies are written out here. Docs moved from useworkflow.dev to workflow-sdk.dev; GA status of the SDK not checked. |
| Prefectpython | | @task on each call and @flow on the body; .submit() to get concurrency (a direct call runs synchronously); futures passed straight into the dependent task, .result() only on the terminal one. Limits: None found for the shape - the join is inferred from the futures passed as arguments ('the downstream task won't receive the PrefectFuture'). Constraints: concurrency is opt-in via .submit() (the developer still chooses which calls go concurrent); futures must be resolved before returning from the flow; with a bounded ThreadPoolTaskRunner, calling .result() on a child from inside a parent task can deadlock; default thread runner needs thread-safe globals. How this was adaptedCombined the docs' submit-then-pass-future example (say_hello -> print_result) with two submitted parents; names adapted. |
| Apache Airflowpython | | @dag/@task decorators; the order arrives as a DAG param read from the injected context; passing return values (XComArgs) as arguments declares the edges. Limits: None found for the shape - TaskFlow infers fraud_score >> authorize and credit_check >> authorize from the arguments; the parallelism is not hand-grouped. Constraints: values passed between tasks go through XCom and must be serializable (built-ins, dataclasses, or custom serialize/deserialize); each task is a separately scheduled task instance (scheduler latency per step); DAG structure is fixed at parse time. How this was adaptedDocs example is compose_email(get_ip()); extended to two upstream tasks. Reading params via a named context kwarg is the documented context-injection mechanism, though the page does not show params specifically. |
| LangGraphtypescript | | A state schema with one channel per value; each call becomes a node returning a partial state update; edges from START fan out and two edges fan in. Restriction: fan-in by separate edges works here only because both branches are one node long and finish in the same superstep; with branches of different lengths the target 'runs more than once' - the docs say to use a list-form edge (add_edge([a, b], target)) or defer=True, and a list-form target never runs (no error) if a listed branch is skipped. Parallel branches writing the same key need a reducer (here keys differ, so none). Graph API derives parallelism from edges; no hand grouping. How this was adaptedDocs' JS fan-out example uses StateSchema/ReducedValue and a->b,c->d; adapted to START fan-out with Annotation.Root (documented alternative state definition) and distinct keys. The list-form edge / defer rule is quoted from the Python version of the same page. |
| Raypython | | @ray.remote on each function, .remote() to launch, object refs passed into the dependent task, ray.get on the final ref. Limits: None found for the shape - passing refs as arguments makes Ray wait for both before running authorize (the dependency is inferred from data, no hand grouping). Not a durable workflow engine: Ray Core retries failed tasks but has no checkpointed workflow state across driver failure. How this was adaptedDocs show passing one object ref into a second task; extended to two refs. No durability layer used (Ray Workflows is not part of this pattern). |
The short version
Plain code, no SDK constructs
Nearly every engine asks you to write its constructs: activities, step.run, ctx.run, decorators, directives, DAGs or JSON state machines. Golem is the exception that comes closest: plain code compiled to WebAssembly, durable with "no annotations, step functions, or activity wrappers". Jaal reads plain Java too (Python in progress), and adds a compiler that refuses what cannot be made durable, by name.
Parallelism without writing it
In every product we checked, parallel work is written out: futures, Promise.all, asyncio.gather, DAG edges, Parallel or Map states, ensemble nodes. In Jaal, calls that do not depend on each other run together because the compiler proved they can.
Derive, not vibe: the graph comes from the code
Elsewhere you declare the graph: nodes and edges in LangGraph, JSON states in Step Functions and Conductor, DAGs in Airflow, YAML in KServe, ensemble configs in Triton. Jaal derives it from the code you wrote, keeps it in step with every edit, and when you change the graph (make two steps a sequence) it changes the code for you.
No determinism rules
Temporal and Cadence require deterministic workflow code; Cloudflare and Hatchet's durable tasks set determinism rules, and Vercel Workflow replays in a deterministic sandbox. Jaal works out what is deterministic itself and records any call it cannot see into, so a clock read or a random number replays exactly, with nothing for you to avoid.
Resume without calling again
Temporal re-runs workflow code from the start against its history; Azure Durable Functions replays from its event history; Inngest and AWS Lambda durable functions re-run the handler with stored step results. Jaal continues from the last recorded step and never makes a settled call again. Rolling back one step and replaying only what depends on it, from the derived graph, is designed, not built.
Where others are ahead
Temporal has eight language SDKs and years in production. Several products have built-in compensation (Cloudflare, Conductor, Prefect, Golem) and run replay or time travel (LangGraph, Inngest, DBOS, Azure, Restate). Jaal's backtest of a changed version over recorded runs is still planned.
Durable execution and workflow engines
| Product | How you write it | Durability | Parallel work | Compensation (sagas) | Replay and debugging | Versions of running work | License and hosting |
|---|---|---|---|---|---|---|---|
| Jaal | Plain Java (Python in progress); a compiler derives the graph | Every step a transaction in Postgres; a crashed process is finished by another | Automatic: independent calls proven by the compiler; a parallel loop is one annotation | Declared per service and caller (the effect contract): built (the caller in code today) | Replay after a crash from the record: built. A run's graph and timeline: built. Stepping through a run, backtest: planned | Yes: a version is its content | Private beta |
| Temporal | SDK constructs: deterministic Workflows plus Activities; Go, Java, Python, TS, .NET, Ruby, PHP, Rust | Event History; code replayed from the start | Explicit: futures, Promise.all, asyncio.gather | Documented pattern (Saga page) | Replayer over a downloaded history; VS Code replay debugger (TypeScript) | Yes: Worker Versioning, patching | MIT server; Temporal Cloud |
| Restate | SDK constructs: handlers, virtual objects, workflows, ctx.run; TS, Java, Kotlin, Python, Go, Rust | Journal per invocation | Explicit: DurableFuture.all, Select | Documented pattern, in regular code | Restart an invocation from a point in its journal; UI and CLI | Yes: immutable deployments | Server BSL 1.1; Restate Cloud |
| Inngest | SDK constructs: step.run and other steps; TS, Python, Go | Step results memoized outside the function | Explicit: Promise.all over steps | not documented | Rerun from a chosen step; bulk replay | Step-ID memoization | Server SSPL with delayed Apache-2.0; cloud or self-host |
| Golem | Plain code compiled to WebAssembly; Rust, TS, Scala, MoonBit, Go | Oplog at the WebAssembly boundary | Explicit: fan out to child agents, fork() | Built in: transactions (not MoonBit) | Oplog viewer; undo agent state | Yes: automatic or manual agent update | BSL 1.1; Golem Cloud |
| Resonate | SDK constructs: generators with context.run; TS, Python, Go, Java, Rust | Durable promises | Explicit: futures, Promise.all | not documented | not documented | not documented | Apache-2.0 |
| DBOS | Library: annotated workflows and steps; Python, TS, Go, Java | Checkpoints in Postgres | Explicit: durable queues | not documented | Fork a workflow from a chosen step | Yes: app versions, patching | MIT; DBOS Cloud, Conductor console |
| Hatchet | SDK constructs: tasks, DAGs, durable tasks; Python, TS, Go, Ruby | Durable event log in Postgres | Explicit: DAG dependencies, child tasks | not documented | Replay a run (new run, same input) | not documented | MIT; Hatchet Cloud or self-host |
| Trigger.dev | SDK constructs: task(), triggerAndWait; TypeScript | Checkpoint and restore of the task (CRIU) | Explicit: batch trigger | not documented | Replay a run on the latest code | Yes: a run is locked to its version | Apache-2.0; cloud or self-host |
| Conductor (Orkes) | JSON workflow definitions; workers in Java, Python, JS, Go, C# | Executions persisted on the server | Declarative: fork/join, dynamic fork | Built in: failureWorkflow | Rerun from a chosen task | Yes: pinned to a definition snapshot | Apache-2.0; Orkes. The Netflix repository was archived in Dec 2023 |
| Cadence | SDK constructs: workflows plus activities; Go, Java | Event sourcing, history replay | not documented | Documented (orchestration use case) | Replayer, replay shadowing | Yes: GetVersion | Apache-2.0; CNCF Sandbox |
| Dapr Workflow | SDK constructs: workflows plus activities; Python, JS, .NET, Java, Go | History in a state store | Explicit: when_all, Task.WhenAll | Documented pattern | Event history in the dev dashboard | Yes: patching, named versions | Apache-2.0; CNCF graduated |
| AWS Step Functions | JSON state machines (Amazon States Language) | Standard: exactly-once, up to a year; Express: at-least-once | Declarative: Parallel and Map states | Documented pattern via Catch | Redrive failed runs within 14 days | Yes: versions and aliases | Proprietary; AWS only |
| AWS Lambda durable functions | SDK constructs: DurableContext step, wait, invoke; Node, Python, Java, Go, .NET | Checkpoint log; handler re-run with stored results | Explicit: parallel, map | not documented | History kept 1 to 90 days | Yes: pinned to the starting version | Proprietary; AWS only |
| Azure Durable Functions | SDK constructs: orchestrator, activity, entity functions; C#, JS, TS, Python, PowerShell, Java | Event-sourced history replay | Explicit fan-out/fan-in | Documented pattern | Rewind a failed instance | Yes: version settings | MIT extension; Azure |
| Cloudflare Workflows | SDK constructs: step.do, step.sleep; TS, JS, Python (beta) | Step results persisted by step name | Explicit: Promise.all; Python DAG | Built in: rollback on a step | not documented | not documented | Proprietary; Cloudflare |
| Vercel Workflow | Directives "use workflow", "use step"; TypeScript | Event log, deterministic replay | Explicit: Promise.all | Documented pattern (saga) | not documented | Yes: pinned to its deployment | Apache-2.0; Vercel, Postgres or local |
| Prefect | Decorators @flow, @task; Python | Result persistence and caching (off by default) | Explicit: .submit(), task runners | Built in: transactions with rollback hooks | not documented | Deployment versions with rollback | Apache-2.0; Prefect Cloud |
| Apache Airflow | Python DAG definitions | Task state; retry or clear and rerun | Declarative: DAG dependencies | not documented | Rerun with the original code bundle | Yes (Airflow 3): versioned bundles | Apache-2.0; managed by third parties |
| Dagster | Decorators: assets, ops, jobs; Python | Re-execute from failure | Through the executor, over the graph | not documented | Re-execute from failure | not documented | Apache-2.0; Dagster+ |
| n8n | Visual node editor; code nodes in JS or Python | Execution data saved; retry resumes | not documented | not documented | Retry with the original or current workflow | Workflow history; the published version runs | Sustainable Use License; cloud or self-host |
Also in the field, not compared cell by cell for lack of documentation we could confirm: Flyte (Union.ai), Upstash Workflow.
Process automation and integration platforms
BPM suites, low-code automation and integration platforms. Almost all are defined in BPMN, a low-code designer or JSON/YAML; Camel and the Flowable engine are code-first. None derives the process from ordinary program code.
| Product | How you define it | Durability | Parallel work | Compensation | Versions of running work | License and hosting |
|---|---|---|---|---|---|---|
| Jaal | Plain Java; the graph derived by a compiler; steps can be made a sequence on the graph | Every step a transaction in Postgres; a crash finished by another process | Automatic: proven by the compiler | Declared per service and caller (the effect contract): built | Hot swap inside the type contract: built (Eclipse tooling); a new contract via a router: planned | Private beta |
| Camunda 8 | BPMN models; job workers in Java, TypeScript, Python, C# | Replicated log (Raft), state in RocksDB with snapshots | BPMN parallel gateway; multi-instance | Built in: BPMN compensation events | Explicit instance migration, mapped per active element | SaaS or self-managed; Camunda License v1, Enterprise for production |
| Pega Platform | Low-code case types and process flows | Cases saved to database tables; queue processors retry | Split Join, Split For Each shapes | not documented | No automatic migration; manual strategies | Proprietary; Pega Cloud, client cloud, on-premises |
| Appian | Low-code Process Modeler (BPMN-style) | In-memory engines persisting a transaction log to disk | AND gateway | not documented | Running processes stay on their version; Process Upgrade via the Java API | Proprietary; Appian Cloud or self-managed on Kubernetes |
| IBM Business Automation Workflow | BPMN in Process Designer; BPEL in the Advanced configuration | Instances kept in its databases | not documented | Documented for BPEL processes only | Instances stay on their snapshot or migrate | Proprietary; traditional install or containers |
| MuleSoft Anypoint | Mule flows in XML, built visually | not documented | Scatter-Gather (parallel routes) | Pattern: Try scope and error handlers | not documented | Mule Kernel CPAL; Enterprise proprietary; CloudHub, Runtime Fabric |
| Azure Logic Apps | Visual designer or JSON workflow definitions | Stateful: every action's state in external storage; runs up to 90 days by default | Parallel branches | Pattern: retry policies, run-after, scopes | not documented | Proprietary; Consumption, Standard, hybrid |
| Power Automate | Low-code cloud flow designer | Runs last up to 30 days | Parallel branches | Pattern: configure run after | Drafts and version history | Proprietary SaaS (Power Platform) |
| Google Cloud Workflows | YAML or JSON steps | Executions keep state and wait up to a year | Parallel step with branches; parallel for | not documented | Running executions unaffected by an update | Proprietary, managed on Google Cloud |
| UiPath Maestro | BPMN 2.0 and DMN; Flow; Case Management | Pause, resume and retry of long-running instances | Parallel and inclusive gateways; multi-instance | Not executed (per its BPMN support matrix) | Instance migration to another package version | Proprietary; Automation Cloud |
| ServiceNow Workflow Studio | Low-code flows, subflows and actions | Waiting flows serialized into context records | Parallel block; one flow runs on a single thread | Pattern: flow error handler | not documented | Proprietary SaaS |
| Boomi | Low-code visual process canvas | Retry schedules re-run failed documents | Flow Control step across threads or processes | Pattern: retries and error paths | not documented | Proprietary; Boomi cloud or self-hosted runtime |
| Flowable | BPMN, CMMN, DMN engines in Java; low-code designer (commercial) | Wait states committed to the database as jobs; retries | BPMN parallel gateway; multi-instance | Built in: BPMN compensation events | Process migration service (one or all instances) | Engine Apache 2.0; commercial on-prem or cloud |
| TIBCO BPM Enterprise | BPMN in Business Studio | Process instances kept in a database | not documented | Compensation events (documented in 4.3.2) | Migration at design-time migration points | Proprietary; Kubernetes runtime |
| Apache Camel | Code: Java, XML or YAML DSLs | Route state not persisted; Saga state via an external LRA coordinator | Multicast and Splitter parallel processing | Built in: Saga EIP with compensation endpoints | not documented | Apache 2.0, runs on any JVM |
Not covered here: SAP Build Process Automation, Oracle Integration. AWS Step Functions is in the table above.
Sources
Each product's own documentation, blog or repository, read on 26 October 2026.
- Temporal: workflows, parallel execution, saga, versioning, VS Code replay debugger, license
- Restate: durable execution, concurrency, sagas, versioning, restart as new, license
- Inngest: execution, parallelism, versioning, replay, license
- Golem: durable execution, parallel workers, transactions, updates, license
- Resonate: docs, repository
- DBOS: architecture, workflow management, license
- Hatchet: docs, replay
- Trigger.dev: how it works, versioning, replaying
- Conductor: repository, failure workflows, versions, Netflix repository (archived)
- Cadence: versioning, replay shadowing, CNCF
- Dapr: overview, patterns, versioning
- AWS Step Functions: overview, versions, saga
- AWS Lambda durable functions: concepts, versions
- Azure Durable Functions: overview, rewind, versioning
- Cloudflare Workflows: Workers API, Python
- Vercel Workflow: workflows and steps, versioning, saga
- Prefect: flows, transactions, versioning
- Airflow: DAGs, bundles
- Dagster: concepts, run retries
- n8n: executions, license
- Camunda 8: compensation, instance migration, clustering, licenses
- Pega: split shapes, cases in flight, deployment
- Appian: Process Upgrade, gateways, engine persistence
- IBM BAW: instance migration, compensation (BPEL)
- MuleSoft: Scatter-Gather, error handling, licensing
- Azure Logic Apps: stateful and stateless, parallel branches, limits, error handling
- Power Automate: limits, drafts and versions
- Google Cloud Workflows: overview, parallel steps, quotas
- UiPath Maestro: BPMN support, instance migration
- ServiceNow: architecture, parallel block
- Boomi: Flow Control, retry schedules
- Flowable: engine, compensation, migration
- TIBCO BPM Enterprise: instance migration, compensation (4.3.2)
- Apache Camel: Saga EIP, Multicast, DSLs