Compare

Dwarf vs. Temporal vs. LangGraph

Temporal made durable execution mainstream, and LangGraph made agent graphs easy to write. Both are excellent projects. Dwarf takes a different position: durable agentic workflows as a library in your own application, with your database as the only infrastructure. Here is what that trade looks like, with sources for every claim we make about the others.

DwarfTemporalLangGraph
What it isA Go library that you embed in your own binary.A server cluster that you host, or Temporal Cloud, plus an SDK that runs in your workers.A Python or JavaScript library, plus LangSmith Deployment for managed hosting.
Infrastructure you runYour application and a SQL database.The Temporal Service (frontend, history, matching and worker services) and its persistence store, or a Temporal Cloud subscription.Your application and a checkpointer database, or a LangSmith Deployment.
Scale-out architectureFully distributed. Replicas and database shards coordinate only through the databases. Each step is dispatched on its own, so one flow's parallel branches run at once, split between the two replicas that dispatch its shard. Shards are added with rolling restarts and no maintenance window.Horizontally scaled services over a sharded history. The history shard count is fixed when the cluster is created, and changing it means migrating to a new cluster.The library runs each graph in the calling process. To spread runs across machines, the Agent Server adds queue workers over Postgres and Redis, and needs a license key when self-hosted. Each run executes on a single worker, so a run's parallel branches share that worker.
What caps throughputYour databases. A 32-vCPU Postgres runs about 19,000 to 24,000 steps/s, and four such shards ran 3.8× one. Past 32 vCPUs, the database’s write-ahead log is the limit, so you add shards. The methodology is published.On Cloud, a per-namespace limit of 500 actions/s by default, raised with usage or provisioned capacity. Self-hosted, your cluster and its persistence store.A single Postgres database holds all durable state, with no sharding documented. Concurrency is the worker count times the runs each worker handles (10 by default).
Concurrent long-running tasksEach task in flight is handled by a worker goroutine, and the engine grows workers on demand, into the tens of thousands per process. A waiting task holds no database connection: one is used only to claim the step and record its outcome. Memory is the practical limit when flow state is large. A measured run held 10,000 one-minute tasks in flight in one process, on under one core.Activities run in slots on worker processes, 1,000 per worker by default (Go SDK). You add worker processes for more. A single workflow execution can have at most 2,000 incomplete activities on Cloud.Runs execute on queue workers, 10 concurrent runs per worker by default. A run holds its slot until it finishes or interrupts, including while it waits on LLM and tool calls. You add workers for more.
How orchestration logic is writtenA declarative graph of transitions, conditions, fan-out and loops, plus tasks that are plain functions, in Go or any other language. Nothing is replayed, so neither layer has determinism constraints.Imperative workflow code that must be deterministic: it can't read the wall clock, use randomness, iterate maps in their native order, start native goroutines or do I/O. Activities are unrestricted.A graph of nodes and edges. Node code is unrestricted.
Changing code with flows in flightEach flow keeps the graph it started with. Task code can change mid-flow as long as it still accepts the state already in flight. Retired task names must stay routable until their flows drain.Any change that alters the command sequence of running workflow code needs a version guard (patching) or worker versioning.Saved checkpoints resume against the current graph code, so keeping them compatible is up to you.
Crash recoveryAutomatic. When a lease expires, the step is re-dispatched on another replica.Automatic, by replaying the event history.With the library alone, a run resumes from its last checkpoint when the thread is invoked again. On the Agent Server, a sweeper re-queues runs from crashed workers every 2 minutes.
State sizeUp to 256 MB per value, about what a Postgres jsonb column holds. 250 MB per step was measured.Cloud allows 2 MB per payload, 4 MB per transaction, and 50 MB or 51,200 events of history per execution. Self-hosted uses the same defaults, but they are configurable.State is bounded by your checkpointer's database.
Human-in-the-loopInterrupt and ResumeSignals and Updatesinterrupt() and a resume command
Parallel fan-out and mergeStatic and forEach fan-out, with per-field reducers merging results at fan-in.Child workflows or asynchronous activities. Merging the results is up to your code.The Send API, with state reducers merging results.
Priority and fairnessPriority bands, plus weighted fairness keys within a band.Task queue priority and fairness keys.Not built in.
Metrics and tracingOpenTelemetry logs, over 30 metrics and traces, sent to the stack you already run at no cost.Prometheus metrics, OpenTelemetry tracing and a Web UI.Tracing through LangSmith ($39 per seat per month on Plus, with 10k traces included and $5 per 1k after that), or through third-party tools.
Languages for your codeThe host is Go. Graphs and flows are JSON on the wire, so tasks can be written in any language, though there are no task SDKs outside Go yet.Native SDKs for Go, Java, TypeScript, Python, .NET, PHP, Ruby and Rust. The server itself is written in Go.Python and JavaScript / TypeScript.
LicenseApache 2.0MITMIT
PricingFree. You pay only your own cloud bill, which comes to about $0.05 per million steps at scale.Self-hosted, the software is free, but you run and pay for four services, usually replicated, plus their database. Cloud starts at $50 per million actions and drops to $25 with volume.The library is free. LangSmith Deployment is metered by the resources each deployment consumes, such as $0.0675 per vCPU-hour of runtime compute, and Plus includes one small serverless deployment.
Good to know

Common questions

Do my tasks have to be written in Go?
No. Only the host that embeds the engine is Go, and it implements two callbacks: one loads a graph, the other runs a task. Both can reach any language, over HTTP or any other transport. Graphs and the flow handed to a task are plain JSON, including state and control signals such as retry, sleep and interrupt. There are no ready-made task SDKs for other languages yet, so a non-Go task reads and writes that JSON directly. Microbus runs its workflows on Dwarf this way, with every task behind a URL.
Who runs the database?
Whoever runs your databases today. Amazon RDS, Google Cloud SQL and Azure Database for PostgreSQL all work, and retention is built in: closed flows move to an archive that keeps each day for 30 days by default (SetRetention), and Purge or DeleteOnCompletion removes flows sooner. There is no separate orchestration service to host, monitor or buy support for: Dwarf runs inside your application, so its availability is your application's and your database's, which you already run to your own standard.
Can I bring my LangGraph graphs?
The programming models are close: nodes and edges, conditional routing, state reducers, interrupts, and Send alongside Dwarf's forEach. Porting a graph is mostly a mechanical translation, the kind of job a coding agent does well. Node code can keep running in Python behind an HTTP call, LangChain integrations included, while Dwarf takes over durability.
Isn't a graph less expressive than workflow code?
It is more structured, on purpose. Graphs cover loops, conditional and first-match routing, static and dynamic fan-out, subgraphs and human-in-the-loop interrupts. Anything a graph can't express goes in a task, which is plain code, and a task can choose the next step itself with Goto. In exchange, the graph is data: you can inspect it, draw it and freeze it per flow, and nothing about it is replayed.
Is execution exactly-once?
In normal operation, each task runs once and its step records exactly one outcome. A task runs a second time only after a failure: a worker crashes mid-task and its lease expires, the database connection is lost for longer than the retry budget, or the task overruns its time budget. That makes execution at-least-once, as with every durable execution engine, and that rare second run is what StepKey() is for: a stable key per step that makes side effects such as charges and emails idempotent.
What happens when Postgres goes down?
Work pauses and resumes on its own. In-flight writes wait out a brief freeze; after a severed connection, outcomes are retried when the database returns, and steps whose leases lapsed are re-run. The engine is built not to fail a flow because the database was away, and replicas recover without a restart.
Sources

Checked November 2026. Spotted something out of date? Open an issue and we will fix it.