v1.40.0 follows the Dwarf engine extracted in v1.39.0 with a graph/task API cleanup (dwarf v0.4.1 → v0.5.0) that prefers node names over endpoint URLs, and a new typed Subflow client for invoking one microservice's tasks and workflows from inside another task body. workflow.NewGraph(name, url) drops its URL argument to become NewGraph(name), and graph builders register a node's endpoint with graph.SetEndpoint(name, url) instead of graph.AddTask. Every generated *api/client.go now ships a Subflow type and a NewSubflow(flow) constructor — the blessed, state-isolated way for a task to call another unit of work — backed by the new flow.Subtask(name, taskURL, in, out) primitive, the task-level sibling of flow.Subgraph. The Foreman's CreateTask gains a required leading name argument, and the OnFlowStopped event now carries the flowKey directly (the FlowOutcome.FlowKey field is gone). Separately, the act package is no longer scaffolded into new projects: imperative claim checks go through frame.Of(ctx) directly, with the typed Actor struct demoted to an optional, solution-owned pattern.
Highlights
workflow.NewGraph(name, url)→NewGraph(name)(loud). A graph is now named at construction and gets its node endpoints separately; the URL argument is gone. A call left at two arguments is a compile error.graph.AddTask(name, url)→graph.SetEndpoint(name, url)(loud). Registering a node's endpoint is renamed to read as what it does — binding an already-named node to a URL. The old method name no longer exists.- Typed
Subflowclient on every microservice (additive). Each generated*api/client.goexposes aSubflowtype andNewSubflow(flow)constructor. From inside a task body,someapi.NewSubflow(flow).SomeTask(ctx, …)runs another service's task or workflow as an isolated child flow — only the declared inputs cross in and the declared outputs cross back. This is the blessed call path; theExecutorremains test-only. flow.Subtask(name, taskURL, in, out)(additive). The task-level sibling offlow.Subgraph: it launches a single task endpoint as an isolated child flow by synthesizing a one-node graph namedname, with the same park / re-enter / out-pointer semantics. No graph definition is required.- Foreman
CreateTaskgains a requirednameargument (loud).CreateTask(ctx, taskURL, …)becomesCreateTask(ctx, name, taskURL, …);nameis the node's display name in diagrams and history. OnFlowStoppedcarriesflowKey;FlowOutcome.FlowKeyremoved (loud). Subscribers read the flow key from the event argument (OnFlowStopped(ctx, flowKey, outcome)) instead of the removedoutcome.FlowKeyfield.actpackage no longer scaffolded (loud if used). New projects no longer generate theactpackage. Useframe.Of(ctx).IfActor/ParseActorfor imperative claim checks, or keep a typedActorstruct as an optional pattern you own.
New Features
Names over URLs in Graph Construction
A graph is now named when it is built, and its nodes are bound to endpoints separately:
g := workflow.NewGraph("CreditApproval")
g.SetEndpoint("SubmitCreditApplication", creditflowapi.SubmitCreditApplication.URL())
g.SetEndpoint("VerifyCredit", creditflowapi.VerifyCredit.URL())
g.AddTransitionChain("SubmitCreditApplication", "VerifyCredit")
g.SetEntryPoint("SubmitCreditApplication")Transitions, Goto, and fan-in all reference nodes by the names registered with SetEndpoint, completing the move (begun in v1.39.0 with flow.Goto node names) away from threading endpoint URLs through the graph topology.
The Typed Subflow Client
Every microservice's *api/client.go now carries a Subflow type alongside the Client, MulticastClient, and the test-only Executor. NewSubflow(flow) binds it to the calling task's *workflow.Flow, and each method parks the calling step and re-enters it when the child terminates — returning the endpoint's normal outputs with a yield bool inserted before err:
func (svc *Service) RunIdentityVerification(ctx context.Context, flow *workflow.Flow, applicant Applicant) (identityVerified bool, err error) {
identityVerified, yield, err := creditflowapi.NewSubflow(flow).IdentityVerification(
ctx, applicant.Name, applicant.SSN, applicant.Address, applicant.Phone,
)
if yield {
return false, nil // parked, child running
}
if err != nil {
return false, errors.Trace(err)
}
return identityVerified, nil
}A method whose endpoint is a task maps to flow.Subtask; one whose endpoint is a workflow maps to flow.Subgraph. Only the declared inputs cross into the child and only the declared outputs cross back — the caller's flow state is never shared. This is the way one task invokes another unit of work with state isolation; calling the Executor or foremanapi from a task body is not. The boilerplate (the Subflow type, NewSubflow, and the marshalSubflow helper) ships in every client, even services with no tasks or workflows yet, so the typed methods are already there when an endpoint is added.
The flow.Subtask Primitive
flow.Subtask(name, taskURL, in, out) launches a single task endpoint as an isolated child flow — the task-level sibling of flow.Subgraph. The engine synthesizes a trivial one-node graph named name around taskURL, so any task endpoint can be invoked without a graph definition; parking, re-entry, the out-pointer result, and cancel/interrupt propagation are identical to Subgraph:
var out VerifySSNOut
yield, err := flow.Subtask("VerifySSN", creditflowapi.VerifySSN.URL(), VerifySSNIn{SSN: ssn}, &out)Pass a task URL (not a graph URL); the typed Subflow client makes the distinction automatic, and the raw primitive is the dynamic-only escape hatch. The subflow boundary reads in ⇄ out throughout, matching Subgraph's in/out symmetry.
Breaking Changes
The upgrade skill handles each of these. Manual migration is not recommended.
workflow.NewGraph(name, url)→NewGraph(name)(loud). Drop the URL argument from every graph constructor.graph.AddTask(name, url)→graph.SetEndpoint(name, url)(loud). Rename the call in every graph builder; arguments are unchanged.foremanapiCreateTaskgains a leadingname(loud).CreateTask(ctx, taskURL, initialState, opts)becomesCreateTask(ctx, name, taskURL, initialState, opts). The newCreateTaskIn.Nameflows through the endpoint, service, generated code, and manifest.OnFlowStopped(ctx, flowKey, outcome);FlowOutcome.FlowKeyremoved (loud). Subscribers take the flow key from the event argument; the field on the outcome no longer exists.actpackage no longer generated (loud if referenced). Projects that imported the scaffoldedactpackage switch toframe.Of(ctx).IfActor/ParseActor, or keep a hand-owned typedActorstruct.- dwarf bumped
v0.4.1→v0.5.0. Pulled in bygo mod tidy; the graph/task API cleanup above rides this bump.
Migration
From inside a Microbus project, ask Claude Code to upgrade Microbus:
Get the latest version of Microbus.
The upgrade skill handles the version bump end-to-end:
- Bump
go.modtov1.40.0andgo mod tidy(which bumpsgithub.com/microbus-io/dwarftov0.5.0). - Refresh
.claude/rules/,.claude/skills/, and project-wide framework-managed files. - Drop the URL argument from every
workflow.NewGraphcall and renamegraph.AddTasktograph.SetEndpoint. - Thread the
nameargument through everyforemanapiCreateTaskcall site. - Update
OnFlowStoppedsubscribers to readflowKeyfrom the event argument instead ofFlowOutcome.FlowKey. - Retrofit the
Subflowclient (Subflowtype,NewSubflow,marshalSubflow) into every*api/client.go, with per-endpoint methods for services that have task or workflow endpoints. - Remove the scaffolded
actpackage and repoint imperative claim checks atframe.Of(ctx). - Regenerate mocks (
genmock) and manifests (genmanifest), then rungo vet ./... && go test ./....
Documentation
- Updated: Building Agentic Workflows and Building an LLM Workflow for
NewGraph(name),graph.SetEndpoint, and the typed Subflow client. - Updated: State, Reducers, and Yield and Re-Enter for
flow.Subtask, theNewSubflowcall path, and in/out subflow symmetry. - Updated: Package
workflowforflow.Subtaskand the names-over-URLs graph API. - Updated: Foreman for the
CreateTaskname argument and theOnFlowStoppedflow-key signature. - Updated: Credit Flow example for the Subflow client and the updated graph construction.
- Updated: Enabling Auth for imperative claim checks without the
actpackage.