Open source Go library

Durable Execution for
Agentic Workflows.

Go. Postgres. Period.

Dwarf is a durable workflow engine you embed in your own application: an alternative to Temporal and LangGraph that runs as a library, not a platform. Every step of every agent loop and business process is checkpointed to the database you already run. No cluster, no broker, no per-step fee.

A durable agent loop, running on your own Postgres
// Define the agent loop: think, use tools, repeat until done.
// Tasks are addressed by URLs that your host maps to a function, an RPC or a queue.
g := workflow.NewGraph("Agent")
g.SetURL("Think", "https://agent/think")
g.SetURL("UseTools", "https://agent/use-tools")
g.AddTransitionSwitch("Think", "UseTools", "hasToolCalls")
g.AddTransitionSwitch("Think", workflow.End, "!hasToolCalls")
g.AddTransition("UseTools", "Think")

// Embed the engine in your app and point it at Postgres.
eng := dwarf.NewEngine()
eng.SetShard(engine.ShardSpec{Index: 1, DSN: "postgres://db.internal/dwarf"})
eng.SetHost(yourHost) // The host implements 2 simple methods
eng.Startup(ctx)

// Start a flow. Every step is checkpointed, so it survives crashes and deploys.
flowKey, err := eng.Create(ctx, "https://agent/graph", map[string]any{"goal": "Plan a trip to Lisbon"}, nil)
$ go get github.com/microbus-io/dwarf
The durable execution tax

Durable execution. No strings attached.

Agents and long-running workflows have to survive crashes, deploys and humans who take a week to click Approve. Today, durability means adopting a platform: a cluster to run, rules your code must obey, quotas on your data, and a meter that runs on every step.

Durable execution platforms ask a lot

  • Determinism constraintsReplay-based engines re-run your orchestration code to rebuild state, so it can't read the clock, roll dice, range over a map, spawn goroutines or do I/O.
  • Versioning ceremonyChange orchestration code in a way that alters its sequence of commands while executions are in flight, and each change needs a version guard, or replays break.
  • Quotas on your dataPayload caps measured in megabytes and a ceiling on history push large state into side stores and long flows into continue-as-new.
  • A platform to runSelf-hosting means running a multi-service cluster and its persistence tier. The alternative is to rent it and pay per action.
  • Checkpoints are not durabilityAgent frameworks save checkpoints, but in the library alone, noticing a crash and re-dispatching the work is left to you.

Dwarf asks for a database

  • Tasks are plain codeDwarf checkpoints state after every step instead of replaying your code. Read the clock, run work in parallel, hit the network. It all just works, in Go or any language your host can reach.
  • Flows keep their graphEach flow freezes the graph it started with, so in-flight flows finish on the shape they began. Task code can change underneath them, as long as it still reads the state they carry.
  • State as big as your database allowsEach value can be up to 256 MB, about what a Postgres column holds, and 250 MB per step was measured. State lives in your database, under your retention rules.
  • A library, not a platformgo get it, embed it, point it at Postgres. Replicas coordinate through the database, with nothing in between.
  • Durable by constructionA crashed worker's step is re-dispatched by lease expiry on another replica. Each step records exactly one outcome; tasks run at least once, and StepKey() is your idempotency key.
No determinism constraints

Your tasks are just Go.

A task is an ordinary function that reads the flow's state, does its work and writes its results. Dwarf persists the outcome before the next step runs, so there is nothing to replay and nothing your code has to promise.

  • Use the wall clock, randomness, maps, goroutines and network calls freely
  • Durable retries with backoff, timed sleeps and jumps are one call each
  • A crash mid-task re-runs that one step, and StepKey() gives you its idempotency key
  • There is no SDK sandbox or special test runner: a task is a function you can unit test
// Research gathers notes on a topic from several sources in parallel.
func Research(ctx context.Context, f *workflow.Flow) error {
    started := time.Now() // wall clock: fine
    topic := f.GetString("topic")
    sources := map[string]string{ // ranging over a map: fine
        "wiki": "https://en.wikipedia.org/wiki/" + topic,
        "news": "https://news.example.com/search?q=" + topic,
    }
    var mu sync.Mutex
    var wg sync.WaitGroup
    notes := map[string]string{}
    for name, url := range sources {
        wg.Go(func() { // goroutines: fine
            body, err := fetch(ctx, url) // network I/O: fine
            if err == nil {
                mu.Lock()
                notes[name] = body
                mu.Unlock()
            }
        })
    }
    wg.Wait()
    if len(notes) == 0 {
        if f.Retry(time.Second, 2.0, time.Minute, time.Hour) {
            return nil // durable retry with backoff
        }
        return errors.New("no sources reachable")
    }
    f.Set("notes", notes)
    f.SetInt("sample", rand.IntN(len(notes))) // randomness: fine
    f.SetDuration("elapsed", time.Since(started))
    return nil
}
Built for agents

Agentic workflows that outlive the process.

An agent that plans, calls tools in parallel, waits days for a human and then acts is a workflow. Dwarf gives you the pieces as graph primitives, and every one of them is durable.

  • Loops and routing: cycle through think and act until the goal is met
  • Parallel tool calls: forEach fans out one branch per item, and reducers merge the results
  • Human-in-the-loop: Interrupt parks the flow for as long as it takes; Resume picks it up
  • Subgraphs: call one workflow from another, like a function
  • Any LLM: bring the model SDK you already use; a task is just a function
  • Time travel: Fork a closed flow from any of its steps, or Continue a completed flow for the next turn of a conversation
