Skip to main content

Overview

start() initiates a new workflow execution and returns a Run object for tracking and interacting with the workflow. It handles argument serialization, event sourcing, and queuing the workflow for execution.

Usage

import { start } from 'workflow/runtime';
import { myWorkflow } from './workflows';

const run = await start(myWorkflow, ['arg1', 'arg2']);
console.log('Started workflow:', run.runId);

Signature

function start<TArgs extends unknown[], TResult>(
  workflow: WorkflowFunction<TArgs, TResult> | WorkflowMetadata,
  args: TArgs,
  options?: StartOptions
): Promise<Run<TResult>>

function start<TResult>(
  workflow: WorkflowFunction<[], TResult> | WorkflowMetadata,
  options?: StartOptions
): Promise<Run<TResult>>

Parameters

workflow
WorkflowFunction | WorkflowMetadata
required
The workflow function to start. Must have a 'use workflow' directive and be properly compiled.
args
TArgs
Arguments to pass to the workflow function. Must be serializable.
options
StartOptions

Returns

Run<TResult>
Run
A Run object for interacting with the workflow execution. See Run API.

Examples

Basic Workflow Start

import { start } from 'workflow/runtime';
import { processOrder } from './workflows/orders';

// Start a workflow with arguments
const run = await start(processOrder, [{ orderId: '123', items: [...] }]);

// Wait for completion
const result = await run.returnValue;
console.log('Order processed:', result);

No Arguments

import { start } from 'workflow/runtime';
import { dailyCleanup } from './workflows/maintenance';

// Start a workflow without arguments
const run = await start(dailyCleanup);
console.log('Cleanup started:', run.runId);

With Options

import { start } from 'workflow/runtime';
import { createLocalWorld } from '@workflow/world-local';
import { dataProcessing } from './workflows';

// Use a custom world for testing
const testWorld = createLocalWorld({ dataDir: './test-data' });

const run = await start(
  dataProcessing,
  [{ dataset: 'test' }],
  { world: testWorld }
);

Start and Monitor

import { start } from 'workflow/runtime';
import { analyzeData } from './workflows';

const run = await start(analyzeData, [data]);

// Check status periodically
const checkStatus = async () => {
  const status = await run.status;
  console.log('Current status:', status);
  
  if (status === 'running') {
    setTimeout(checkStatus, 1000);
  }
};

checkStatus();

Start and Stream Results

import { start } from 'workflow/runtime';
import { generateReport } from './workflows';

const run = await start(generateReport, [params]);

// Stream results as they're generated
const reader = run.readable.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  
  console.log('Received chunk:', value);
}

Parallel Workflow Starts

import { start } from 'workflow/runtime';
import { processItem } from './workflows';

// Start multiple workflows in parallel
const items = [1, 2, 3, 4, 5];
const runs = await Promise.all(
  items.map(item => start(processItem, [item]))
);

console.log('Started', runs.length, 'workflows');

// Wait for all to complete
const results = await Promise.all(
  runs.map(run => run.returnValue)
);

console.log('All workflows completed:', results);

Type-Safe Workflow Starts

import { start } from 'workflow/runtime';

// Workflow with typed arguments
export async function createUser(name: string, email: string) {
  'use workflow';
  // ... workflow implementation
  return { userId: '123', name, email };
}

// TypeScript ensures correct argument types
const run = await start(createUser, ['John Doe', '[email protected]']);

// Return type is inferred
const user: { userId: string; name: string; email: string } = await run.returnValue;

Using Workflow Metadata

import { start } from 'workflow/runtime';
import { myWorkflow } from './workflows';

// Access workflow metadata (added by compiler)
const workflowId = (myWorkflow as any).workflowId;
console.log('Starting workflow:', workflowId);

const run = await start(myWorkflow, [args]);

Error Handling

import { start } from 'workflow/runtime';
import { WorkflowRuntimeError } from '@workflow/errors';
import { riskyWorkflow } from './workflows';

try {
  const run = await start(riskyWorkflow, [params]);
  
  try {
    const result = await run.returnValue;
    console.log('Success:', result);
  } catch (error) {
    // Workflow execution error
    if (error instanceof WorkflowRunFailedError) {
      console.error('Workflow failed:', error.message);
      console.error('Run ID:', error.runId);
    }
  }
} catch (error) {
  // Start error (e.g., invalid workflow, serialization error)
  if (WorkflowRuntimeError.is(error)) {
    console.error('Failed to start workflow:', error.message);
  }
}

Run ID Generation

Each workflow run gets a unique ID in the format wrun_{ulid}. The ID is:
  • Generated client-side before execution
  • Globally unique and sortable by creation time
  • Used for tracking, logging, and event correlation
const run = await start(myWorkflow, [args]);
console.log(run.runId); // e.g., "wrun_01HQXYZ123456789ABCDEFGH"

Serialization

Workflow arguments must be serializable. Supported types:
  • Primitives: string, number, boolean, null, undefined
  • Objects and arrays
  • Dates
  • URLs
  • Custom classes with proper serialization
  • Streams (automatically handled)
// ✅ Valid arguments
await start(workflow, [
  'string',
  123,
  { nested: { object: true } },
  new Date(),
  new URL('https://example.com'),
]);

// ❌ Invalid arguments
await start(workflow, [
  function() {}, // Functions not serializable
  Symbol('test'), // Symbols not serializable
]);

Trace Propagation

start() automatically propagates OpenTelemetry trace context to the workflow:
import { trace } from '@opentelemetry/api';
import { start } from 'workflow/runtime';
import { myWorkflow } from './workflows';

const tracer = trace.getTracer('my-app');

await tracer.startActiveSpan('process-request', async (span) => {
  // Trace context automatically propagated to workflow
  const run = await start(myWorkflow, [data]);
  
  span.setAttribute('workflow.runId', run.runId);
  span.end();
});

Best Practices

  1. Await the start call: Always await start() to ensure the workflow is queued
  2. Store run IDs: Save run.runId for later reference and monitoring
  3. Use type inference: Let TypeScript infer return types from workflow functions
  4. Handle start errors: Catch and handle errors from the start call separately from execution errors
  5. Serialize carefully: Ensure all arguments are serializable before passing to workflows
  6. Don’t start workflows from workflows: Use direct function calls or steps instead

See Also

Build docs developers (and LLMs) love