v1.45.0 changes how a caller bridges a durable flow to a bounded request. The foreman's flow-stop notification — FlowOptions.NotifyOnStop and the OnFlowStopped outbound event — is removed in favor of a new long-poll: Poll waits up to the request's time budget for a flow to stop, but a timeout is not an error, so a still-running flow comes back as a non-error running outcome and the caller can answer within its budget and re-poll immediately. Two new example microservices are built on it: banksupport.example, an authentication-and-structured-output agent whose LLM tools are scoped to the signed-in customer's own data, and flightbooking.example, a human-in-the-loop agent that parks on a real Interrupt/Resume decision and composes a nested seat-selection subgraph. The release also hardens token verification — a rotated signing key is now published in JWKS before it signs anything, and per-issuer JWKS fetches are debounced so tokens carrying garbage kids cannot amplify into a fetch storm — and fixes a data race in the connector's SetConfig.
Highlights
PollandPollAndParseon the foreman. The long-poll sibling ofAwait: it waits up to the request's time budget for the flow to stop, but returns a still-running flow as a non-error outcome (outcome.Stopped() == false) so a caller bridging an open-ended flow to a bounded HTTP request can answer within its budget and re-poll with no client-side delay.- Flow-stop notification is removed.
FlowOptions.NotifyOnStopand the foreman'sOnFlowStoppedoutbound event are gone; the launch-then-poll pattern replaces them. (Breaking — see below.) - The
banksupport.exampleagent. A single-microservice showcase of JWT authentication, actor-claims authorization, actor-scoped LLM tools (confused-deputy safe), a durableSupportworkflow, and typed structured LLM output. - The
flightbooking.exampleagent. A durableBookFlightworkflow that parks on a real human accept/keep-searching decision viaflow.Interrupt/foreman.Resume, teaches graph-composition-vs-subgraph, and nests a seat-selection child workflow. - JWKS verification hardening. Rotated keys are advertised in JWKS for a short activation delay before they sign anything, and connector JWKS refreshes are debounced per issuer (at most one fetch per second) so unknown-
kidfloods cannot amplify into bus calls against the token services. TheJWKSresponse is marked cacheable for one second. SetConfigdata race fixed. The connector now holdsconfigLockacross the whole read-modify-write of a config value instead of releasing it mid-update, closing a race withConfig()and config refresh.genserviceliteral guards. The generator now errors on a non-literal string-valueddefine.*field (a const or expression is silently dropped by the AST reader — e.g. leaving an endpoint ungated) and on a backtick inside a feature's godoc (which would break the generated raw string literal).
Bridging Open-Ended Flows with Poll
A flow can outlast a single request's time budget — an LLM tool-calling loop, a long human-in-the-loop pause, a slow downstream. Previously a caller could opt a flow into an asynchronous OnFlowStopped notification; that mechanism is removed. The replacement is Poll, launched after a plain Create:
flowKey, err := foremanapi.NewClient(svc).Create(ctx, myapi.Support.URL(), in, nil)
// ... store flowKey; return it to the browser ...
// From a status endpoint the caller polls:
outcome, err := foremanapi.NewClient(svc).Poll(ctx, flowKey)
if !outcome.Stopped() {
// Still running — report "running" and let the caller re-poll immediately.
}Poll waits up to the request's time budget for the flow to stop. Unlike Await, a timeout is not an error: a still-running flow returns a non-error outcome with outcome.Stopped() == false, so the connection is held efficiently for the duration of the budget and the caller re-fetches with no fixed-interval delay. A completed flow's outcome is delivered the instant it finishes. PollAndParse additionally unmarshals the terminal state into a typed struct, the way AwaitAndParse does for Await. Await and Run remain the synchronous, block-until-stopped forms for flows that finish within the request budget.
Two New Agent Examples
banksupport.example is the suite's authentication + structured-output showcase. A signed-in customer asks a natural-language banking question and an LLM agent answers by calling the customer's own Balance and Transactions endpoints as tools, returning a typed verdict (advice, a card-block flag, a 0–10 risk score). The tools take no customer-id argument — each reads the identity from the verified actor claim and is gated by requiredClaims: roles.customer, so the model can only ever read the signed-in customer's own data. The agent is a durable Support workflow rather than a synchronous Chat, because a multi-turn tool-calling loop outlasts one request's budget; the browser learns the result by long-polling the foreman's new Poll. Demo accounts (alice, bob) and their histories live in a deterministically-seeded, read-only in-memory store so every replica agrees.
flightbooking.example is the human-in-the-loop showcase. Its BookFlight workflow searches a route, proposes candidate flights one at a time, and parks on a real flow.Interrupt awaiting a human accept-or-keep-searching decision, resumed via foreman.Resume. It teaches the graph-composition-vs-subgraph distinction on purpose: the propose/decide/confirm nodes share one graph, while seat selection is delegated to an isolated ChooseSeatAgent child workflow that itself runs llm.core's ChatLoop as a further subgraph. The LLM only refines the seat, so the headline — Interrupt/Resume, a goto loop, nested subgraphs — is fully demonstrable with no provider key; without one, seat selection falls back to the first available seat.
Both examples are wired into the examples app by default. The standalone structured-output and bank-account CRUD examples are subsumed by banksupport.
Token Verification Hardening
The token-verification hot path is hardened against unknown-kid flood amplification and made more forgiving of key rotation:
- Delayed key activation. When a signing key rotates, the new key is published in JWKS for a short activation delay before it signs any token — the access token service keeps signing with its previous key and the bearer token service with its alternate (outgoing) key during the window. Verifiers therefore have time to fetch the new key before the first token bearing its
kidarrives, eliminating a class of transient verification failures right after a rotation. - Debounced JWKS fetches. A connector refreshes JWKS on an unknown
kid, but that refresh is now debounced per issuer (at most once per second), so a flood of tokens carrying garbagekids cannot amplify into a storm of JWKS fetches againstaccess.token.core/bearer.token.core. TheJWKSresponse is marked cacheable for one second for the same reason.
Neither change adds a configuration knob; the activation delay and fetch cooldown are fixed constants.
Other Changes
- Connector
SetConfigdata race.SetConfiglooked up a config underconfigLockbut then read the original value and wrote the new one after releasing it, racingConfig()and the config-refresh path. The lock is now held across the whole read-modify-write and released before theonConfigChangedcallback, using a pre-computedchangedflag so the callback gate no longer reads shared state. - Transport teardown read. The NATS connection refcount is now read inside
poolLockso the teardown decision is a single synchronized observation. - Dependencies. Dwarf is updated to v0.9.3 (which adds the foreman
Pollprimitive), alongside a general dependency refresh.
Breaking Changes
- Flow-stop notification is removed.
FlowOptions.NotifyOnStopand the foreman'sOnFlowStoppedoutbound event no longer exist. A microservice that subscribed toOnFlowStoppedto learn when a flow stopped should instead launch the flow withCreateand long-poll it withPoll(or block withAwait/Runwhen the flow finishes within the request budget). Remove theOnFlowStoppedinbound event sink and dropNotifyOnStopfrom anyFlowOptions.
Migration
From inside a Microbus project, ask Claude Code to upgrade Microbus:
Get the latest version of Microbus.
The upgrade system is now a single self-propagating skill. Each release ships one self-contained upgrade-microbus skill that hardcodes its source and destination versions: it bumps go.mod to the destination, applies that release's source edits, regenerates with genservice, and gates the increment with go vet. If a farther target is requested, the skill pulls the next published release's copy of itself and chains hop-by-hop until it lands, running go test once at the target as a best-effort behavior check. For v1.45.0 the migration removes the workflow flow-stop notification — the OnFlowStopped sink and FlowOptions.NotifyOnStop — and points the affected callsites at the launch-then-Poll pattern.
Documentation
- New:
banksupportpackage reference — the authentication + structured-output agent with actor-scoped LLM tools. - New:
flightbookingpackage reference — the human-in-the-loop agent with Interrupt/Resume and a nested subgraph. - Updated:
foremanpackage reference — thePoll/PollAndParselong-poll, replacing the removed flow-stop notification. - Updated: Build a workflow and Build an LLM workflow — the launch-then-poll pattern.
- Updated:
accesstokenandbearertokenpackage references — delayed key activation and debounced JWKS fetches.