v1.48.0 — Contributions Everywhere, Every Output Applied, Priority Bands That Spill

Released 2026-10-23. GitHub release.

v1.48.0 moves the embedded workflow engine to dwarf v0.12.0, which gives reduced state fields one meaning wherever a task writes them. A write to a field with a reducer is now a contribution, folded through the reducer at every hand-off rather than only at a fan-in, so a task writes only what it adds and the result no longer depends on the graph’s shape. Fabric’s generated task code follows suit: every value a task returns without an error is applied to the state, zero values included. Priorities become five bands that spill, the engine sizes each shard’s connections itself, and an agent loop’s conversation is stored once instead of at every step. The release changes the engine’s schema, its API and its wire format, so it needs a maintenance window.

Highlights

  • Reducers fold at every hand-off. A write to a reduced field adds to it at the next step, the fan-in, the final state and Continue. flow.Overwrite replaces a reduced field and flow.Del clears it. (Behavior change - see below.)
  • Every returned output is applied. The generated task wrapper writes each declared output with its own flow.Set, so returning false, 0 or nil resets a field with no reducer instead of being silently skipped.
  • Stricter task and workflow arguments. genservice rejects an In/Out field the state cannot carry as written: unexported or json:"-" fields, embedded structs, the ,string option, and names ending in ! or +.
  • Priority bands that spill. Priorities run from 1 to the foreman’s new MaxPriority config (5 by default), an unset priority is the midpoint, and capacity a better band leaves unused goes to the next one. (Breaking - see below.)
  • Engine-derived connection budgets. Each shard’s connections are a budget the engine derives and shares between the shard’s two dispatcher replicas; maxConnections on a Shards entry overrides it, and MaxOpenConns is gone. (Breaking - see below.)
  • An agent loop stores each message once. llm.core’s ChatLoop appends its conversation through a reducer, and the engine keeps an appended list as references rather than a full copy per step.
  • agentstudio shows what a subgraph returned, on a new Subgraph tab of the step page.
  • Workflow diagrams render again. genworkflowmmd, which housekeeping runs to draw each workflow, had been failing since dwarf v0.10.0; it is fixed and now covered by a test.

Reducers Fold at Every Hand-Off

Before this release, a step’s write to a reduced field outside a fan-out branch was the field’s whole value, and the reducer combined writes only where parallel branches met. Now every write to a field wired with graph.SetReducer is a contribution, folded wherever the state passes from one step to the next. A read returns the combined value.

For task authors this comes down to four rules:

  • Write your contribution, not the value you read. With messages on ReducerAppend, returning []llmapi.Item{reply} adds the reply. Returning append(messages, reply) adds the whole history again.
  • Overwrite to trim or reset. flow.Overwrite("messages", messages[max(0, len(messages)-20):]) keeps the last 20. Overwrite, Del and Clear of a reduced field are refused inside a fan-out branch, where a reset has no defined meaning for the other branches.
  • Contribute after Interrupt or Subgraph. A task runs again from the top after a park or a Retry, and each run’s contributions add to the earlier runs’. A contribution made before the park is made twice.
  • A subgraph returns only what it adds. Writing a child’s result into a reduced field of the parent is a contribution like any other.

The Out-suffixed pair (items in, itemsOut out) invites the read-modify-write the first rule forbids. On a reduced field, return only the new elements, and return nil on a path that adds nothing, including a park’s yield path. Fields with no reducer behave as before.

Every Returned Output Is Applied

dwarf v0.12.0 changes Flow.SetChanges to skip empty omitempty/omitzero fields. Every generated Out struct is omitzero, so a task that returned false after an earlier step set true would have left true in the state. The generated task wrapper and workflow mock now write each declared output with its own flow.Set:

err = flow.Set("approved", &out.Approved)
if err != nil {
	return errors.Trace(err)
}

Any value a task returns without an error is applied, through the field’s reducer or the default replace, on every successful return, including a park’s yield path. A zero value is applied like any other; under some reducers it simply changes nothing. An error return still applies nothing. Returned values are applied after the task body, so a returned value overwrites an earlier flow.Set of the same field: set a field or return it, not both. The value is passed by pointer, so a MarshalJSON method with a pointer receiver is honored.

