Skip to content

Add support for idempotent workflow creation #85

@mikearnaldi

Description

@mikearnaldi

Problem

Currently, when starting a workflow using start(), there is no way to provide a custom runId. The system always auto-generates a unique ID. This creates problems in several common scenarios:

Use Cases Affected

  1. Webhook Deduplication: When webhook providers send duplicate events (common with retry logic), each call to start() creates a new workflow run, even if it's processing the same event.

  2. Business Entity Uniqueness: Applications often need to ensure only one workflow runs per business entity (e.g., one order processing workflow per order ID). Currently, there's no built-in way to enforce this.

  3. Retry-Safe Triggers: In distributed systems, workflow triggers may be retried. Without idempotency support, retries create duplicate workflow runs.

Example Scenario

// Webhook handler receives same event twice due to retry
async function handleOrderWebhook(orderId: string) {
  // Problem: Both calls create separate workflow runs!
  const run1 = await start(processOrderWorkflow, [orderId]);
  const run2 = await start(processOrderWorkflow, [orderId]); // Duplicate!
}

Proposed Solution

Add optional runId field to StartOptions:

interface StartOptions {
  deploymentId?: string;
  runId?: string; // NEW: Enable idempotent workflow creation
}

Usage Example

async function handleOrderWebhook(orderId: string) {
  const customRunId = `wrun_order_${orderId}`;
  
  // First call creates workflow
  const run1 = await start(processOrderWorkflow, [orderId], {
    runId: customRunId
  });
  
  // Second call returns existing run (idempotent)
  const run2 = await start(processOrderWorkflow, [orderId], {
    runId: customRunId
  });
  
  console.log(run1.runId === run2.runId); // true
}

Implementation Requirements

  1. world.runs.create() should accept optional runId
  2. If runId exists, return existing run instead of throwing error
  3. If runId not provided, auto-generate as before (backward compatible)
  4. All storage backends (world-local, world-postgres, world-vercel) should support this

Benefits

  • No breaking changes: Fully backward compatible
  • Simpler code: No external deduplication needed
  • Safer: Built-in idempotency guarantees
  • Standard pattern: Matches idempotency key patterns from Stripe, AWS, etc.

Related

This follows the same pattern already used for step-level idempotency with stepId (see docs).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions