Version and evolve durable workflows
Keep definition identity stable, deploy compatible Workers, reject unknown checkpoint schemas, and migrate without reinterpreting history.
Why versioning is runtime state
A durable Task may resume weeks after creation on a different process. Its checkpoint names the Workflow definition and schema that produced it. Changing step order, branch identity, wait meaning, input shape, or recovery policy can change what old state means.
Version is therefore part of execution identity, not release decoration.
Definition identity
Use stable Workflow name plus explicit version. Step IDs and branch IDs are durable data; never derive them from source line numbers or display labels. Register every definition that active Tasks may require.
Reject missing or mismatched definitions before acquiring external authority. Do not silently send an old checkpoint through the newest graph.
Deployment strategies
Safe options include running old and new Worker pools together, registering multiple versions in one Worker, draining old versions before cutover, or creating new Tasks only on the new version while old Tasks finish unchanged.
Use deployment admission to control which version receives new work. Monitor active and waiting Task counts by version without placing high-cardinality Task identity in metrics.
Checkpoint migrations
Prefer no migration when old code can remain available. When migration is required, define a typed transformation from an exact source schema to an exact target schema. Validate tenant, Workflow identity, phase, branch membership, usage, and Effect evidence.
Migration must be idempotent, fenced against active Workers, auditable, and tested on production-shaped fixtures. Unsupported history fails explicitly.
Retirement checklist
Before removing a version, prove there are no queued, leased, waiting, or recoverable Tasks; no MCP Task routes target it; no Timer or Signal can wake it; retention and legal-hold requirements are satisfied; old checkpoint fixtures remain covered where support is promised; and rollback no longer requires the old binary.
Register two versions during rollout
let v1 = Workflow::builder("order-review")
.version(1)
.step("review", ReviewV1, CapabilitySet::new())
.build()?;
let v2 = Workflow::builder("order-review")
.version(2)
.step("review", ReviewV2, CapabilitySet::new())
.step("audit", WriteAudit, audit_capabilities)
.build()?;
let mut registry = WorkflowRegistry::new();
registry.register(WorkflowDefinition::new(
Arc::new(v1), v1_budget, v1_capabilities,
))?;
registry.register(WorkflowDefinition::new(
Arc::new(v2), v2_budget, v2_capabilities,
))?;Route only new Tasks to version 2 while workers can still resume version 1.
Count queued, leased, waiting, and recoverable Tasks by definition version.
Remove version 1 only when every count is zero and no external route can create
or wake it. A missing definition produces DefinitionUnavailable; never
silently substitute the newest version.