Inside the World
State and delivery
How an event becomes durable work, why the outbox exists, and where transactions stop.
The event is the durable record
An accepted event changes both history and the queryable state for that run. A completed step, for example, appends its event, updates its step row and may leave dispatch work to wake the orchestrator. The relevant local changes commit together.
BEGIN;
append event;
apply run / step / hook / wait transition;
record dispatch or index outbox debt;
COMMIT;
// After commit: attempt external publication.
// If interrupted: the stored debt remains retryable.Keeping queryable rows avoids folding the entire log just to answer a status read. Keeping the log lets the SDK reconstruct the workflow’s decisions.
Why commit an outbox?
Suppose the store commits a step result and crashes before sending its wake. Without durable intent, the step is complete but the run may never hear about it. Publishing first is also unsafe: the next delivery could observe missing state.
The outbox records the obligation with the state change. After commit, publication is attempted. If acknowledgment is lost, the same intent may publish again; deduplication and replay must tolerate that.
Hook claims cross transaction boundaries
A forced claim must not dispose another run’s hook before validating the requester. The run store checks the real reducer in a rolled-back savepoint, then persists the accepted request and reserves its hook ID before contacting the token registry.
Operation digests and canonical decisions make retries address the same claim. A reset leaves a recoverable intent; a pending takeover prevents a third claimant from slipping between release and transfer. Disposal callbacks stay outside run gates, including when the previous owner’s start Hook has not materialized yet.
At-least-once means side effects can repeat
A queue can lose the acknowledgment after a step called an external API. It must retry because it cannot know whether the body finished. The SDK has durable step outcomes, but it cannot atomically commit your payment provider’s state with our event log.
Use a stable business idempotency key when calling external systems. A replay reading a stored result is different from retrying a step whose outcome was never durably recorded.
| Mechanism | What it protects | What it does not promise |
|---|---|---|
| Queue idempotency key | Repeated admission for the same key; the first accepted payload and delay win. | Exactly-once execution of a side effect. |
| Per-run replay serialization | Concurrent orchestrator deliveries for one run. | Global serialization across all runs or steps. |
| Pure-wake coalescing | Avoids eligible replay requests already covered by a successful delivery. | Dropping distinct hook inputs or arbitrary messages. |
| Durable in-flight state | Recovery when a shard restarts during delivery. | No duplicate starts after acknowledgment loss. |
Retries, limits and recovery
Counted queue attempts back off by min(attempt² seconds, 300 seconds). The World defaults to 64 attempts. Core has a separate delivery policy, whose default limit is 48. These limits are not interchangeable.
A valid positive numeric handler-error retryAfter sets a retry floor over local backoff, capped at 23 hours. A successful spec-8 timeout enqueues a fresh message before acknowledging the original, without spending its failed-delivery budget. A spec-9 zero-second continuation is a handoff rather than another counted failure. Transient depth or object-reset failures surface as retryable 503s. A fatal payload-size error is different: retrying the same bytes cannot make them fit.
Exhausted messages go to the DLQ bucket. Supported store redrives use new persisted delivery keys. Accepted deduplication keys remain stored; there is no automated retention policy to prune all historical state.
Sources and implementation
- packages/world-cloudflare/src/store/events/apply.ts
- packages/world-cloudflare/src/store/outbox.ts
- packages/world-cloudflare/src/queue/deliver.ts
- packages/world-cloudflare/src/queue/dedup.ts
Repository links may require access. Vercel links point to public source or published documentation. Logical API names do not establish backend internals.