One Python loop, four inference engines
An assistant writes a Python orchestrator live. For every photo, in parallel: our CUDA kernels sharpen it, Triton classifies it, llama.cpp describes it; then vLLM writes the album summary.
The compiler and the transpiler turn plain Java or Python into a durable, parallel execution graph: run it on the Java runtime, or transpile it to C++ or Rust (in progress). Change the graph and your code changes with it. Every step recorded, every run rewindable. No workflow SDK to learn, no determinism rules to follow, no rewrite between the demo and the real thing.
At one end, tools that make building easy. At the other, platforms that keep what you built running at scale. Both ends have very good products. In between lives the part nobody puts in a demo.
Drag a node, write a prompt, add a decorator. Good company here: n8n, LangGraph, the AI app builders.
Then someone says "production".
The workflow SDK, the determinism rules, the retries, the sagas by hand, the glue between the two.
Most teams stay here longer than they planned.
Good company here too: Temporal, Ray, Step Functions.
Bring the rewrite with you.
A coding agent will write your business logic before lunch. The infrastructure around it is a different job: it is not a prompt, it is a platform.
Price the order, check the policy, call the model, write the summary. Ask, review, ship.
Durability, retries, parallel work, compensation when a step fails, versions that hold under running work, deployment, replay. Infrastructure a model writes is infrastructure you now review, test and keep running at 2 a.m.
Let the agent write the logic. The compiler derives the rest: the same way every time, checked before it runs.
You write the intent as plain code, or ask the assistant to write it. Jaal derives the graph from it: what depends on what, what runs together, what is recorded. You never draw the graph and never keep it in sync; when you want it different, you change the code, or you change the graph and the code follows.
Flight f = flight.hold(trip);
Hotel h = hotel.hold(trip);
Car c = car.hold(trip);
Paid p = pay.charge(f, h, c);
Plain code, written top to bottom. No futures, no fan-out construct.
The three holds do not depend on each other, so the compiler runs them together; the payment waits for all three.
Flight f;
Hotel h;
{{
f = flight.hold(trip);
h = hotel.hold(trip);
}}
Car c = car.hold(trip);
Pick flight and hotel on the graph, choose Run these in order, and the code gains a {{ }} block: a sequence inside the flow, everything else still as parallel as it can be. Nothing is reordered, the compiler checks the change before it is written, and one click undoes it.
Change a service while it runs. Jaal compares the new version with the old, tree against tree, grades the change from the lowest risk to the highest (same, bytes, surface, contract), and offers the swap that fits. The grade is derived, never typed in.
The contract itself changes: what goes in or comes out, even one optional field. That is a new endpoint version. Callers move to it only when it is promoted, and a router endpoint in front can shift traffic by weight or adapt between the old model and the new. Graded contract.
The contract stays as it is; only the code behind it changes. The endpoint takes the new code in place: the next step of every conversation, new or already running, goes through it, with no restart. A change to the contract, the dependencies or the container is refused by name, so this swap cannot break a caller. Graded bytes or surface.
One topic, asked precisely. Where others have it too, they get the tick.
| 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 24 | 16½ | 13½ | 11 | 9½ | 13 | 8 | 10 | 9 | 7 | 10 | 9 | 9 | 10½ | 11½ | 10½ | 8 | 8 | 9 | 9 | 9 | 9 |
| 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 |
| Independent calls run in parallel without futures, Promise.all or parallel nodes | ✓ | ✗21 | ✗22 | ✗23 | ✗24 | ✗25 | ✗26 | ✗27 | ✗28 | ✗29 | ✗30 | ✗31 | ✗32 | ✗33 | ✗34 | ✗35 | ✗36 | ✗37 | ✗38 | ✗39 | |
| A version's identity is derived from its content, and running work stays on it | ✓ | ✗40 | ✓41 | ✗42 | ✓43 | ✗44 | ✓ | ✓45 | ✗46 | ✗47 | ✓48 | ✓49 | ✗50 | ✓51 | ✓52 | ✗53 | |||||
| After a crash, it resumes from the last state: no replay of the code from the start | ✓ | ✗54 | ✗55 | ✗56 | ✓57 | ✗58 | ✗59 | ✗60 | ✗61 | ✓ | ✗62 | ✗63 | ✓64 | ✗65 | ✗66 | ✗67 | ✗68 | ✓69 | |||
| Compensation built in, not a saga you hand-write | ✓ | ✗70 | ✗71 | ✓ | ✓ | ✗72 | ✗73 | ✗74 | ✓ | ✗75 | ✓ | ||||||||||
| The execution graph is drawn from the code, and editing the graph rewrites the code | ✓ | ||||||||||||||||||||
| A deploy that would break the contract is refused by the platform, by name | ✓ | ✓76 | ✗77 | ✗78 | |||||||||||||||||
| Compensation declared by the called service; the caller writes no undo code | ✓ | ✗79 | ✓80 | ✗81 | ✓82 | ||||||||||||||||
| A call to another service reads as a plain method call: no stubs, run(), invoke or step wrappers | ✓ | ✓83 | ✓84 | ✗85 | ✓86 | ✗87 | ✓88 | ✗89 | ✗90 | ✗91 | ✓92 | ✗93 | ✗94 | ✗95 | ✓96 | ✗97 | ✓98 | ✓99 | ✗100 | ✓101 | ✗102 |
| A streamed response cut by a crash continues where it left off: no tokens regenerated | WIP103 | ✓104 | ✓105 | ✓106 | ✗107 | ✓108 | ✓109 | ✓110 | |||||||||||||
| Concurrency safety proven by a compiler before the run, not by lint rules or run-time checks | ✓ | ✓111 | ✗112 | ✗113 | ✗114 | ✗115 | ✗116 | ✓117 | ✓118 | ✗119 | ✗120 | ||||||||||
| Non-deterministic calls (clock, random, any library) recorded automatically: no determinism rules | ✓ | ✓121 | ✗122 | ✗123 | ✓ | ✗124 | ✗125 | ✗126 | ✗127 | ✗128 | ✗129 | ✗130 | ✗131 | ✓132 | ✗133 | ||||||
| A conversation already running finishes on the new code, with no version markers in code | ✓ | ✓134 | ✗135 | ✓ | ✓136 | ✗137 | ✗138 | ✗139 | ✓140 | ✓141 | ✗142 | ✓143 | ✓144 | ✗145 | ✓146 | ✓147 | |||||
| Runtime out of the box: engine, process management and scaling ship with it; no server, worker fleet and database to assemble | ✓148 | ✓149 | ✓150 | ✓151 | ✓ | ✓152 | ✓153 | ✓154 | ✓155 | ✓156 | ✗157 | ✗158 | ✓ | ✓ | ✓ | ✓ | ✓159 | ✓160 | ✗161 | ✓162 | ✓163 |
| New code swapped into running work in place, no side-by-side deployment | ✓ | ✓ | ✓164 | ✓ | ✓ | ✗165 | ✗166 | ✗167 | ✓ | ✓ | ✗168 | ✓169 | ✓ | ✗170 | ✓ | ✓ | |||||
| Running work stays on the version it started on when new code is deployed | ✓ | ✓ | ✓ | ✗171 | ✓ | ✓ | ✓ | ✓ | ✗172 | ✗173 | ✓ | ✓174 | ✓175 | ✓ | ✓176 | ✗177 | |||||
| Execution history is an append-only log | ✓ | ✓ | ✓ | ✓ | ✗178 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓179 | |||||||||
| Languages you write the workflow in | |||||||||||||||||||||
| Write the workflow itself in Java | ✓ | ✓ | ✓ | ✓180 | ✗181 | ✓182 | ✓ | ✗183 | ✗184 | ✓185 | ✓ | ✓ | ✓186 | ✓ | ✓ | ✗187 | ✗188 | ✗189 | ✓190 | ✓191 | ✓192 |
| Write the workflow itself in Python | ✗193 | ✓ | ✓ | ✓ | ✗194 | ✓ | ✓ | ✓ | ✓195 | ✓196 | ✓ | ✓ | ✓197 | ✓ | ✓ | ✓198 | ✓199 | ✓ | ✓ | ✓ | ✓ |
| Write the workflow itself in TypeScript | ✗200 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓201 | ✗202 | ✓ | ✓203 | ✓ | ✓ | ✓ | ✓ | ✗204 | ✗205 | ✓ | ✗206 |
| Write the workflow itself in Go | ✗207 | ✓ | ✓ | ✓ | ✓208 | ✓209 | ✓ | ✓ | ✗210 | ✓211 | ✓ | ✓ | ✓212 | ✓ | ✗213 | ✗214 | ✗215 | ✗216 | ✓217 | ✓218 | ✗219 |
| Write the workflow itself in Rust | ✗220 | ✓ | ✓ | ✗221 | ✓ | ✓222 | ✗223 | ✗224 | ✗225 | ✓226 | ✗227 | ✗228 | ✓229 | ✗230 | ✗231 | ✗232 | ✗233 | ✗234 | ✗235 | ✗236 | ✗237 |
| Write the workflow itself in C/C++ | ✗238 | ✗239 | ✗240 | ✗241 | ✗242 | ✗243 | ✗244 | ✗245 | ✗246 | ✗247 | ✗248 | ✗249 | ✓250 | ✗251 | ✗252 | ✗253 | ✗254 | ✗255 | ✗256 | ✗257 | ✓258 |
| Write the workflow itself in C#/.NET | ✗259 | ✓ | ✗260 | ✗261 | ✗262 | ✗263 | ✗264 | ✗265 | ✗266 | ✓267 | ✗268 | ✓ | ✓269 | ✓ | ✓ | ✗270 | ✗271 | ✗272 | ✗273 | ✗274 | ✗275 |
✓ 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 first one starts from an empty project; the others open on finished code and go straight to the run, the crash and the replay.
An assistant writes a Python orchestrator live. For every photo, in parallel: our CUDA kernels sharpen it, Triton classifies it, llama.cpp describes it; then vLLM writes the album summary.
Flight, hotel and car held in parallel, then payment. Kill the server mid-run: it finishes anyway. Rewind the recorded run, fix a bug, replay it against the fix, with nothing booked twice.
An agent looks up the order and the policy, drafts a refund, and waits for a person to approve or correct it on a review page. Stop the machine while it waits; nothing is lost.
Thousands of FIX execution reports, each with a costly cost-of-trading analysis, run as one parallel loop. Kill it halfway: only the unfinished messages run again.
A browser IDE for orchestrations. It checks your code as you type, draws the graph the compiler derived, deploys with one gesture, and shows every run step by step.
The Java language server for completion and errors, and Jaal's own compiler underneath: an unsafe line is refused by name, with the fix.
Sign in with your email. Everything you make lives under your own package, like com.gmail.you.
Turn a project into an endpoint, deploy it to a node, send it work, watch the run.
Open the IDE by invitation for now
What is built, what is in progress. We say which.
Plain code in. The compiler derives the workflow graph, decides what is recorded, and refuses what is unsafe, by name.
Calls that do not depend on each other run together because the compiler proved it. One measured run went from about 610 ms to 311 ms with no code change.
Every step is a transaction in Postgres. A killed process is finished by another from the last recorded step, without re-running the code from the start, and a settled call never runs again.
You do not have to write deterministic code. The compiler works out what is deterministic; a call it cannot see into is recorded, so a random number or a clock read is replayed exactly as it was.
Step through a recorded run, back and forth. A replayed step is answered from the record: a model or a service is never called twice.
Run a changed version against the cases already recorded, with no live system behind them, and see what would have changed.
Compensation is declared, not coded: the service states how it is undone and retried, the caller states what a failure means.
A version is its content. Running work stays on its version; new work takes the new one.
Write the orchestrator in Python; it is checked as you type and compiled to the same graph.
The same orchestrator printed as Java, C++ or Rust. The C++ print already runs bit for bit with the Java one.
vLLM, Triton, llama.cpp and plain CUDA as services of one orchestrator, each in its own process.
How this compares with Temporal, Restate, Inngest, Ray, LangGraph and the rest →