diff --git a/.changeset/docs-throttled-step-results.md b/.changeset/docs-throttled-step-results.md new file mode 100644 index 0000000000..639e57db6f --- /dev/null +++ b/.changeset/docs-throttled-step-results.md @@ -0,0 +1,4 @@ +--- +--- + +Docs: note that throttled step-result writes on Vercel no longer re-run the step body, and document `afterStepBody` for World authors. diff --git a/docs/content/worlds/v5/building-a-world.mdx b/docs/content/worlds/v5/building-a-world.mdx index 168b1f32f7..01542b0e2d 100644 --- a/docs/content/worlds/v5/building-a-world.mdx +++ b/docs/content/worlds/v5/building-a-world.mdx @@ -158,6 +158,8 @@ interface Storage { **Batch writes:** `events.createBatch()` is optional, and implementing it is what declares it. The runtime folds a suspension's `step_created` and `wait_created` writes into batches only when the method exists, and otherwise takes the single-event path unchanged. Implement it with real atomicity per attempt, so a lost race leaves nothing behind, or do not implement it at all. The events land in request order at consecutive positions, and a concurrent writer may push the whole batch above the caller's view of the log. No skipped-event report accompanies the result, so a position-tracking caller compares the committed positions against what it expected and reloads. Reject the whole batch, with a request-level error, for `run_created`, `run_started`, `run_cancelled`, `hook_created`, `hook_disposed`, `attr_set`, for more events targeting one entity than a single write can express, and for a batch over your own size caps. The one legal same-entity pair is `step_created` followed by `step_started`, which creates the step born-running, and there the input must ride the `step_created`. +**Throttled step results:** `step_completed`, `step_retrying`, and a `step_failed` marked with `afterStepBody: true` in `CreateEventParams` record a step body that already ran. If your World retries throttled writes, keep these waiting longer than other writes, because losing one to queue redelivery runs the body again. `@workflow/world-vercel` waits until the invocation's deadline. The flag is advisory, and a World may ignore it. + **Run creation:** For `run_created` events, the `runId` parameter may be a client-provided string or `null`. When `null`, your World generates and returns a new `runId`. **Event data resolution:** `events.list()` and `events.create()` accept `resolveData: 'skip-step-inputs'`, and the runtime replays with it. The World may leave `input` out of `step_created` and `step_started` events, and returns everything else as for `'all'`. Treat any value other than `'none'` as `'all'`, because a World that tests `resolveData === 'all'` strips step results and breaks every replay. Map the value with `entityResolveData()` from `@workflow/world` before passing it to an entity read.