Skip to content

Conversation

@pranaygp
Copy link
Collaborator

@pranaygp pranaygp commented Dec 16, 2025

Pranay:

corresponding workflow-server PR: https://github.com/vercel/workflow-server/pull/154

important: This is a big change to the way workflows work since everything is now event sourced, I introduced new events types, and changed the shape of the step object (lastKnownError -> error and startedAt -> firstStartedAt). New event logs that use this published version of workflow will be incompatible with previous workflow version event logs. This doesn't affect the runtime of workflows since those are deployment pegged - but this does affect observability since the event shape looks different and the world spec has changed. The web-shared package just needs to be compatible with viewing workflow runs of the old schema for this to work correctly (which I believe it does, but please double check @VaguelySerious if I missed anything).

The currently failing e2e tests on vercel world are related to the CLI I believe (slack x-ref). However once we merged the workflow-server PR, we can drop the env var changes on the vercel deployments for PR so that this PR points to the main prod deployment, again and then I'll re-run e2e tests to make sure they work :)

I Also added a new docs page with diagrams to explain the event sourcing and state machine lifecycles (preview link):

Docs preview

small: I also removed the unused run paused/resumed stuff which we've never used to simplify

Summary

Implement event-sourced architecture for runs, steps, and hooks:

  • Add run lifecycle events (run_created, run_started, run_completed, run_failed, run_cancelled)
  • Add step_retrying event for non-fatal step failures that will be retried
  • Remove fatal field from step_failed event (step_failed now implies terminal failure)
  • Rename step's lastKnownError to error for consistency with server
  • Update world-local, world-postgres, and world-vercel to create/update entities from events via events.create()
  • Entities (runs, steps, hooks) are now materializations of the event log
  • Fix hook token conflict error to use WorkflowAPIError with status 409
  • Move event log corruption check to step_created event for earlier detection
  • BREAKING CHANGE: Remove unused run_paused/run_resumed events and paused status

This makes the system faster, easier to reason about, and resilient to data inconsistencies.

Test plan

  • TypeScript compiles
  • Unit tests pass
  • E2E tests pass

🤖 Generated with Claude Code

@changeset-bot
Copy link

changeset-bot bot commented Dec 16, 2025

🦋 Changeset detected

Latest commit: 91fc888

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@workflow/world Minor
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/errors Patch
@workflow/cli Patch
@workflow/core Patch
@workflow/world-vercel Patch
@workflow/web-shared Patch
@workflow/world-testing Patch
@workflow/builders Patch
@workflow/docs-typecheck Patch
workflow Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/astro Patch
@workflow/sveltekit Patch
@workflow/ai Patch
@workflow/nuxt Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Contributor

vercel bot commented Dec 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jan 18, 2026 9:41am
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jan 18, 2026 9:41am
example-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-astro-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-express-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-fastify-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-hono-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-nitro-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-nuxt-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-sveltekit-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workbench-vite-workflow Ready Ready Preview, Comment Jan 18, 2026 9:41am
workflow-docs Ready Ready Preview, Comment Jan 18, 2026 9:41am

@github-actions
Copy link
Contributor

github-actions bot commented Dec 16, 2025

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 435 0 38 473
✅ 💻 Local Development 398 0 32 430
✅ 📦 Local Production 398 0 32 430
✅ 🐘 Local Postgres 398 0 32 430
✅ 🪟 Windows 43 0 0 43
❌ 🌍 Community Worlds 28 156 0 184
Total 1700 156 134 1990

❌ Failed Tests

🌍 Community Worlds (156 failed)