Because each argument is read and written as its own state field, genservice now rejects what would silently misbehave in a task’s or workflow’s In and Out structs: a field that does not marshal to JSON (unexported or json:"-"), an embedded struct, the ,string option, and a JSON name ending in ! or +, which the engine refuses to write.

Priorities and the Foreman’s Configuration

Priorities now run from 1 (served first) to a maximum, 5 by default and at most 100, and a flow that sets no priority gets the midpoint, 3. The scheduler fills the best due band first and gives whatever capacity remains to the next, so a sparse but steady stream of urgent work no longer starves everything behind it while workers sit idle. Priority stays strict over capacity: worse work gets only what better work leaves unused.

The foreman’s configuration follows the engine:

  • MaxPriority replaces DefaultPriority. It sets the number of priority levels and must be the same on every replica. Create and Run reject a priority above it with a 400.
  • MaxOpenConns is removed. The engine derives each shard’s connection budget (250 to 500 by round trip, clamped by the server’s max_connections) and splits it between the two replicas that dispatch the shard. Set maxConnections on a Shards entry for a database shared with other workloads, or when several shards are databases on one server.
  • virtualCPUs is gone from Shards entries, since pools no longer follow vCPUs. A new weight sets a shard’s relative share of new flows; any weight other than 1 switches the fleet to weighted placement.

The engine’s state cache now defaults to a tenth of the memory limit (GOMEMLIMIT, else the cgroup limit, else 1 GiB), so a foreman’s resident memory grows after the upgrade. Setting GOMEMLIMIT bounds it.

llm.core

ChatLoop wires its items conversation to ReducerAppend, and CallLLM contributes only the folded tool results and the new turn. Reading items still returns the whole conversation, a subgraph caller still receives it whole, and the engine stores each message once. One consequence: a Continue of a finished ChatLoop thread now appends the items it is given, so pass only the new turn.

agentstudio

The flow list’s task column and the dashboard’s error timeline read StoppedTaskName, the task a flow stopped at, which is empty while the flow runs. A step that called a subgraph gains a Subgraph tab showing the child flow’s status, a link to it, and its final state, which is what the subgraph returned.

Observability

  • dwarf_flows_terminated is now dwarf_flows_ended. The bundled Workflow Overview dashboard is updated; dashboards and alerts of your own must change.
  • dwarf_flows_started and dwarf_flows_ended gain a priority label.
  • dwarf_steps_pending and dwarf_steps_oldest_pending_age_seconds are reported every 30 seconds by one leading replica per shard. Aggregate pending steps with max per shard and priority, then sum across shards; the dashboard now does.
  • New panels for dispatch lag by shard, API operation latency by shard, dispatching replicas per shard, and state cache memory.

Other Changes

  • Workflow rules and skills. The workflow rules carry the reducer contract and the returned-values rule, drop their copy of the workflow API in favor of the semantics godoc does not state, and correct several descriptions: the Interrupt-and-Resume example (resume data arrives through the &resume pointer, not the state), forEach branch state, merge order, and fairness weights. A subgraph’s result is described as the calling task’s to merge, and the review skills check reduced-field writes.
  • genworkflowmmd. The program it generates is compiled only when the tool runs, so it had silently failed since GraphRenderer.Render stopped returning an error in dwarf v0.10.0. It is fixed, and a golden test now runs it against a fixture. Rendered diagrams no longer carry a title.
  • Dependencies. dwarf v0.12.0, sequel v1.15.0, OpenTelemetry v1.47.0, modernc.org/sqlite v1.60.1, and a general refresh.

Breaking Changes

  • Writes to reduced fields are contributions. A task that returns or sets a reduced field’s current value plus its addition now adds the history twice. Return only the new part; use flow.Overwrite to reset.
  • dwarf API renames and removals that reach Fabric code: workflow.END is workflow.End; Flow.ParseState is Parse; Graph.SetEndpoint is SetURL; Flow.Get returns (ok bool, err error); GotoRequested, RetryRequested, SleepRequested, InterruptRequested and SubgraphRequested moved from Flow to RawFlow; workflow.Node is Task; Transition’s WithGoto/OnError/Switch booleans are one Kind; FlowStep.SubWorkflowURL/SubWorkflowName are SubgraphURL/SubgraphName, and its edges are step keys; workflow.TerminalStatuses is gone in favor of IsTerminalStatus; engine.TestProxy is hosts.Mux.
  • FlowSummary.TaskName and Query.TaskName are StoppedTaskName, and the meaning changed: empty while a flow runs, so a filter on it matches no running flow.
  • foremanapi.ShardSummary: Shard is Index, LatencyMs int is Latency time.Duration, and Steps and Flows are gone.
  • Foreman configuration: DefaultPriority and MaxOpenConns are removed, MaxPriority is new, and virtualCPUs is gone from Shards entries.
  • Priorities above the range are rejected. An explicit priority above MaxPriority fails Create and Run with a 400. An unset priority now runs at 3, ahead of explicit 4s and 5s, where it previously ran at DefaultPriority (5).
  • Changed status codes: Terminate of an ended flow returns nil (was 409); an Await or Run timeout is a 408 that also satisfies errors.Is(err, context.DeadlineExceeded); a Terminate or Cancel reason over 4 KB is a 400; any value over 256 MB is a 413.
  • Stricter genservice. A task or workflow In/Out field that is unexported, tagged json:"-", embedded, uses ,string, or has a name ending in ! or + now fails generation.
  • The engine’s schema and wire format changed. See Operational Prerequisites.

Operational Prerequisites

  • Recreate or migrate workflow databases. dwarf v0.12.0 revised its migrations in place, so a database migrated by an earlier version does not receive the changes and the new engine fails on it. In LOCAL development, delete shard_1.local.sqlite. For deployed shards, plan a maintenance window: drain or terminate in-flight flows, then use a fresh database or apply the schema changes by hand, as described under “Upgrading from v0.11.0” in the dwarf v0.12.0 release notes. Every shard needs the new dwarf_peers.ordering_seq column.
  • Deploy everything together. Every microservice decodes and re-encodes the flow, whose JSON changed, so task microservices and foreman replicas must all run v1.48.0. Old and new replicas also disagree on how a shard’s dispatchers are chosen, and must not run side by side.
  • Bring old priorities into range with UPDATE dwarf_flows SET priority=3 WHERE priority>5 and the same on dwarf_steps, unless MaxPriority covers them. Threads continued, forked or subgraphed from a flow created at the old default keep its priority of 5.
  • Budget the foreman’s memory with GOMEMLIMIT, which bounds the engine’s state cache.
  • PostgreSQL sessions run in UTC. sequel v1.15.0 starts every PostgreSQL and CockroachDB session in UTC unless the data source name sets a time zone. SQL that relied on a non-UTC server or role time zone, such as NOW() into a TIMESTAMP column, now computes in UTC; add a timezone parameter to keep the old zone.

Migration

From inside a Microbus project, ask Claude Code to upgrade Microbus:

Get the latest version of Microbus.

The upgrade-microbus skill applies the dwarf renames, walks through the signature changes go vet reports, and reviews every task that writes a reduced field, including Out pairs and park paths, with you where the intended total is a design choice. It asks how to bring explicit priorities into the new range, what to do with a pinned MaxOpenConns, and flags ChatLoop Continue callers, assertions on changed status codes, dashboard queries and PostgreSQL time zones. It offers to delete the local SQLite shard file; deployed databases are out of its reach, so it reminds you to migrate them.

Documentation

  • Reducers is rewritten around contributions: write what you add, Overwrite to reset, contribute after a park, and return only the contribution from an Out pair on a reduced field. State covers typed arguments, every returned value being applied, and the In/Out fields genservice rejects, and corrects forEach branch state and the empty-forEach fan-in.
  • workflow follows dwarf v0.12.0: the Flow accessors, Get, Parse, SetChanges and Overwrite, the Transition kinds, Task, NewGraphRenderer, the full FlowOptions, and RawFlow.
  • foreman, Production Readiness and Priority and Fairness document MaxPriority and bands that spill, Shards entries with maxConnections and weight, the derived connection budget, the state cache and GOMEMLIMIT, database settings, and the new metrics and dashboard panels. Reliability and Recovery covers crashed replicas and StoppedTaskName.
  • llm, LLM Integration and Building an LLM Workflow describe ChatLoop’s append-reduced items and passing only the new turn to Continue. The example service pages show the current generated diagrams.
  • Python Integration shows the pyvenv calls as they are: CallAndAwait in an endpoint, and Call then Await in a workflow task.
  • agentstudio has its own page, including the step page’s Subgraph tab, and cmd documents genworkflowmmd and geninit.