v1.48.1 — Python Tasks Rejoin Long Calls

Released 2026-10-23. GitHub release.

v1.48.1 is a fix release. Its main fix is to workflow tasks that call Python: the retry that lets a long Python computation outlive a task’s time budget never fired, so such a task failed at its deadline instead of rejoining the call. The fix uses Poll, new in pyvenv v1.1.0. The release also corrects the foreman’s Purge limits and several statements in the workflow and Python rules.

Highlights

  • Python tasks rejoin long calls. The add-python-task pattern now uses pyvenv’s new Poll, which reports a call still running near the task’s deadline as an ordinary result, so the task arms its retry in time and the next dispatch rejoins the same Python call.
  • pyvenv v1.1.0. Adds Venv.Poll, a sibling of Await for deadline-bounded callers.
  • Purge documents its real limits: at most 4096 flows per call, and the query must set at least one filter.
  • Corrected workflow and Python rules.

Python Tasks Rejoin Long Calls

A Python computation can run longer than a workflow task’s time budget (at most 15 minutes). The add-python-task pattern handles that by persisting the call’s ID in the flow state, arming a retry when the wait runs out, and rejoining the same call on the next dispatch. The retry was gated on a 408, but an in-process Await that runs out of time returns context.DeadlineExceeded, which reports as a 500, so the retry never fired. Even with the right check, an Await on the task’s own context returns only at the task’s deadline, when the dispatch has already timed out.

pyvenv v1.1.0 adds Poll, which waits for 4/5 of the time left until the context’s deadline and reports a still-running call as done=false with no error. The generated task now reads:

var out myserviceapi.MyTaskOut
done, err := svc.venv.Poll(ctx, callID, &out)
if err != nil {
    return 0, errors.Trace(err)
}
if !done {
    flow.Retry(0, 1.0, 0, 0) // unlimited; the retry polls the same call
    return 0, nil
}
flow.Del("pyCallID") // the call is consumed, so later steps must not see it
return out.Output1, nil

The pattern also stops clearing pyCallID before returning an error, which had no effect: an error discards the writes of the run that returns it.

Other Changes

  • Purge. The foreman’s description said 10,000 flows per call; dwarf caps it at 4096, and rejects a query with no filter.
  • Workflow rules. ReducerUnion accepts a single element, an object included, as well as a list, and deduplicates objects by value; the rules said it rejected objects. flow.Interrupt returns an error for a payload that does not marshal, resume data that does not unmarshal, or a second park in the step; the rules said its error was always nil, and the example now handles it.
  • Python rules. The retry guidance follows the new pattern, Poll joins the list of pyvenv primitives, and a reference to a nonexistent AddTransitionOnTimeout is gone.
  • Dependencies. pyvenv v1.1.0, nats.go v1.55.0, nats-server v2.15.1, Prometheus client_golang v1.25.0, OpenTelemetry exporters for v1.47.0, modernc.org/sqlite v1.61.0, and a general refresh.

Migration

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

Get the latest version of Microbus.

The upgrade-microbus skill finds each task built from the earlier add-python-task pattern, recognizable by its venv.Await call followed by a retry gated on http.StatusRequestTimeout, and rewrites it to poll. A project without Python tasks needs no source change.

Documentation

Python Integration shows the workflow task polling its Python call with Poll, and Yield and Re-Enter states when flow.Interrupt returns an error.