Workflow graph: Plan fans out one Search branch per query; the branches merge at Draft; Approve waits for a human, then either continues to Book or loops back to Plan.
// Plan, search in parallel, merge, get a human's sign-off, book.
g := workflow.NewGraph("TripPlanner")
g.SetURL("Plan", "https://trip/plan")
g.SetURL("Search", "https://trip/search")
g.SetURL("Draft", "https://trip/draft")
g.SetURL("Approve", "https://trip/approve")
g.SetURL("Book", "https://trip/book")
g.AddTransitionForEach("Plan", "Search", "queries", "query") // one branch per query
g.AddTransition("Search", "Draft")
g.SetFanIn("Draft")                              // waits for every branch
g.SetReducer("findings", workflow.ReducerAppend) // and merges what they found
g.AddTransition("Draft", "Approve")
g.AddTransitionSwitch("Approve", "Book", "approved")
g.AddTransitionSwitch("Approve", "Plan", "!approved") // or plan again
g.AddTransition("Book", workflow.End)

// Approve parks the flow until a human decides, whether that takes minutes or weeks.
func Approve(ctx context.Context, f *workflow.Flow) error {
    var decision struct{ Approved bool }
    yield, err := f.Interrupt(map[string]any{"question": "Book this itinerary?"}, &decision)
    if err != nil || yield {
        return err // parked; re-enters on Resume
    }
    f.SetBool("approved", decision.Approved)
    return nil
}
Performance & cost

A small footprint that packs a punch.

Dwarf was measured against managed cloud Postgres across a real network hop, in 243 runs over 40 sessions, without a single failed flow. Every number below comes with the configuration that produced it.

~19,000
steps per second

That is one 32-vCPU Postgres database at the engine's defaults, the slowest of four instances measured; the fastest ran 24,300. Planned at 95% of it, that is 47 billion steps a month.

$0.05
per million steps

This covers the database and two engine hosts at list price, planned at 95% of capacity, where latency stays low.

3.8×
throughput on four 32-vCPU shards

Add database shards to scale out. Four shards ran 91,800 steps per second, each close to what one shard runs alone.

10,000
concurrent minute-long tasks

One engine held them all in flight on under one core. A waiting task holds a goroutine, not a database connection.

Postgres sizeMeasured steps/sPlanned steps/sSteps / monthInfra / monthPer million steps
4 vCPU1,8501,7504.6 B$435$0.095
8 vCPU4,5934,40011.6 B$700$0.061
16 vCPU8,2197,80020.5 B$1,240$0.060
32 vCPU18,852–24,26217,90047.0 B$2,310$0.049
64 vCPU29,34027,90073.3 B$4,440$0.061

Each row is one shard running a 10-step workflow of no-op tasks on Google Cloud SQL for PostgreSQL 16, with 4 GB RAM per vCPU, a 1 TB SSD, checkpoints sized as recommended and the archive on, served by two Arm engine hosts at half the database's vCPU, at on-demand list prices in one US region. The 64-vCPU row sets MaxConnections: 500. Planned is 95% of the measured rate, which a database sustains with low latency. Your tasks' own run time and compute, and large payloads, are not included. Already running Postgres? The extra cost can be close to zero. Full methodology →

What would your volume cost?

–

The estimate uses the same assumptions as the table, at the planned rate. Above one 32-vCPU database, it scales out on 32-vCPU shards (the configuration scale-out was measured on), at 95% of a shard each, as four shards ran.

Production-grade

Everything a workflow engine owes you.

There is no enterprise tier, no managed-only feature and no per-trace observability bill. What you see in the repository is what runs in production, and it is the engine behind Microbus's workflows.

Durability

Checkpointed every step

State is persisted between steps in SQL. A flow can be inspected, resumed, forked or continued days later.

Scheduling

Fair and prioritized

Strict priority bands across the cluster, and weighted fairness within a band, so one tenant can't starve another.

Scale

Fully distributed

Run many replicas against many shards. They coordinate only through the databases they share: no leader, no bus.

Observability

OpenTelemetry native

Structured logs, over 30 metrics and distributed traces, sent through the providers you inject to the stack you already run.

Databases

Postgres first, not only

PostgreSQL is recommended for production. MySQL, MariaDB and SQL Server are supported, and SQLite runs tests and single-replica apps at a modest flow rate.

Portability

Compiles anywhere Go does

Dwarf is pure Go, with no cgo. Cross-compile it into a server, a CLI or an edge box, on any OS and architecture Go targets.

Lifecycle

From laptop to fleet in the same binary.

E

Embed

Two callbacks wire Dwarf to your code: one loads a graph, one runs a task. Transport is up to you.

T

Test

NewEngineUnderTest runs the real engine on in-memory SQLite. It needs no containers and no mocks.

D

Deploy

Point replicas at Postgres. Connection pools and worker counts size themselves from your database's specs and measured round-trip time.

O

Operate

A production checklist, a symptom-indexed runbook and a dashboard's worth of metrics come with it.

Durable execution, minus the platform.

Dwarf is free and open source under Apache 2.0. Add it to your Go module, point it at Postgres, and have a durable workflow running this afternoon.