Workers and rollout
Production separates four responsibilities.
Queue tasks may be duplicated and queue records may expire. Every handler returns to the event store to confirm current state.
Recommended task-manager wiring
Enable boot and the target store feature. The host also depends on Boot directly to construct its queue.
Boot owns processor registration, job state, leases, task retry, and shutdown. Flow owns task payload and engine handling semantics. Queue retry handles worker failure, while step retry handles business-step failure. Configure the two budgets separately.
An in-process queue fits one process or development. Use a host-configured durable Boot backend when tasks themselves must survive process loss.
Scheduler loop
The scheduler handles timer waits and delayed retries only. External entry points push hooks and signals.
Several schedulers may scan concurrently. They can dispatch duplicate tasks, while event sequences and wait state make handling converge. Add loop jitter, backoff, and database timeouts so an outage cannot create a tight retry cycle.
Pin runtime builds
Generate a concrete RuntimeBuildId for actual executable code and configure both the engine and new run definitions.
An engine configured for build compatibility rejects unpinned history by default. During legacy migration, enable accept_unpinned() for a bounded period and remove it after those runs drain.
Route tasks by exact build
Keep old and new processors during rolling deployment and route by persisted build identity.
Before dispatching any task, the scheduler preflights all target routes in the scan. One missing build fails the whole tick before partial dispatch. Retain each old route until no active history needs it.
Use with_compatible_build() only when the current worker includes the complete code and dependencies required by old histories. Similar version numbers, a small passing sample, or successful deserialization do not prove replay compatibility.
Replay-safe code changes
When new code changes a workflow decision, add an immutable patch marker to new runs and retain both runtime branches.
Deploy code containing both paths first, add the marker to new definitions second, and remove the old branch only after old history drains. Never reuse one patch ID for another change.
Observation and metrics
Observers run after event commit. They cannot roll back a successful event and must not become workflow state authority.
Metric labels should contain bounded values such as workflow name, definition version, event key, and status. Run IDs, step IDs, hook tokens, and complete error text belong in logs or traces, not metric dimensions. Always redact hook tokens.
Monitor at least these signals.
- Run count by status and oldest suspension age
- Due wakeups, scan lag, and task queue delay
- Step attempts, retry exhaustion, and execution duration
- Active hook count, oldest hook, and callback conflicts
- Event-store errors, sequence conflicts, and schema admission failure
- Active runs and route coverage by
runtime_build_id
Graceful shutdown
- Stop accepting new runs and external callbacks.
- Stop scheduler dispatch.
- Finish or requeue current leases.
- Shut down task queues and database connections.
- Leave workflows non-terminal for replacement processes.
Do not call terminate_for_host_shutdown() during ordinary deployment. That API states that host policy will never resume the run.
Pre-production failure drills
Verify at least four crash windows before launch.
- Exit after a step side effect succeeds and before completion commit, then confirm idempotent redelivery.
- Dispatch a timer task more than once and confirm one completion event.
- Exit after child request commit and before child creation, then confirm one child identity.
- Remove an old-build route and confirm no wrong worker accepts the task and no history changes.
Use the same store and queue types as production. Passing only with in-memory storage does not prove database locking, leases, and recovery order.