mongodb (39 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

redis (39 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

starter (39 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

turso (39 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 39 0 4
✅ example 39 0 4
✅ express 39 0 4
✅ fastify 39 0 4
✅ hono 39 0 4
✅ nextjs-turbopack 42 0 1
✅ nextjs-webpack 42 0 1
✅ nitro 39 0 4
✅ nuxt 39 0 4
✅ sveltekit 39 0 4
✅ vite 39 0 4
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 39 0 4
✅ express-stable 39 0 4
✅ fastify-stable 39 0 4
✅ hono-stable 39 0 4
✅ nextjs-turbopack-stable 43 0 0
✅ nextjs-webpack-stable 43 0 0
✅ nitro-stable 39 0 4
✅ nuxt-stable 39 0 4
✅ sveltekit-stable 39 0 4
✅ vite-stable 39 0 4
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 39 0 4
✅ express-stable 39 0 4
✅ fastify-stable 39 0 4
✅ hono-stable 39 0 4
✅ nextjs-turbopack-stable 43 0 0
✅ nextjs-webpack-stable 43 0 0
✅ nitro-stable 39 0 4
✅ nuxt-stable 39 0 4
✅ sveltekit-stable 39 0 4
✅ vite-stable 39 0 4
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 39 0 4
✅ express-stable 39 0 4
✅ fastify-stable 39 0 4
✅ hono-stable 39 0 4
✅ nextjs-turbopack-stable 43 0 0
✅ nextjs-webpack-stable 43 0 0
✅ nitro-stable 39 0 4
✅ nuxt-stable 39 0 4
✅ sveltekit-stable 39 0 4
✅ vite-stable 39 0 4
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 43 0 0
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 3 0 0
❌ mongodb 4 39 0
✅ redis-dev 3 0 0
❌ redis 4 39 0
✅ starter-dev 3 0 0
❌ starter 4 39 0
✅ turso-dev 3 0 0
❌ turso 4 39 0

📋 View full workflow run

Copy link
Collaborator Author

pranaygp commented Dec 16, 2025

@pranaygp pranaygp force-pushed the pranaygp/perf-phase-3b-atomic-events branch from 6ebd4c5 to 2e46b8a Compare December 16, 2025 05:45
@pranaygp pranaygp force-pushed the pranaygp/12-04-perf_parallelize_suspension_handler_for_high-concurrency branch from eece359 to 290e879 Compare December 16, 2025 05:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a performance optimization for event creation by adding a createBatch() method to the World interface. The implementation enables atomic batch creation of multiple events, significantly improving the wait completion logic in the runtime from O(n²) to O(n) complexity.

Key Changes

  • Added events.createBatch() method to the World interface for creating multiple events in a single operation
  • Implemented batch creation across three storage backends (world-vercel, world-postgres, world-local) with backend-specific optimizations
  • Optimized runtime wait completion logic using Set-based correlation ID lookup and batch event creation

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/world/src/interfaces.ts Added createBatch() method signature with JSDoc documentation to the Storage events interface
packages/world-vercel/src/storage.ts Integrated batch event creation into the storage adapter
packages/world-vercel/src/events.ts Implemented createWorkflowRunEventBatch() using parallel API calls via Promise.all
packages/world-postgres/src/storage.ts Implemented batch creation using a single INSERT query with multiple values for optimal database performance
packages/world-local/src/storage.ts Implemented sequential batch creation to maintain monotonic ULID ordering for filesystem storage
packages/core/src/runtime.ts Refactored wait completion to use Set-based lookup and batch event creation, improving from O(n²) to O(n) complexity
.changeset/brave-dots-bake.md Added changeset documenting the performance improvement across all affected packages

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

When resumeHook() is called on a legacy run (specVersion < 2), the
hook_received event was previously rejected. This adds support for
storing hook_received events on legacy runs without entity mutation,
matching the behavior of wait_completed.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

Missing version.ts module - file was imported but didn't exist after genversion removal

View Details
📝 Patch Details
diff --git a/packages/world-vercel/src/utils.ts b/packages/world-vercel/src/utils.ts
index baf9566..5e3eeeb 100644
--- a/packages/world-vercel/src/utils.ts
+++ b/packages/world-vercel/src/utils.ts
@@ -3,7 +3,7 @@ import { getVercelOidcToken } from '@vercel/oidc';
 import { WorkflowAPIError } from '@workflow/errors';
 import { type StructuredError, StructuredErrorSchema } from '@workflow/world';
 import type { z } from 'zod';
-import { version } from './version.js';
+import { version } from './version';
 
 export interface APIConfig {
   baseUrl?: string;

Analysis

The file packages/world-vercel/src/utils.ts was importing a non-existent version.js module at line 6, which is actively used in the getUserAgent() function to generate User-Agent headers. The version.ts file appears to have been auto-generated by genversion but was removed during cleanup. The fix involved:

  1. Creating packages/world-vercel/src/version.ts with an exported version constant matching the package.json version (4.0.1-beta.27)
  2. Updating the import statement in utils.ts from './version.js' to './version' (standard TypeScript import format)

This aligns with the turbo.json build configuration that lists src/version.ts as an expected build output, and ensures the User-Agent header can be properly constructed with the package version.


// =============================================================================
// Legacy workflow events (deprecated, use run_* events instead)
// =============================================================================
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use an equivalent end comment, otherwise CreateEventSchema might be misinterpreted as legacy code

* Wire format schema for step in event results.
* Handles error deserialization from wire format.
*/
const StepWireSchema = StepSchema.omit({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate with the schema in workflow-vercel/src/steps.ts?

"dev": "tsc --watch",
"clean": "tsc --build --clean && rm -rf dist",
"test": "vitest",
"typecheck": "genversion --es6 src/version.ts && tsc --noEmit"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ You removed genversion but packages/world-vercel/src/utils.ts still relies on the version file existing to report the user agent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also breaking builds. I'll push a fix for now and we can discuss later

@@ -1,576 +1,11 @@
import path from 'node:path';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the contents of runs-storage.ts, steps-storage.ts, etc. into this file? It'd be nice to see the actual diff, and I don't know if there's a point to have them spread out. We can split it into smaller files in a separate PR if it's just a tidy

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

Missing version.ts module - file was imported but didn't exist after genversion removal

View Details
📝 Patch Details
diff --git a/packages/world-vercel/src/utils.ts b/packages/world-vercel/src/utils.ts
index baf9566..5e3eeeb 100644
--- a/packages/world-vercel/src/utils.ts
+++ b/packages/world-vercel/src/utils.ts
@@ -3,7 +3,7 @@ import { getVercelOidcToken } from '@vercel/oidc';
 import { WorkflowAPIError } from '@workflow/errors';
 import { type StructuredError, StructuredErrorSchema } from '@workflow/world';
 import type { z } from 'zod';
-import { version } from './version.js';
+import { version } from './version';
 
 export interface APIConfig {
   baseUrl?: string;

Analysis

The file packages/world-vercel/src/utils.ts was importing a non-existent version.js module at line 6, which is actively used in the getUserAgent() function to generate User-Agent headers. The version.ts file appears to have been auto-generated by genversion but was removed during cleanup. The fix involved:

  1. Creating packages/world-vercel/src/version.ts with an exported version constant matching the package.json version (4.0.1-beta.27)
  2. Updating the import statement in utils.ts from './version.js' to './version' (standard TypeScript import format)

This aligns with the turbo.json build configuration that lists src/version.ts as an expected build output, and ensures the User-Agent header can be properly constructed with the package version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants