This guide covers a safe migration path for world implementations moving to the Workflow 4.1 contract.
Workflow 4.1 moves world state transitions to an event-sourced flow.
- Runtime writes now flow through
storage.events.create(...). run_createdmust be created withrunId = null.- World adapters should expose read operations on
runs,steps, andhooks, while writes are driven by events. - Streamers now support
listStreamsByRunId(runId).
- Update dependencies to the same
@workflow/*4.1 beta line across all packages. - Move storage write paths into
events.createhandling. - Keep read APIs (
runs.get/list,steps.get/list,hooks.get/list/getByToken) stable. - Add
listStreamsByRunIdin streamer implementations. - Verify
resolveData: 'none'behavior is still correct for runs, steps, events, and hooks. - Add or update tests for legacy-run handling.
- Enforce terminal-state guards consistently:
- reject run state transitions on terminal runs (except idempotent
run_cancelled) - reject step mutations on terminal steps
- reject step/hook creation on terminal runs
- reject run state transitions on terminal runs (except idempotent
Legacy runs must remain readable and safely constrained.
- Supported for legacy runs:
run_cancelled(updates run state directly)wait_completedandhook_received(append-only event writes)
- Rejected for legacy runs:
- Other event types should return
409conflict
- Other event types should return
- Future-only runs:
- If a run requires a newer world spec version, return
RunNotSupportedError
- If a run requires a newer world spec version, return
This is implemented across starter, mongodb, redis, and turso.
Not all worlds migrated to event-sourcing in the same way.
events.create() directly constructs run, step, and hook state from event payloads. There is no delegation to legacy CRUD methods for new-spec runs. This is the target architecture.
events.create() validates the event and then delegates to the pre-existing legacyStorage.runs.update(), legacyStorage.steps.update(), etc. The event is appended, but the actual state mutation still flows through the old CRUD path.
This is intentional as a transitional step — it reduces risk by reusing battle-tested persistence logic. However, it means these worlds are not yet fully event-sourced. A future pass should inline the state-building logic to match Starter/MongoDB and remove the legacyStorage shim.
- No manual schema migration script required.
- New stream-to-run mapping is key-based and created on write.
- No manual migration script required.
- New collections/indexes are created lazily at initialization, including
stream_runs.
- Migration scripts are required and included via Drizzle migrations.
- New tables:
workflow_run_versionsstream_runs
- Run setup before first use and after upgrades:
pnpm exec workflow-turso-setupUse package-level tests to validate behavior:
pnpm --filter @workflow-worlds/starter test
pnpm --filter @workflow-worlds/mongodb test
pnpm --filter @workflow-worlds/redis test
pnpm --filter @workflow-worlds/turso test- Ensure this migration doc and package READMEs are updated.
- Add a Changeset describing 4.1 migration and Turso migration requirements.
- Run tests for affected packages.
- Merge to
mainso the release workflow can open/publish the release PR.