<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dwarf Documentation on Dwarf</title><link>https://docs.dwarf.run/</link><description>Recent content in Dwarf Documentation on Dwarf</description><generator>Hugo</generator><language>en-US</language><atom:link href="https://docs.dwarf.run/index.xml" rel="self" type="application/rss+xml"/><item><title>Building Graphs</title><link>https://docs.dwarf.run/building-workflows/graphs/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/graphs/</guid><description>&lt;h1 id="building-graphs"&gt;Building Graphs&lt;a class="anchor" href="#building-graphs" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; defining workflows. This is the graph-authoring API: the tasks, transitions, conditions
and &lt;a href="https://docs.dwarf.run/concepts/#reducer"&gt;reducers&lt;/a&gt; that decide what runs when. Writing the task bodies themselves is &lt;a href="https://docs.dwarf.run/building-workflows/tasks/"&gt;Writing Tasks&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;A &lt;code&gt;workflow.Graph&lt;/code&gt; is the definition of a workflow. You build one in code and hand it to the engine via
your host&amp;rsquo;s &lt;code&gt;LoadGraph&lt;/code&gt;. This guide covers the full graph-authoring API.&lt;/p&gt;
&lt;h2 id="tasks"&gt;Tasks&lt;a class="anchor" href="#tasks" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A graph is a set of tasks connected by transitions. Bind a task with &lt;code&gt;SetURL&lt;/code&gt;, giving a &lt;strong&gt;task name&lt;/strong&gt;
(its identity in the graph) and a &lt;strong&gt;URL&lt;/strong&gt; (the address your host&amp;rsquo;s &lt;code&gt;ExecuteTask&lt;/code&gt; resolves to reach it):&lt;/p&gt;</description></item><item><title>Cloud Benchmarks</title><link>https://docs.dwarf.run/performance/benchmark-cloud/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/performance/benchmark-cloud/</guid><description>&lt;h1 id="cloud-benchmarks"&gt;Cloud Benchmarks&lt;a class="anchor" href="#cloud-benchmarks" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; sizing a deployment. This page answers how much one database serves, what adding
shards and engine replicas does, and what the engine itself costs. Every number was measured, and the
configuration that produced it is stated beside it.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="the-short-version"&gt;The Short Version&lt;a class="anchor" href="#the-short-version" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What it delivers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One 32 vCPU PostgreSQL database runs 18,900–24,300 steps/s with the archive on&lt;/strong&gt;, 1.6–2.1 billion steps a day,
at the engine&amp;rsquo;s shipped defaults and with the database&amp;rsquo;s checkpoints sized as recommended. The range is the
instance: the slower one spent 22% more database CPU per step and took 25% longer per log flush. Check yours
(&lt;a href="#method-notes"&gt;Method Notes&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payload is paid in write-ahead log.&lt;/strong&gt; With 8 KB of JSON per step the same database runs ~10,300 steps/s, with
64 KB ~2,800 (~176 MB/s of payload). Every flush of the log costs about 0.8 ms plus its bytes, so the bytes a step
logs set the rate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Size the database&amp;rsquo;s checkpoints.&lt;/strong&gt; With 8 KB payloads, a 20 GB &lt;code&gt;max_wal_size&lt;/code&gt; ran 42% more steps/s than the
1.5 GB default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;32 vCPU is the most economical database size&lt;/strong&gt;, $0.036–0.047 per million steps at full load. Smaller
databases run out of CPU; larger ones are held back by the write-ahead log&amp;rsquo;s flush, so past 32 vCPU you add
shards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The engine is cheap.&lt;/strong&gt; A shard&amp;rsquo;s work takes about 5 engine cores. Two 8 vCPU engine hosts per
32 vCPU database serve it at about a third of their CPU each, and either can carry it alone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shards add throughput linearly.&lt;/strong&gt; Four 32 vCPU shards ran 91,800 steps/s, 3.8 times one shard, each shard
at close to what one shard runs alone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No flow failed in any run.&lt;/strong&gt; In the 243 runs behind this page, across 40 sessions, no flow errored and no
step was left stuck. The engine recovered a crashed worker&amp;rsquo;s step only in the runs that killed engines on
purpose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failover takes about 40 seconds and loses nothing.&lt;/strong&gt; With every dispatcher killed at once, dispatch
paused for ~40 s, standby replicas took over, and every flow completed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replicas add availability, not cost.&lt;/strong&gt; One, two or four replicas on a shard deliver the same
throughput within 6%. Each shard is served by two of them, and any others stand by to take over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long tasks cost no throughput, and mixed durations share one engine.&lt;/strong&gt; With 8-second tasks the engine
delivered the full offered rate, and a mix of 5 ms, 200 ms and 8 s tasks ran at the latency each class
predicts. 10,000 concurrent 60-second tasks ran on under one engine core.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Large state works up to the database column&amp;rsquo;s limit.&lt;/strong&gt; 250 MB per step completed cleanly, at ~45 MB/s for
one flow and ~135 MB/s for four.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A day of varied load ran without falling behind.&lt;/strong&gt; 4.5 hours of mixed shapes, payloads, task durations and
priorities, with bursts above capacity, a rolling restart and a database restart: flows completed at the rate they
arrived, bursts cost only the lowest priorities a wait, and the live tables levelled off at 10 GB
(&lt;a href="https://docs.dwarf.run/performance/benchmark-day/"&gt;A Day of Varied Load&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A growing archive costs nothing measurable.&lt;/strong&gt; A 32 vCPU database ran saturated until its archive held 100 million
step rows, and ran at its empty rate the whole way, while the live tables stayed at ~3 GB. Archived reads took
7–20 ms at every size (&lt;a href="#database-volume"&gt;Database Volume&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What it means for sizing&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Getting Started</title><link>https://docs.dwarf.run/getting-started/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/getting-started/</guid><description>&lt;h1 id="getting-started"&gt;Getting Started&lt;a class="anchor" href="#getting-started" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; meeting Dwarf for the first time. It installs the module, explains the two callbacks you
must implement, and gets a flow running against the &lt;a href="https://docs.dwarf.run/building-workflows/testing/"&gt;in-process test harness&lt;/a&gt; — no database required.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="install"&gt;Install&lt;a class="anchor" href="#install" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go get github.com/microbus-io/dwarf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dwarf requires Go 1.27.3+ and a SQL database for production use (PostgreSQL, MySQL/MariaDB, or SQL Server).
For tests and local experiments it uses SQLite in-memory automatically — no database to set up.&lt;/p&gt;</description></item><item><title>Glossary</title><link>https://docs.dwarf.run/reference/glossary/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/reference/glossary/</guid><description>&lt;h1 id="glossary"&gt;Glossary&lt;a class="anchor" href="#glossary" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For everyone.&lt;/strong&gt; Each term in one sentence, with a link to where it is explained in full.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Archive.&lt;/strong&gt; Where a closed flow moves about a minute after it closes, readable by its key and listed by
&lt;code&gt;ListClosed&lt;/code&gt; until the retention drops it. &lt;a href="https://docs.dwarf.run/building-workflows/flows/#retention"&gt;Driving Flows&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Baggage.&lt;/strong&gt; Context your application attaches to a flow, such as the acting user, that every task can
read but no task can change. &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#baggage-host-context"&gt;Writing Tasks&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Production Checklist</title><link>https://docs.dwarf.run/operating/production-checklist/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/production-checklist/</guid><description>&lt;h1 id="production-checklist"&gt;Production Checklist&lt;a class="anchor" href="#production-checklist" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators&lt;/strong&gt; about to run Dwarf for real. Every item links to the guide that explains
it. Most defaults are safe; what follows is the set that is &lt;strong&gt;not&lt;/strong&gt; defaulted, or where the default is
deliberately conservative and wrong for production.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="database"&gt;Database&lt;a class="anchor" href="#database" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;PostgreSQL&lt;/strong&gt;, unless you have a reason. It needs no isolation-level tuning and runs fan-out
deadlock-free at any concurrency. → &lt;a href="https://docs.dwarf.run/operating/deployment/#choosing-a-database"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;SQLite only for one replica at a modest flow rate&lt;/strong&gt;, on a local-disk file with the recommended DSN
(&lt;code&gt;journal_mode(WAL)&lt;/code&gt;, &lt;code&gt;synchronous(NORMAL)&lt;/code&gt;, &lt;code&gt;auto_vacuum(FULL)&lt;/code&gt;) if flows must survive a restart, a retention that
fits the disk, and live backups with &lt;code&gt;VACUUM INTO&lt;/code&gt;; in memory they last only as long as the process.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#sqlite--one-replica-low-throughput"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;MySQL/MariaDB only:&lt;/strong&gt; &lt;code&gt;transaction-isolation = READ-COMMITTED&lt;/code&gt;, &lt;code&gt;innodb_autoinc_lock_mode = 2&lt;/code&gt; with
&lt;code&gt;binlog_format = ROW&lt;/code&gt;. Without the first, concurrent flow creation deadlocks on gap locks.
→ &lt;a href="https://docs.dwarf.run/operating/runbook/#deadlock-errors-on-mysql-or-sql-server"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;MySQL/MariaDB only:&lt;/strong&gt; &lt;code&gt;interpolateParams=true&lt;/code&gt; in the DSN. Without it every statement with arguments
costs two round trips, roughly doubling each step&amp;rsquo;s. → &lt;a href="https://docs.dwarf.run/operating/deployment/#mysql--mariadb--supported-expect-tuning"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;SQL Server only:&lt;/strong&gt; &lt;code&gt;READ_COMMITTED_SNAPSHOT ON&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every replica connects to a shard as one database user&lt;/strong&gt;, allowed to create and drop tables in the
engine&amp;rsquo;s schema. The engine keeps the archive in a table per day, creating and dropping them as it runs.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#sharding"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Disk IOPS sized to the write rate&lt;/strong&gt;, not just to capacity. Cloud providers often provision IOPS by
disk size, and a throttled disk shows up as throughput collapse with everything apparently idle.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#disk-throughput"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;PostgreSQL 16 and older: &lt;code&gt;autovacuum_work_mem&lt;/code&gt; set to 1 GB.&lt;/strong&gt; At a 64 MB default, vacuum on the step table
rescans every index each time its memory fills, and falls behind. → &lt;a href="https://docs.dwarf.run/operating/deployment/#postgresql--recommended-for-production"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;PostgreSQL: checkpoints sized for the write load.&lt;/strong&gt; &lt;code&gt;max_wal_size&lt;/code&gt; about 2 × peak WAL MB/s × 60 s,
&lt;code&gt;checkpoint_timeout&lt;/code&gt; 15 minutes. At the 1.5 GB default a busy shard checkpoints every few
seconds and ran 30% slower with 8 KB payloads. → &lt;a href="https://docs.dwarf.run/operating/deployment/#postgresql--recommended-for-production"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;ANALYZE&lt;/code&gt;/autovacuum tuned for &lt;code&gt;dwarf_steps&lt;/code&gt;.&lt;/strong&gt; It is a high-churn queue table whose row counts swing
by orders of magnitude, and stale statistics can flip the selection query from an index scan to a
sequential one. → &lt;a href="https://docs.dwarf.run/operating/runbook/#backlog-is-growing"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;No statement, transaction or idle-in-transaction timeout&lt;/strong&gt; on the engine&amp;rsquo;s database, its role or its
connection string. That means PostgreSQL&amp;rsquo;s &lt;code&gt;statement_timeout&lt;/code&gt;, &lt;code&gt;transaction_timeout&lt;/code&gt; and
&lt;code&gt;idle_in_transaction_session_timeout&lt;/code&gt;, and MySQL&amp;rsquo;s &lt;code&gt;max_execution_time&lt;/code&gt;. General database guidance often
recommends them; leave them unset here.
&lt;ul&gt;
&lt;li&gt;Some of the engine&amp;rsquo;s background queries grow with the backlog, so no value is safe for every backlog. A
timeout that cuts them off can stop a database&amp;rsquo;s work from being dispatched at all.&lt;/li&gt;
&lt;li&gt;An idle-in-transaction timeout ends transactions while the engine is busiest, and the work is retried.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Long-running queries stay off the shard databases:&lt;/strong&gt; analytics, reporting, ad hoc sessions. While a query
runs for minutes, PostgreSQL cannot clean up the engine&amp;rsquo;s queue table behind it, and dispatch slows until it
ends. A read replica with &lt;code&gt;hot_standby_feedback&lt;/code&gt; on holds cleanup back the same way, and so does a lagging
replication slot. → &lt;a href="https://docs.dwarf.run/operating/runbook/#backlog-is-growing"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="engine-configuration"&gt;Engine Configuration&lt;a class="anchor" href="#engine-configuration" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Connection budgets suit each database.&lt;/strong&gt; The default (250, rising to 500 for a distant database,
never more than half its &lt;code&gt;max_connections&lt;/code&gt;) suits databases of 4-32 vCPU. Set &lt;code&gt;MaxConnections&lt;/code&gt; for a larger
one or one shared with other heavy users, and set &lt;code&gt;Weight&lt;/code&gt; on shards of unequal size so new flows follow
capacity. → &lt;a href="https://docs.dwarf.run/operating/deployment/#connection-pool"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The shard index-to-DSN map is identical on every replica&lt;/strong&gt; and stable across restarts. A flow key
encodes its shard, so a flow created on a shard a peer does not know about is unroutable there.
→ &lt;a href="https://docs.dwarf.run/operating/resharding/"&gt;Resharding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;SetEngineID&lt;/code&gt; pinned&lt;/strong&gt; if your platform restarts replicas in place. Otherwise a crash-looping replica
leaves a stale registry row per restart, and one that ranked as a shard&amp;rsquo;s &lt;a href="https://docs.dwarf.run/operating/deployment/#dispatchers"&gt;dispatcher&lt;/a&gt; holds that slot until it
ages out, leaving the shard fewer replicas running it. Must be unique among
concurrently-live replicas. → &lt;a href="https://docs.dwarf.run/operating/upgrading/#deploying-your-own-application"&gt;Upgrading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;GOMEMLIMIT&lt;/code&gt; set on engine hosts&lt;/strong&gt;, a little below the memory the process may use. The engine keeps a
tenth of it for large state fields, and without it Go&amp;rsquo;s garbage collector can let the heap reach about twice
its live size. → &lt;a href="https://docs.dwarf.run/operating/deployment/"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Logger, meter provider and tracer provider injected.&lt;/strong&gt; All three default to no-ops, so an engine with
nothing injected is silent — including its error logs. → &lt;a href="https://docs.dwarf.run/operating/observability/"&gt;Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Two engine replicas per shard, each at half the database&amp;rsquo;s vCPU&lt;/strong&gt; (two 8-vCPU replicas per 32-vCPU
database), so either can carry the shard alone. → &lt;a href="https://docs.dwarf.run/operating/deployment/#sizing-a-deployment"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="lifecycle"&gt;Lifecycle&lt;a class="anchor" href="#lifecycle" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;Shutdown&lt;/code&gt; is called&lt;/strong&gt; on termination, and the platform&amp;rsquo;s &lt;strong&gt;termination grace period exceeds your
largest &lt;a href="https://docs.dwarf.run/building-workflows/scheduling/#time-budgets"&gt;&lt;code&gt;TimeBudget&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;. Graceful shutdown waits for in-flight tasks; if the platform kills the process
first, those steps are abandoned and &lt;strong&gt;their tasks re-run elsewhere&lt;/strong&gt;.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#shutting-down"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Deploys replace replicas one at a time&lt;/strong&gt;, letting the fleet settle between steps, so pools re-divide
cleanly rather than transiently over-connecting. → &lt;a href="https://docs.dwarf.run/operating/upgrading/"&gt;Upgrading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Dwarf version bumps are planned as a maintenance window&lt;/strong&gt;, not a rolling deploy — v0.x makes no
compatibility promise. → &lt;a href="https://docs.dwarf.run/operating/upgrading/#dwarf-is-v0x-and-that-is-the-headline"&gt;Upgrading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="observability"&gt;Observability&lt;a class="anchor" href="#observability" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;These five page someone.&lt;/strong&gt; Each is documented as zero in a healthy engine:
&lt;code&gt;dwarf_steps_unwedged_total&lt;/code&gt;, &lt;code&gt;dwarf_flows_orphaned_total&lt;/code&gt;, &lt;code&gt;dwarf_steps_write_failed_total&lt;/code&gt;,
&lt;code&gt;dwarf_flows_reaped_total{reason=&amp;quot;archive_failed&amp;quot;}&lt;/code&gt; and &lt;code&gt;dwarf_flows_expire_failed_total&lt;/code&gt;.
→ &lt;a href="https://docs.dwarf.run/operating/runbook/"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;dwarf_steps_dispatch_lag_seconds&lt;/code&gt; is dashboarded&lt;/strong&gt; — it is the honest backlog signal, better
than a queue count.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Per-shard gauges aggregate with &lt;code&gt;max&lt;/code&gt; per shard, not &lt;code&gt;sum&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;dwarf_steps_dispatch_lag_seconds&lt;/code&gt;,
&lt;code&gt;dwarf_steps_pending&lt;/code&gt; and &lt;code&gt;dwarf_steps_oldest_pending_age_seconds&lt;/code&gt; describe a shard, and more than one
replica can report the same one; summing across replicas overstates the backlog and makes the age readings
meaningless. → &lt;a href="https://docs.dwarf.run/operating/observability/"&gt;Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Someone knows where the runbook is&lt;/strong&gt; before the first alarm, not after.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="data-and-retention"&gt;Data and Retention&lt;a class="anchor" href="#data-and-retention" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The retention is set to what you must keep.&lt;/strong&gt; Closed flows are archived and kept for &lt;code&gt;SetRetention&lt;/code&gt; days
(30 by default), then dropped a day at a time; open flows are never removed by the engine, unless they expire
(below).
→ &lt;a href="https://docs.dwarf.run/operating/data-handling/#retention-and-deletion"&gt;Data Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Abandoned flows are bounded, if they can occur.&lt;/strong&gt; A flow left interrupted, or polling forever, stays in the
live tables until it closes. &lt;code&gt;SetFlowExpiration&lt;/code&gt; terminates any flow older than it.
→ &lt;a href="https://docs.dwarf.run/building-workflows/flows/#expiring-flows-that-never-end"&gt;Expiring Flows That Never End&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The archive&amp;rsquo;s disk is sized for the retention&lt;/strong&gt;: a day&amp;rsquo;s closed flows times the retention, per shard.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Backups cover every shard on the same schedule&lt;/strong&gt;, and a restore has been rehearsed against a staging
fleet — the part worth testing is what your workflows do when they re-run.
→ &lt;a href="https://docs.dwarf.run/operating/backup-and-restore/"&gt;Backup and Restore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;ListOpen&lt;/code&gt; and &lt;code&gt;ListClosed&lt;/code&gt; (and their &lt;code&gt;Query.Search&lt;/code&gt;) are gated by credential&lt;/strong&gt; in your host. The engine has
no notion of a caller, and the lists are the operations that return flow keys wholesale — which are capabilities.
→ &lt;a href="https://docs.dwarf.run/operating/data-handling/#access-control-is-the-hosts-job"&gt;Data Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Quotas enforced before &lt;code&gt;Create&lt;/code&gt;.&lt;/strong&gt; Beyond refusing any single value over 256 MB, the engine bounds
nothing: not initial state size, not baggage, not fan-out width, not subgraph depth.
→ &lt;a href="https://docs.dwarf.run/operating/data-handling/#what-the-engine-does-not-bound"&gt;Data Handling&lt;/a&gt;,
and a worked front door that enforces a quota in &lt;a href="https://docs.dwarf.run/building-workflows/building-a-host/#starting-a-flow-quota-claims-and-tenant"&gt;Building a Host&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;A flow&amp;rsquo;s final state fits in 256 MB.&lt;/strong&gt; State can grow past it while the flow runs, but the final
state holds every field at once. → &lt;a href="https://docs.dwarf.run/operating/data-handling/#the-size-limit-256-mb-per-value"&gt;Data Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;MySQL/MariaDB: &lt;code&gt;max_allowed_packet&lt;/code&gt; is at least 512M.&lt;/strong&gt;
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#mysql--mariadb--supported-expect-tuning"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="workflows-and-tasks"&gt;Workflows and Tasks&lt;a class="anchor" href="#workflows-and-tasks" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Tasks are idempotent&lt;/strong&gt;, keyed on &lt;code&gt;f.StepKey()&lt;/code&gt; where the downstream supports it. Execution is
at-least-once and two attempts can overlap. → &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#idempotency"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Tasks respect their context deadline.&lt;/strong&gt; A task that overruns its &lt;code&gt;TimeBudget&lt;/code&gt; loses its &lt;a href="https://docs.dwarf.run/how-dwarf-works/#crash-recovery"&gt;lease&lt;/a&gt; to a
peer and runs twice concurrently.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;No PII in error text or terminate/cancel reasons.&lt;/strong&gt; Error text is returned by both the lists and
&lt;code&gt;History&lt;/code&gt; — the readers that otherwise expose no state payloads — and the terminate or cancel reason by
the lists. Both are substring-searchable. → &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#keep-payload-data-out-of-error-text"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Large integers read with typed accessors, or carried as strings.&lt;/strong&gt; Beyond ±2^53 storage is exact and
&lt;code&gt;GetInt&lt;/code&gt; and &lt;code&gt;when&lt;/code&gt; expressions are exact, but an untyped read rounds. And &lt;strong&gt;binary data base64-encoded&lt;/strong&gt; —
a &lt;code&gt;NUL&lt;/code&gt; byte is rejected by PostgreSQL and accepted by everything else.
→ &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#large-integers-exact-in-storage-rounded-by-untyped-reads"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every field whose writes should combine has an explicit reducer.&lt;/strong&gt; There is no inference from a field&amp;rsquo;s name —
an unwired &lt;code&gt;sumTotal&lt;/code&gt; silently takes &lt;code&gt;replace&lt;/code&gt;, not &lt;code&gt;add&lt;/code&gt;. → &lt;a href="https://docs.dwarf.run/building-workflows/graphs/"&gt;Building Graphs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Flow deadlines your workflows rely on are in author space.&lt;/strong&gt; An &lt;code&gt;interrupted&lt;/code&gt; flow waits indefinitely unless
&lt;code&gt;SetFlowExpiration&lt;/code&gt; is set, and that terminates without running any of your code. → &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#timestamps"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="first-week"&gt;First Week&lt;a class="anchor" href="#first-week" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Watch &lt;code&gt;dwarf_peer_replicas&lt;/code&gt; match your actual replica count.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Watch &lt;code&gt;dwarf_steps_recovered_total&lt;/code&gt; — a steady trickle usually means tasks are overrunning their
budget, or that your termination grace period is cutting drains short. Neither is broken, but both are
worth knowing. → &lt;a href="https://docs.dwarf.run/operating/runbook/#tasks-are-running-more-than-once"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Confirm the live tables stay level under load, by watching table size rather than disk usage —
PostgreSQL holds its high-water mark. → &lt;a href="https://docs.dwarf.run/operating/runbook/#disk-did-not-shrink-after-a-purge"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Re-read the sizing rules against what you actually observe. The published numbers assume cheap
in-process tasks; a host dispatching over a network transport pays more engine CPU per step.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next: &lt;a href="https://docs.dwarf.run/operating/deployment/"&gt;Deployment&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>v0.14.0</title><link>https://docs.dwarf.run/about/release-notes/v0.14.0/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.14.0/</guid><description>&lt;h1 id="v0140--a-daily-archive-for-closed-flows-lists-by-state-continue-by-key"&gt;v0.14.0 — A Daily Archive for Closed Flows, Lists by State, Continue by Key&lt;a class="anchor" href="#v0140--a-daily-archive-for-closed-flows-lists-by-state-continue-by-key" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Upcoming release: these notes may change before it is published on &lt;a href="https://github.com/microbus-io/dwarf/releases" target="_blank" rel="noopener"&gt;GitHub Releases&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Closed flows now move to an archive about a minute after they close, kept by the day for a retention you set and
dropped a day at a time, so the live tables that every step reads hold the open flows and little else. An archived
flow reads by its key as before. The lists are named by a flow&amp;rsquo;s state - &lt;code&gt;ListOpen&lt;/code&gt; and &lt;code&gt;ListClosed&lt;/code&gt; - and
&lt;code&gt;SetFlowExpiration&lt;/code&gt; can end flows that would otherwise never close. &lt;strong&gt;This release edits migrations that earlier
releases shipped, so existing databases cannot be upgraded in place&lt;/strong&gt;: start v0.14.0 on fresh databases (see
&lt;a href="https://github.com/microbus-io/dwarf/blob/main/docs/upgrading.md" target="_blank" rel="noopener"&gt;Upgrading&lt;/a&gt;). Nothing detects an old database: &lt;code&gt;Startup&lt;/code&gt; succeeds on one, and &lt;code&gt;ListOpen&lt;/code&gt; then fails
on every call.&lt;/p&gt;</description></item><item><title>A Day of Varied Load</title><link>https://docs.dwarf.run/performance/benchmark-day/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/performance/benchmark-day/</guid><description>&lt;h1 id="a-day-of-varied-load"&gt;A Day of Varied Load&lt;a class="anchor" href="#a-day-of-varied-load" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; deciding whether one database carries their traffic through a day, not just at a steady rate.
Four and a half hours of mixed flows on one PostgreSQL database, with bursts above its capacity, a rolling restart
of the engines and a restart of the database itself. The steady-rate measurements are in
&lt;a href="https://docs.dwarf.run/performance/benchmark-cloud/"&gt;Cloud Benchmarks&lt;/a&gt;, whose database and settings this run shares.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;img src="https://docs.dwarf.run/performance/benchmark-day.png" alt="A day on one database: offered load and completed flows by priority" /&gt;&lt;/p&gt;</description></item><item><title>Concepts</title><link>https://docs.dwarf.run/concepts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/concepts/</guid><description>&lt;h1 id="concepts"&gt;Concepts&lt;a class="anchor" href="#concepts" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For everyone&lt;/strong&gt; — developers and operators alike. This is the shared vocabulary the rest of the
documentation assumes. Read it once, before the guides.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Five terms run through all of Dwarf: graph, task, flow, step and reducer. This page defines them.
What happens at runtime (which process runs what, and how crashes are recovered) is covered next, in
&lt;a href="https://docs.dwarf.run/how-dwarf-works/"&gt;How Dwarf Works&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="graph"&gt;Graph&lt;a class="anchor" href="#graph" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;graph&lt;/strong&gt; is the definition of a workflow: which tasks exist, how they connect, and how parallel
branches merge. It&amp;rsquo;s a directed graph of &lt;strong&gt;tasks&lt;/strong&gt; and &lt;strong&gt;transitions&lt;/strong&gt; between them, with an entry
point and optional per-field reducers. You build one in code with &lt;code&gt;workflow.NewGraph&lt;/code&gt; and the &lt;code&gt;Add*&lt;/code&gt;
methods; it carries a display name, and your host&amp;rsquo;s &lt;code&gt;LoadGraph&lt;/code&gt; resolves it by a workflow URL you choose.
Graphs are reusable templates — many flows run the same graph.&lt;/p&gt;</description></item><item><title>Deployment</title><link>https://docs.dwarf.run/operating/deployment/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/deployment/</guid><description>&lt;h1 id="deployment"&gt;Deployment&lt;a class="anchor" href="#deployment" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; standing Dwarf up in production, and for developers doing the wiring. It covers choosing
and tuning a database, declaring shards, connection pools, workers, and running multiple replicas. Once it
is running, &lt;a href="https://docs.dwarf.run/operating/"&gt;Operating Dwarf&lt;/a&gt; takes over.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="configuration"&gt;Configuration&lt;a class="anchor" href="#configuration" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All configuration is set with &lt;code&gt;Set*&lt;/code&gt; methods, each returning an &lt;code&gt;error&lt;/code&gt;. They split by whether the knob can
change on a running engine: the &lt;strong&gt;live&lt;/strong&gt; one (&lt;code&gt;SetTimeBudget&lt;/code&gt;) takes effect immediately, even after &lt;code&gt;Startup&lt;/code&gt;; the
&lt;strong&gt;construction-time-only&lt;/strong&gt; ones
(&lt;code&gt;SetShard&lt;/code&gt;, &lt;code&gt;SetWorkers&lt;/code&gt;, &lt;code&gt;SetStateCacheSize&lt;/code&gt;, &lt;code&gt;SetMaxPriority&lt;/code&gt;, &lt;code&gt;SetRetention&lt;/code&gt;, &lt;code&gt;SetFlowExpiration&lt;/code&gt;, and the dependency-injection setters below) are
rejected if called after &lt;code&gt;Startup&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>FAQ</title><link>https://docs.dwarf.run/reference/faq/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/reference/faq/</guid><description>&lt;h1 id="faq"&gt;FAQ&lt;a class="anchor" href="#faq" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For everyone.&lt;/strong&gt; Short answers, each with a link to the full explanation.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="does-dwarf-need-its-own-server"&gt;Does Dwarf Need Its Own Server?&lt;a class="anchor" href="#does-dwarf-need-its-own-server" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;No. Dwarf is a library that runs inside your application. The only infrastructure is the SQL database it
stores flows in, and replicas of your application coordinate through that database. See
&lt;a href="https://docs.dwarf.run/how-dwarf-works/"&gt;How Dwarf Works&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="do-tasks-have-to-be-written-in-go"&gt;Do Tasks Have to Be Written in Go?&lt;a class="anchor" href="#do-tasks-have-to-be-written-in-go" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;No. The engine and its host are Go, but the host can reach tasks written in any language, for example
over HTTP with JSON in and out. See &lt;a href="https://docs.dwarf.run/building-workflows/other-languages/"&gt;Tasks in Other Languages&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>v0.13.0</title><link>https://docs.dwarf.run/about/release-notes/v0.13.0/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.13.0/</guid><description>&lt;h1 id="v0130--the-creation-hour-in-every-flow-and-step-key"&gt;v0.13.0 — The Creation Hour in Every Flow and Step Key&lt;a class="anchor" href="#v0130--the-creation-hour-in-every-flow-and-step-key" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-10-31. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.13.0" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Flow and step keys carry the flow&amp;rsquo;s creation hour, and a step key names its flow. This is groundwork for retaining
ended flows cheaply. &lt;strong&gt;This release edits migrations that earlier releases shipped, so existing databases cannot be
upgraded in place&lt;/strong&gt;: start v0.13.0 on fresh databases (see &lt;a href="https://github.com/microbus-io/dwarf/blob/v0.13.0/docs/upgrading.md" target="_blank" rel="noopener"&gt;Upgrading&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="breaking-changes"&gt;Breaking Changes&lt;a class="anchor" href="#breaking-changes" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flow keys are &lt;code&gt;{shard}-{hour}-{flowID}-{token}&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;{hour}&lt;/code&gt; is the flow&amp;rsquo;s creation hour, in whole hours since
2000-01-01 00:00 UTC, taken from the database clock. This applies everywhere a flow key appears:&lt;/p&gt;</description></item><item><title>Writing Tasks</title><link>https://docs.dwarf.run/building-workflows/tasks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/tasks/</guid><description>&lt;h1 id="writing-tasks"&gt;Writing Tasks&lt;a class="anchor" href="#writing-tasks" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; writing the code that runs inside a workflow. It covers the &lt;code&gt;workflow.Flow&lt;/code&gt; carrier a
task receives: reading and writing state, the control signals, and handling failure. The graph that decides
&lt;em&gt;which&lt;/em&gt; task runs is &lt;a href="https://docs.dwarf.run/building-workflows/graphs/"&gt;Building Graphs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;A task is a function your host&amp;rsquo;s &lt;code&gt;ExecuteTask&lt;/code&gt; runs. It receives a &lt;code&gt;*workflow.Flow&lt;/code&gt; — the carrier holding the
step&amp;rsquo;s state and control signals — does its work, writes outputs back onto the flow, and returns. This
guide covers the Flow API a task uses.&lt;/p&gt;</description></item><item><title>Building a Host</title><link>https://docs.dwarf.run/building-workflows/building-a-host/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/building-a-host/</guid><description>&lt;h1 id="building-a-host"&gt;Building a Host&lt;a class="anchor" href="#building-a-host" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; putting Dwarf behind a real application. It turns
&lt;a href="https://docs.dwarf.run/operating/data-handling/#access-control-is-the-hosts-job"&gt;Access Control Is the Host&amp;rsquo;s Job&lt;/a&gt; into code.
Read &lt;a href="https://docs.dwarf.run/getting-started/"&gt;Getting Started&lt;/a&gt; first for the two-method &lt;code&gt;Host&lt;/code&gt; interface.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The engine has no notion of a caller: no authentication, no authorization, no tenants. Every access decision
belongs to the layer you build around it. This page builds that layer for a multi-tenant service, as one
type, &lt;code&gt;App&lt;/code&gt;, that is three things at once:&lt;/p&gt;</description></item><item><title>Coming from Temporal or LangGraph</title><link>https://docs.dwarf.run/reference/coming-from/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/reference/coming-from/</guid><description>&lt;h1 id="coming-from-temporal-or-langgraph"&gt;Coming from Temporal or LangGraph&lt;a class="anchor" href="#coming-from-temporal-or-langgraph" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; who know Temporal or LangGraph and want to find their footing in Dwarf quickly.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="from-temporal"&gt;From Temporal&lt;a class="anchor" href="#from-temporal" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Temporal&lt;/th&gt;
 &lt;th&gt;Dwarf&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Workflow definition&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/graphs/"&gt;Graph&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;A declarative graph of tasks and transitions instead of imperative code, so nothing is replayed and there are no determinism constraints.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Workflow execution&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/concepts/#flow"&gt;Flow&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Activity&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/tasks/"&gt;Task&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;A plain function, reached by URL through your host.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Worker&lt;/td&gt;
 &lt;td&gt;Your application, with the engine embedded&lt;/td&gt;
 &lt;td&gt;There is no separate Temporal Service to run.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Task queue&lt;/td&gt;
 &lt;td&gt;Your host&lt;/td&gt;
 &lt;td&gt;The host decides how each task URL is reached.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Retry policy&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/tasks/#handling-transient-failures"&gt;&lt;code&gt;f.Retry&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Called by the task, which decides what is retryable.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Timer&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/tasks/#sleep"&gt;&lt;code&gt;f.Sleep&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Signal&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/flows/#resume"&gt;&lt;code&gt;Resume&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Delivers input to a task waiting in &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#interrupt-human-in-the-loop"&gt;&lt;code&gt;f.Interrupt&lt;/code&gt;&lt;/a&gt;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Query&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/flows/#inspecting"&gt;&lt;code&gt;Snapshot&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Reads a flow&amp;rsquo;s status and state.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Child workflow&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/fan-out-and-subgraphs/#subgraphs"&gt;Subgraph&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Continue-as-new&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/flows/#continue-a-flow"&gt;&lt;code&gt;Continue&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Starts a new flow from a completed flow&amp;rsquo;s final state. Your application keeps the chain of flow keys.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Versioning and patching&lt;/td&gt;
 &lt;td&gt;Frozen graphs&lt;/td&gt;
 &lt;td&gt;Each flow keeps the graph it started with. See &lt;a href="https://docs.dwarf.run/operating/upgrading/#the-obligation-that-outlives-every-deploy-graph-evolution"&gt;Graph Evolution&lt;/a&gt;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Visibility and search attributes&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/flows/#listing-flows"&gt;&lt;code&gt;ListOpen&lt;/code&gt; and &lt;code&gt;ListClosed&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Filter by status, workflow, task, fairness key, time and free text. Closed flows are listed from the archive, kept for the &lt;a href="https://docs.dwarf.run/building-workflows/flows/#retention"&gt;retention&lt;/a&gt;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Task queue priority and fairness&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.dwarf.run/building-workflows/scheduling/"&gt;Priority and fairness keys&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The main shift is where orchestration logic lives. In Temporal it is code that the server replays; in
Dwarf it is a graph, with loops, conditions, fan-out and computed jumps, and anything more complex goes in
a task, which is plain code. See &lt;a href="https://docs.dwarf.run/building-workflows/graphs/"&gt;Building Graphs&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>How Dwarf Works</title><link>https://docs.dwarf.run/how-dwarf-works/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/how-dwarf-works/</guid><description>&lt;h1 id="how-dwarf-works"&gt;How Dwarf Works&lt;a class="anchor" href="#how-dwarf-works" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For everyone&lt;/strong&gt; who wants to know what actually happens at runtime: which process runs what, where the
state lives, and what happens when something crashes. The terms defined here are used throughout the
guides.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Dwarf is a library that runs inside your application process. There is no Dwarf server, broker or control
plane: every copy of your application embeds the engine, and the copies coordinate only through the SQL
databases they share.&lt;/p&gt;</description></item><item><title>Observability</title><link>https://docs.dwarf.run/operating/observability/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/observability/</guid><description>&lt;h1 id="observability"&gt;Observability&lt;a class="anchor" href="#observability" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators.&lt;/strong&gt; Developers inject the three providers; operators read what comes out.
This is the full instrument catalogue and how to interpret each one. For which of them to alert on, see
&lt;a href="https://docs.dwarf.run/operating/"&gt;Operating Dwarf&lt;/a&gt;; for what to do when one fires, the &lt;a href="https://docs.dwarf.run/operating/runbook/"&gt;Runbook&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Dwarf emits structured logs, OpenTelemetry metrics, and distributed traces. All three go through standard
providers you inject — the engine owns no exporter and adds no network connections. With nothing injected,
everything degrades to a no-op, so unconfigured and test use pays nothing.&lt;/p&gt;</description></item><item><title>Sizing &amp; Tuning</title><link>https://docs.dwarf.run/performance/sizing-and-tuning/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/performance/sizing-and-tuning/</guid><description>&lt;h1 id="sizing--tuning"&gt;Sizing &amp;amp; Tuning&lt;a class="anchor" href="#sizing--tuning" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; who want to know how the engine sizes itself, or who need to override it. You don&amp;rsquo;t need
this page to run Dwarf: the decisions are in &lt;a href="https://docs.dwarf.run/operating/deployment/"&gt;Deployment&lt;/a&gt;, and the engine applies
these numbers for you. The measurements behind them are in the &lt;a href="https://docs.dwarf.run/performance/benchmark-cloud/"&gt;Cloud Benchmarks&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="connection-pool"&gt;Connection Pool&lt;a class="anchor" href="#connection-pool" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each shard&amp;rsquo;s connection budget is a constant adjusted for distance and capped by the server: &lt;strong&gt;250&lt;/strong&gt;
connections up to 0.25 ms of round-trip time, rising evenly to &lt;strong&gt;500&lt;/strong&gt; at 1 ms, and never more than half
the server&amp;rsquo;s own &lt;code&gt;max_connections&lt;/code&gt; (a fixed 8 on SQLite). The budget is shared by the shard&amp;rsquo;s two
dispatching replicas, about 125 connections each at the default. The rules for overriding it are in
&lt;a href="https://docs.dwarf.run/operating/deployment/#connection-pool"&gt;Deployment&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>v0.12.0</title><link>https://docs.dwarf.run/about/release-notes/v0.12.0/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.12.0/</guid><description>&lt;h1 id="v0120--reducers-that-fold-everywhere-history-stored-once-priority-bands-that-spill"&gt;v0.12.0 — Reducers That Fold Everywhere, History Stored Once, Priority Bands That Spill&lt;a class="anchor" href="#v0120--reducers-that-fold-everywhere-history-stored-once-priority-bands-that-spill" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-10-23. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.12.0" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;v0.12.0 makes a reduced field mean the same thing wherever a task writes it. Every write to a field with a
reducer is a &lt;strong&gt;contribution&lt;/strong&gt;, folded through the reducer at every hand-off: the next step, the fan-in, the final
state and &lt;code&gt;Continue&lt;/code&gt;. A task writes only what it adds, and the outcome no longer depends on the graph&amp;rsquo;s shape. An
agent loop&amp;rsquo;s history is stored &lt;strong&gt;once&lt;/strong&gt;: a field under &lt;code&gt;append&lt;/code&gt;, or a list a task keeps rewriting but only extends,
is kept as references to the rows that hold its elements rather than as the whole history at every step.
Priorities are now five &lt;strong&gt;bands&lt;/strong&gt; that spill, so a better band no longer starves a worse one while workers sit
idle. Beneath that, the engine does fewer round trips per step, a shard is dispatched by two replicas at a
budgeted pool, and a step whose transition was lost to a crash is replayed rather than stranded.&lt;/p&gt;</description></item><item><title>Dialect Benchmarks</title><link>https://docs.dwarf.run/performance/benchmark/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/performance/benchmark/</guid><description>&lt;h1 id="benchmarks"&gt;Benchmarks&lt;a class="anchor" href="#benchmarks" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators&lt;/strong&gt; comparing SQL dialects or shard counts on their own hardware. These are
single-host laptop-scale numbers — good for &lt;em&gt;relative&lt;/em&gt; comparison, not for capacity planning. For
absolute numbers and how to size a deployment, see &lt;a href="https://docs.dwarf.run/performance/benchmark-cloud/"&gt;Cloud Benchmarks&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The benchmarks in &lt;a href="https://github.com/microbus-io/dwarf/tree/main/fixtures" target="_blank" rel="noopener"&gt;&lt;code&gt;fixtures&lt;/code&gt;&lt;/a&gt; in the Dwarf repository measure end-to-end flow throughput and latency through the &lt;strong&gt;public API&lt;/strong&gt;
(&lt;code&gt;Run&lt;/code&gt; = create + await) against whatever SQL dialect &lt;code&gt;SEQUEL_TESTING_DSN&lt;/code&gt; points at. They measure what code
review cannot tell you: how many flows and steps per second each dialect sustains at each shard count, and the
dispatch-latency percentiles.&lt;/p&gt;</description></item><item><title>Tasks in Other Languages</title><link>https://docs.dwarf.run/building-workflows/other-languages/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/other-languages/</guid><description>&lt;h1 id="tasks-in-other-languages"&gt;Tasks in Other Languages&lt;a class="anchor" href="#tasks-in-other-languages" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; whose tasks are, or need to be, written in a language other than Go. Read
&lt;a href="https://docs.dwarf.run/building-workflows/tasks/"&gt;Writing Tasks&lt;/a&gt; first: everything there about state, outputs and errors
still applies, only the transport changes.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The Dwarf engine is written in Go, and so is the &lt;a href="https://docs.dwarf.run/how-dwarf-works/#the-pieces"&gt;host&lt;/a&gt; that embeds it.
Your tasks don&amp;rsquo;t have to be. The host decides how each task is reached, so it can just as well call a
service written in Python, TypeScript, Java or anything else, over HTTP or any other transport. Graphs are
JSON too (&lt;code&gt;workflow.Graph&lt;/code&gt; marshals to and from JSON), so &lt;code&gt;LoadGraph&lt;/code&gt; can fetch them from a service in any
language as well.&lt;/p&gt;</description></item><item><title>Upgrading</title><link>https://docs.dwarf.run/operating/upgrading/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/upgrading/</guid><description>&lt;h1 id="upgrading"&gt;Upgrading&lt;a class="anchor" href="#upgrading" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; deploying an application that embeds Dwarf. It covers two different operations that get
confused with each other — redeploying your own code, and moving to a new version of Dwarf — plus the
compatibility obligation that outlives both: a long-running flow dispatches tasks from a graph frozen
weeks ago.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="dwarf-is-v0x-and-that-is-the-headline"&gt;Dwarf Is v0.x, and That Is the Headline&lt;a class="anchor" href="#dwarf-is-v0x-and-that-is-the-headline" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;There is no backward-compatibility promise yet.&lt;/strong&gt; The API and the database schema are both still evolving,
and a v0.x release may change either — including by editing a migration an earlier release already shipped,
which a database created by that earlier release does not pick up. Version skew between replicas is not
tested and not supported.&lt;/p&gt;</description></item><item><title>v0.11.0</title><link>https://docs.dwarf.run/about/release-notes/v0.11.0/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.11.0/</guid><description>&lt;h1 id="v0110--graceful-cancel-budget-bound-shard-connections-measured-pool-sizing"&gt;v0.11.0 — Graceful Cancel, Budget-Bound Shard Connections, Measured Pool Sizing&lt;a class="anchor" href="#v0110--graceful-cancel-budget-bound-shard-connections-measured-pool-sizing" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-09-28. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.11.0" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;v0.11.0 gives a flow a way to stop &lt;strong&gt;gracefully&lt;/strong&gt;. The immediate, unconditional stop is renamed &lt;strong&gt;&lt;code&gt;Terminate&lt;/code&gt;&lt;/strong&gt;,
and &lt;strong&gt;&lt;code&gt;Cancel&lt;/code&gt;&lt;/strong&gt; now delivers the cancellation to every step in progress as an error on its node&amp;rsquo;s &lt;code&gt;onError&lt;/code&gt;
transition, so a workflow can compensate, clean up, or carry on. The flow ends &lt;code&gt;cancelled&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, or
&lt;code&gt;completed&lt;/code&gt;, depending on what its handlers do. Beneath that, a shard&amp;rsquo;s connection budget no longer grows with the
fleet. A few replicas per shard &lt;strong&gt;dispatch&lt;/strong&gt; it and split its budget, and every other replica holds a
two-connection reader pool there, so a host scaled wide for its own traffic can no longer over-connect the
database. Pool sizes come from &lt;strong&gt;measured&lt;/strong&gt; sweeps on six database sizes instead of a model, sleeping steps get an
index of their own so they stop slowing every scheduling pass, and graph validation rejects field names that a
&lt;code&gt;when&lt;/code&gt; expression could never reference.&lt;/p&gt;</description></item><item><title>Fan-Out &amp; Subgraphs</title><link>https://docs.dwarf.run/building-workflows/fan-out-and-subgraphs/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/fan-out-and-subgraphs/</guid><description>&lt;h1 id="fan-out--subgraphs"&gt;Fan-Out &amp;amp; Subgraphs&lt;a class="anchor" href="#fan-out--subgraphs" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; building workflows that branch. Both features here are graph-authoring plus task-side
API, so read &lt;a href="https://docs.dwarf.run/building-workflows/graphs/"&gt;Building Graphs&lt;/a&gt; and &lt;a href="https://docs.dwarf.run/building-workflows/tasks/"&gt;Writing Tasks&lt;/a&gt; first.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Two features let a workflow do more than a straight line: &lt;strong&gt;fan-out/fan-in&lt;/strong&gt; runs work in parallel and
merges the results, and &lt;strong&gt;subgraphs&lt;/strong&gt; call another workflow as a child.&lt;/p&gt;
&lt;h2 id="fan-out"&gt;Fan-Out&lt;a class="anchor" href="#fan-out" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A fan-out runs several branches concurrently from one task. There are two kinds.&lt;/p&gt;</description></item><item><title>Resharding</title><link>https://docs.dwarf.run/operating/resharding/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/resharding/</guid><description>&lt;h1 id="resharding"&gt;Resharding&lt;a class="anchor" href="#resharding" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; changing the set of database shards behind a running fleet. Read
&lt;a href="https://docs.dwarf.run/operating/deployment/#sharding"&gt;Deployment → Sharding&lt;/a&gt; first for what shards are and how many to run; this page is
about changing the set after flows already exist.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="the-constraint"&gt;The Constraint&lt;a class="anchor" href="#the-constraint" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A flow&amp;rsquo;s shard is part of its identity and never changes.&lt;/strong&gt; Every flow key is &lt;code&gt;{shard}-{hour}-{id}-{token}&lt;/code&gt;, so
the shard index is encoded in the handle your callers hold, and it is what routes every subsequent operation.
Everything derived from a flow stays on its shard for life: subgraph children, continuations, and
forks are all shard-pinned.&lt;/p&gt;</description></item><item><title>v0.10.5</title><link>https://docs.dwarf.run/about/release-notes/v0.10.5/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.10.5/</guid><description>&lt;h1 id="v0105--update-dependencies"&gt;v0.10.5 — Update Dependencies&lt;a class="anchor" href="#v0105--update-dependencies" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-08-03. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.10.5" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Update dependencies to latest, including Sequel v1.11.1 which changes the &lt;code&gt;sequel_pool_wait_count&lt;/code&gt; and &lt;code&gt;sequel_pool_wait_duration_seconds&lt;/code&gt; gauges to counters and renames them appropriately.&lt;/p&gt;</description></item><item><title>Backup and Restore</title><link>https://docs.dwarf.run/operating/backup-and-restore/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/backup-and-restore/</guid><description>&lt;h1 id="backup-and-restore"&gt;Backup and Restore&lt;a class="anchor" href="#backup-and-restore" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; planning backups, and for whoever is holding the pager when a restore actually happens.
A restore is a workflow-level event, not just a database one — flows re-run, completed work rewinds, and
side effects fire again. Know that before you need to.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="what-to-back-up"&gt;What to Back Up&lt;a class="anchor" href="#what-to-back-up" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The shard databases, and nothing else.&lt;/strong&gt; Dwarf keeps no state outside SQL: no local disk, no broker, no
external service, no in-memory state that matters across a restart. A replica is disposable — everything
durable is in the tables. So your backup strategy is entirely your database&amp;rsquo;s, and the engine adds no step
to it.&lt;/p&gt;</description></item><item><title>Driving Flows</title><link>https://docs.dwarf.run/building-workflows/flows/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/flows/</guid><description>&lt;h1 id="driving-flows"&gt;Driving Flows&lt;a class="anchor" href="#driving-flows" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; integrating Dwarf into an application. This is the reference for the calls your code
makes against a running engine — starting flows, reading them back, resuming and terminating them, and
managing retention. It assumes you have an engine configured; standing one up in production is
&lt;a href="https://docs.dwarf.run/operating/"&gt;Operating Dwarf&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Every interaction with a flow is a method on &lt;code&gt;*engine.Engine&lt;/code&gt;. They fall into a few groups: creating &amp;amp;
running, inspecting, pausing &amp;amp; resuming, terminating &amp;amp; recovering, continuing, and retention. Flows are
addressed by their &lt;strong&gt;flow key&lt;/strong&gt; (&lt;code&gt;{shard}-{hour}-{flowID}-{token}&lt;/code&gt;), returned at creation; steps by their &lt;strong&gt;step
key&lt;/strong&gt; (&lt;code&gt;{shard}-{hour}-{flowID}-{stepID}-{token}&lt;/code&gt;), which names its flow too.&lt;/p&gt;</description></item><item><title>v0.10.3</title><link>https://docs.dwarf.run/about/release-notes/v0.10.3/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.10.3/</guid><description>&lt;h1 id="v0103--a-fan-out-node-can-route-to-end-instead-of-fanning-out"&gt;v0.10.3 — A Fan-Out Node Can Route to END Instead of Fanning Out&lt;a class="anchor" href="#v0103--a-fan-out-node-can-route-to-end-instead-of-fanning-out" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-08-03. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.10.3" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Fixes a non-terminating loop. A node that both fans out over an array and carries a
&lt;code&gt;withGoto&lt;/code&gt;/&lt;code&gt;onError&lt;/code&gt; edge to &lt;code&gt;END&lt;/code&gt; — the shape a task-driven loop takes, &amp;ldquo;fan out over
what is pending, or end the flow when there is nothing left to do&amp;rdquo; — now ends the flow
when the task steers to &lt;code&gt;END&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Data Handling &amp; Security</title><link>https://docs.dwarf.run/operating/data-handling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/data-handling/</guid><description>&lt;h1 id="data-handling--security"&gt;Data Handling &amp;amp; Security&lt;a class="anchor" href="#data-handling--security" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators&lt;/strong&gt; who have to answer for what the engine stores: a compliance review, a
deletion request, a decision about who may hold a flow key. It covers what lands in the database, which of
it is reachable by free-text search, what telemetry carries, and how deletion actually works.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="what-the-engine-stores"&gt;What the Engine Stores&lt;a class="anchor" href="#what-the-engine-stores" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Everything durable is in the SQL databases you provide. There is no other store, no local disk state, and no
external service. Two tables carry data you supply.&lt;/p&gt;</description></item><item><title>Detecting Completion</title><link>https://docs.dwarf.run/building-workflows/detecting-completion/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/detecting-completion/</guid><description>&lt;h1 id="detecting-flow-completion"&gt;Detecting Flow Completion&lt;a class="anchor" href="#detecting-flow-completion" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; whose application needs to know when a flow finished — to answer an HTTP request, send a
notification, or trigger downstream work. It explains the three ways to learn a flow&amp;rsquo;s outcome and when to
use each.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;There are three ways to learn a flow&amp;rsquo;s outcome, and they suit different situations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Await&lt;/code&gt;&lt;/strong&gt; — block a live caller until the flow stops, then read the outcome.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Poll&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;Await&lt;/code&gt; with a bounded wait: on a ctx timeout it returns the flow&amp;rsquo;s &lt;em&gt;current&lt;/em&gt; outcome instead
of an error, so a caller whose own deadline is shorter than the flow can answer now and ask again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orchestration&lt;/strong&gt; — model the follow-up work as tasks inside a workflow that calls the real work as a
&lt;a href="https://docs.dwarf.run/building-workflows/fan-out-and-subgraphs/#subgraphs"&gt;subgraph&lt;/a&gt;, so the reaction to success or failure is itself durable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pick &lt;code&gt;Await&lt;/code&gt; when a caller is standing by and can wait for the result. Pick &lt;code&gt;Poll&lt;/code&gt; when the caller is bounded
by its own deadline — an HTTP handler backing a status endpoint — but the flow is not. Pick orchestration when
the reaction must happen reliably regardless of who is (or isn&amp;rsquo;t) waiting — a push notification, a downstream
call, a compensation.&lt;/p&gt;</description></item><item><title>v0.10.2</title><link>https://docs.dwarf.run/about/release-notes/v0.10.2/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.10.2/</guid><description>&lt;h1 id="v0102--newengineundertest-takes-a-test-name-not-a-testingtb"&gt;v0.10.2 — NewEngineUnderTest Takes a Test Name, Not a testing.TB&lt;a class="anchor" href="#v0102--newengineundertest-takes-a-test-name-not-a-testingtb" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-08-03. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.10.2" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Test-support change only. &lt;code&gt;engine.NewEngineUnderTest&lt;/code&gt; is now keyed by a plain test
name instead of a &lt;code&gt;testing.TB&lt;/code&gt;, so a host can stand up an isolated engine under its
own harness — one that has no &lt;code&gt;*testing.T&lt;/code&gt; to hand over. A name was all the
constructor ever needed from one.&lt;/p&gt;
&lt;p&gt;No schema migrations. No change to dispatch, scheduling, recovery, pool sizing, or
anything else on the production path.&lt;/p&gt;</description></item><item><title>Inspecting and Repairing a Flow</title><link>https://docs.dwarf.run/operating/inspecting-and-repairing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/inspecting-and-repairing/</guid><description>&lt;h1 id="inspecting-and-repairing-a-flow"&gt;Inspecting and Repairing a Flow&lt;a class="anchor" href="#inspecting-and-repairing-a-flow" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators and developers&lt;/strong&gt; looking into a flow that failed, stalled or did something unexpected,
and fixing it. The &lt;a href="https://docs.dwarf.run/operating/runbook/"&gt;Runbook&lt;/a&gt; tells you &lt;em&gt;when&lt;/em&gt; to do this; this page shows &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The example is a payout flow with two tasks, &lt;code&gt;Validate&lt;/code&gt; and &lt;code&gt;Pay&lt;/code&gt;, where &lt;code&gt;Pay&lt;/code&gt; rejects amounts over a limit.
A flow was created with an amount of 5,000, and it failed.&lt;/p&gt;
&lt;h2 id="1-find-the-flow"&gt;1. Find the Flow&lt;a class="anchor" href="#1-find-the-flow" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ListClosed&lt;/code&gt; finds closed flows by status, workflow, task, tenant and free text, within a window on when each
was archived; &lt;code&gt;ListOpen&lt;/code&gt; does the same for flows still running or interrupted:&lt;/p&gt;</description></item><item><title>Scheduling</title><link>https://docs.dwarf.run/building-workflows/scheduling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/scheduling/</guid><description>&lt;h1 id="scheduling"&gt;Scheduling&lt;a class="anchor" href="#scheduling" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators.&lt;/strong&gt; Developers set priority, fairness and time budgets per flow; operators
need the same model to read a backlog. The behaviour described here is what the
&lt;a href="https://docs.dwarf.run/operating/runbook/"&gt;Runbook&lt;/a&gt; assumes you know.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Dwarf decides &lt;em&gt;which&lt;/em&gt; pending step runs next, and how long each task may run. Most of this is automatic;
this guide explains the knobs you control. How a step survives a crashed worker is covered in
&lt;a href="https://docs.dwarf.run/how-dwarf-works/#crash-recovery"&gt;How Dwarf Works&lt;/a&gt;, and how a task retries a transient failure in
&lt;a href="https://docs.dwarf.run/building-workflows/tasks/#handling-transient-failures"&gt;Writing Tasks&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>v0.10.0</title><link>https://docs.dwarf.run/about/release-notes/v0.10.0/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.10.0/</guid><description>&lt;h1 id="v0100--replicas-coordinate-through-the-database-and-size-themselves"&gt;v0.10.0 — Replicas Coordinate Through the Database, and Size Themselves&lt;a class="anchor" href="#v0100--replicas-coordinate-through-the-database-and-size-themselves" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-08-02. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.10.0" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This release rebuilds how a Dwarf fleet finds work, divides resources, and carries state. &lt;strong&gt;Both the API and the database schema change.&lt;/strong&gt; Dwarf is v0.x, so upgrading is a maintenance window, not a rolling deploy — see &lt;a href="https://github.com/microbus-io/dwarf/blob/v0.10.0/docs/upgrading.md" target="_blank" rel="noopener"&gt;docs/upgrading.md&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="breaking-changes"&gt;Breaking Changes&lt;a class="anchor" href="#breaking-changes" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Host&lt;/code&gt; loses &lt;code&gt;SignalPeers&lt;/code&gt;.&lt;/strong&gt; The engine now sends nothing to its peers at all; hosts implement two methods, &lt;code&gt;LoadGraph&lt;/code&gt; and &lt;code&gt;ExecuteTask&lt;/code&gt;. Three signal kinds existed here and all three were deleted after measurement — a per-step work doorbell, a per-flow stop broadcast, and a fleet-membership nudge — because in each case the poll the engine already runs converges faster than the message.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SetShard(ShardSpec{Index, DSN, VirtualCPUs})&lt;/code&gt; replaces &lt;code&gt;SetDSN&lt;/code&gt; / &lt;code&gt;SetNumShards&lt;/code&gt; / &lt;code&gt;SetWorkersPerConn&lt;/code&gt;.&lt;/strong&gt; You declare facts about each shard&amp;rsquo;s server; the engine derives the tuning. New &lt;code&gt;SetEngineID&lt;/code&gt; pins a replica identity so restarts don&amp;rsquo;t inflate the observed fleet count.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;workflow.State&lt;/code&gt; is a type, not a &lt;code&gt;map[string]any&lt;/code&gt;&lt;/strong&gt; — on &lt;code&gt;FlowOutcome.State&lt;/code&gt;, &lt;code&gt;Flow.Snapshot()&lt;/code&gt;, baggage, and interrupt/subgraph payloads. It holds each field as raw JSON and decodes on read. &lt;code&gt;Flow.Delete&lt;/code&gt; → &lt;code&gt;Flow.Del&lt;/code&gt;; &lt;code&gt;Flow.SetState&lt;/code&gt;, &lt;code&gt;Flow.Transform&lt;/code&gt;, &lt;code&gt;MergeState&lt;/code&gt;, &lt;code&gt;Graph.HasFanIn&lt;/code&gt; and the &lt;code&gt;Duration()&lt;/code&gt; helpers are gone; &lt;code&gt;Render()&lt;/code&gt; no longer returns an error; typed getters panic on a mistyped read instead of quietly returning a zero value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NewEngineUnderTest(t)&lt;/code&gt; / &lt;code&gt;SetTestName&lt;/code&gt; replace &lt;code&gt;RunInTest&lt;/code&gt; / &lt;code&gt;SetInTest&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema:&lt;/strong&gt; a new &lt;code&gt;dwarf_peers&lt;/code&gt; registry, new step columns (&lt;code&gt;state_refs&lt;/code&gt;, &lt;code&gt;engine_id&lt;/code&gt;), fixed-width &lt;code&gt;CHAR&lt;/code&gt; tokens and statuses widened to &lt;code&gt;VARCHAR&lt;/code&gt;, and SQL Server JSON payloads stored as &lt;code&gt;VARBINARY&lt;/code&gt; — which ends a silent corruption of non-ASCII payloads on that dialect. The baseline migrations are edited in place, so &lt;strong&gt;a database created by v0.9.x cannot be migrated forward — recreate it.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="coordination-is-the-database-and-nothing-else"&gt;Coordination Is the Database, and Nothing Else&lt;a class="anchor" href="#coordination-is-the-database-and-nothing-else" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Replicas discover each other through a &lt;code&gt;dwarf_peers&lt;/code&gt; registry read per shard, not through the host&amp;rsquo;s transport. From one reading a replica derives two different numbers with deliberately opposite risk postures: how many ways to divide a shard&amp;rsquo;s connection budget (over-count and you collapse a database) and which replicas demonstrably dispatch work (over-count and you strand it). A replica that goes blind holds its last good reading rather than acting on silence.&lt;/p&gt;</description></item><item><title>Runbook</title><link>https://docs.dwarf.run/operating/runbook/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/runbook/</guid><description>&lt;h1 id="runbook"&gt;Runbook&lt;a class="anchor" href="#runbook" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; responding to a live incident. Each entry is a symptom you can observe from a dashboard
or an alert, followed by how to confirm it and what to do. For the orientation — what the engine repairs on
its own, and on what schedule — see &lt;a href="https://docs.dwarf.run/operating/"&gt;Operating Dwarf&lt;/a&gt;. Timings quoted here are the engine&amp;rsquo;s
defaults.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Before anything else: check whether it is already being handled.&lt;/strong&gt; Most faults are absorbed. A database
outage shorter than a step&amp;rsquo;s &lt;a href="https://docs.dwarf.run/how-dwarf-works/#crash-recovery"&gt;lease&lt;/a&gt; produces latency and no errors at all; a dead replica&amp;rsquo;s work is
re-dispatched within the flow&amp;rsquo;s time budget plus about a minute and a half, including a step it had finished
but not yet advanced. If the symptom is &amp;ldquo;things are slow&amp;rdquo; rather than &amp;ldquo;a counter
fired,&amp;rdquo; start at &lt;a href="#backlog-is-growing"&gt;Backlog is growing&lt;/a&gt; and resist restarting
replicas — a restart discards in-flight work that was about to complete and adds a phantom replica to the
&lt;a href="https://docs.dwarf.run/operating/deployment/#running-multiple-replicas"&gt;registry&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Testing</title><link>https://docs.dwarf.run/building-workflows/testing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/testing/</guid><description>&lt;h1 id="testing"&gt;Testing&lt;a class="anchor" href="#testing" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; writing tests for their own workflows. It covers the in-process harness Dwarf ships so
your test suite can run real flows with no database and no transport.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Dwarf ships an in-process test harness so you can exercise real workflows — scheduling, fan-out, subgraphs,
interrupts, retries — with no database to set up and no transport to stand up.&lt;/p&gt;
&lt;h2 id="the-two-pieces"&gt;The Two Pieces&lt;a class="anchor" href="#the-two-pieces" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;engine.NewEngineUnderTest(name)&lt;/code&gt;&lt;/strong&gt; constructs an engine wired for testing: an isolated SQLite in-memory
database (per name, and per shard for multi-shard tests), auto-dropped, with migrations run at &lt;code&gt;Startup&lt;/code&gt; —
no DSN. It takes a plain name rather than a &lt;code&gt;testing.TB&lt;/code&gt;, so a harness with no &lt;code&gt;*testing.T&lt;/code&gt; to hand over
reaches it the same way a Go test does; pass &lt;code&gt;t.Name()&lt;/code&gt; from a test. You call &lt;code&gt;Startup(ctx)&lt;/code&gt; yourself, so
any &lt;code&gt;Set*&lt;/code&gt; configuration goes in between, and you own teardown: &lt;code&gt;defer eng.Shutdown(ctx)&lt;/code&gt;. The name is the
isolation key — give several engines the same name to share one database, or distinct names to isolate
them. It logs to stderr at Error for CI clues (&lt;code&gt;DWARF_TEST_LOG_LEVEL&lt;/code&gt; overrides — &lt;code&gt;info&lt;/code&gt;/&lt;code&gt;debug&lt;/code&gt; for the
flow-status play-by-play, &lt;code&gt;silent&lt;/code&gt;/&lt;code&gt;off&lt;/code&gt; to quiet it); a benchmark or fuzz target silences it outright with
&lt;code&gt;SetLogger(slog.New(slog.DiscardHandler))&lt;/code&gt;, since per-iteration logging would swamp the measurement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hosts.Mux&lt;/code&gt;&lt;/strong&gt; is an in-memory implementation of the &lt;a href="https://docs.dwarf.run/how-dwarf-works/#the-pieces"&gt;&lt;code&gt;Host&lt;/code&gt;&lt;/a&gt; interface. Register graphs and task
functions on it, then register it with &lt;code&gt;SetHost(mux)&lt;/code&gt;; it implements &lt;code&gt;LoadGraph&lt;/code&gt; and &lt;code&gt;ExecuteTask&lt;/code&gt;
from those registrations.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;TestCheckout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Checkout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Reserve&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;inventory.reserve&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Charge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;billing.charge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Reserve&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Charge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Charge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;End&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;inventory.reserve&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;reserved&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;billing.charge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;receipt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;r-123&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eng&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dwarf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewEngineUnderTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sku&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ABC&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusCompleted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status %s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;receipt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;receipt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;receipt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;r-123&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;receipt %q&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Mux.HandleGraph(workflowURL, graph)&lt;/code&gt; registers a graph under the URL you pass to &lt;code&gt;Create&lt;/code&gt; or &lt;code&gt;Run&lt;/code&gt;;
&lt;code&gt;HandleTask(taskURL, handler)&lt;/code&gt; registers a task under the URL bound with &lt;code&gt;SetURL&lt;/code&gt;. The handler signature is the same &lt;code&gt;func(ctx, *workflow.Flow) error&lt;/code&gt;
you write in production.&lt;/p&gt;</description></item><item><title>v0.9.5</title><link>https://docs.dwarf.run/about/release-notes/v0.9.5/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.9.5/</guid><description>&lt;h1 id="v095--seamster-extraction-and-graphvalidate-race-fix"&gt;v0.9.5 — Seamster Extraction and Graph.Validate Race Fix&lt;a class="anchor" href="#v095--seamster-extraction-and-graphvalidate-race-fix" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-07-07. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.9.5" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This release fixes a concurrency bug in graph validation and moves the engine&amp;rsquo;s test-only instrumentation into a
standalone module. Both live in one commit (&lt;code&gt;0c113c3&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="fixed"&gt;Fixed&lt;a class="anchor" href="#fixed" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data race in &lt;code&gt;workflow.Graph.Validate&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;Validate()&lt;/code&gt; built the fan-out to fan-in convergence map as a side
effect stored on the graph. Because a graph is loaded once and shared across concurrent &lt;code&gt;Create&lt;/code&gt; calls, two
validations (or a validation racing a &lt;code&gt;MarshalJSON&lt;/code&gt;) wrote and read that field at the same time, which the race
detector flagged and which could corrupt the map. &lt;code&gt;Validate()&lt;/code&gt; is now pure over the graph&amp;rsquo;s immutable definition
and stores nothing on the graph.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="changed"&gt;Changed&lt;a class="anchor" href="#changed" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fan-in routing is now derived at dispatch, not persisted.&lt;/strong&gt; The fan-out to fan-in map is a pure function of the
graph structure, so the engine computes it per flow from the frozen graph definition (new internal package
&lt;code&gt;internal/faninmap&lt;/code&gt;) and caches it beside the parsed graph. It is no longer written into the stored graph JSON, so
a workflow graph carries only its author-defined shape over the wire.&lt;/p&gt;</description></item><item><title>Patterns</title><link>https://docs.dwarf.run/building-workflows/patterns/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/building-workflows/patterns/</guid><description>&lt;h1 id="patterns"&gt;Patterns&lt;a class="anchor" href="#patterns" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt; looking for how to express a common need. Each recipe is short and links to the guide
that covers its building blocks.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="an-llm-agent-loop"&gt;An LLM Agent Loop&lt;a class="anchor" href="#an-llm-agent-loop" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An agent thinks, calls tools, and repeats until it has an answer. That is a loop in the graph: &lt;code&gt;Think&lt;/code&gt;
decides, and a &lt;code&gt;switch&lt;/code&gt; either runs the tools or ends the flow.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Agent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Think&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://agent/think&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UseTools&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://agent/use-tools&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransitionSwitch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Think&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UseTools&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hasToolCalls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransitionSwitch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Think&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;End&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;!hasToolCalls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UseTools&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Think&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre class="mermaid"&gt;graph LR
 classDef task fill:#32a7c1,color:#f4f2ef,stroke:#32a7c1
 classDef term fill:#e5f4f3,color:#434343,stroke:#32a7c1
 linkStyle default stroke:#32a7c1

 t0[&amp;#34;Think&amp;#34;]:::task
 t0_switch{&amp;#34;switch&amp;#34;}:::term
 t1[&amp;#34;UseTools&amp;#34;]:::task
 t1 --&amp;gt; t0
 t0 --&amp;gt; t0_switch
 t0_switch --&amp;gt;|&amp;#34;hasToolCalls&amp;#34;| t1
 _start(( )):::term --&amp;gt; t0
 t0_switch --&amp;gt;|&amp;#34;!hasToolCalls&amp;#34;| _end(( )):::term&lt;/pre&gt;
&lt;p&gt;Every turn of the loop is a durable step, so a crash resumes the agent where it was. For a conversation,
end the flow when the agent has answered, and start the next turn with
&lt;a href="https://docs.dwarf.run/building-workflows/flows/#continue-a-flow"&gt;&lt;code&gt;Continue&lt;/code&gt;&lt;/a&gt;: it carries the previous turn&amp;rsquo;s final state
(the conversation so far) into a new flow. Your application keeps the conversation as a chain of flow keys,
and continues the key the last &lt;code&gt;Continue&lt;/code&gt; returned. To run several tool calls in parallel, have
&lt;code&gt;Think&lt;/code&gt; emit them as an array and fan out with
&lt;a href="https://docs.dwarf.run/building-workflows/fan-out-and-subgraphs/#dynamic-fan-out-foreach"&gt;&lt;code&gt;forEach&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>v0.9.4</title><link>https://docs.dwarf.run/about/release-notes/v0.9.4/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.9.4/</guid><description>&lt;h1 id="v094--maintenance-release-no-api-or-behavior-changes-to-the-engine"&gt;v0.9.4 — Maintenance Release. No API or Behavior Changes to the Engine&lt;a class="anchor" href="#v094--maintenance-release-no-api-or-behavior-changes-to-the-engine" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-07-06. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.9.4" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="fixes"&gt;Fixes&lt;a class="anchor" href="#fixes" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Test stability:&lt;/strong&gt; Fixed a MySQL-timing flake in &lt;code&gt;TestFault_PollSizingErr&lt;/code&gt;. The &lt;code&gt;faultPollSizingErr&lt;/code&gt;
fault is now armed sticky rather than one-shot, so the background &lt;code&gt;timerLoop&lt;/code&gt;&amp;rsquo;s own poll can no longer
consume the fault before the test&amp;rsquo;s explicit poll — the poll reliably clamps to the near-term
error-retry interval instead of occasionally sleeping the full &lt;code&gt;maxPollInterval&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="dependencies"&gt;Dependencies&lt;a class="anchor" href="#dependencies" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Bumped &lt;code&gt;github.com/microbus-io/sequel&lt;/code&gt; &lt;code&gt;v1.10.6&lt;/code&gt; → &lt;code&gt;v1.10.7&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bumped &lt;code&gt;golang.org/x/text&lt;/code&gt; &lt;code&gt;v0.38.0&lt;/code&gt; → &lt;code&gt;v0.39.0&lt;/code&gt; (indirect).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="docs"&gt;Docs&lt;a class="anchor" href="#docs" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Removed the stale Go Report Card badge from &lt;code&gt;README.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>v0.9.3</title><link>https://docs.dwarf.run/about/release-notes/v0.9.3/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/about/release-notes/v0.9.3/</guid><description>&lt;h1 id="v093--poll-operation"&gt;v0.9.3 — Poll Operation&lt;a class="anchor" href="#v093--poll-operation" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Released 2026-07-05. &lt;a href="https://github.com/microbus-io/dwarf/releases/tag/v0.9.3" target="_blank" rel="noopener"&gt;GitHub release&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The engine now exposes Poll(ctx, flowKey) alongside Await. Like Await, it blocks up to the context deadline for a flow to stop, but where Await treats a deadline expiring before the flow stops as a timeout error, Poll instead returns the flow&amp;rsquo;s current outcome — non-terminal, with Stopped() reporting false — and no error. This makes it the natural fit for bridging an open-ended durable flow to a bounded request (for example, answering an HTTP long-poll within its time budget and re-polling on the next request) without conflating &amp;ldquo;not done yet&amp;rdquo; with a real failure. The returned FlowOutcome gains a Stopped() helper to distinguish a terminal/interrupted outcome from one still running. As always, the underlying flow keeps running on the engine independent of the caller; a ctx timeout never tears it down.&lt;/p&gt;</description></item></channel></rss>