> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/vercel/workflow/llms.txt
> Use this file to discover all available pages before exploring further.

# Understanding Directives

> Explore how JavaScript directives enable the Workflow DevKit's durable execution model.

The Workflow Development Kit uses JavaScript directives (`"use workflow"` and `"use step"`) as the foundation for its durable execution model. Directives provide the compile-time semantic boundary necessary for workflows to suspend, resume, and maintain deterministic behavior across replays.

This page explores how directives enable this execution model and the design principles that led us here.

## Workflows and Steps Primer

The Workflow DevKit has two types of functions:

**Step functions** are side-effecting operations with full Node.js runtime access. Think of them like named RPC calls - they run once, their result is persisted, and they can be retried on failure:

```typescript theme={null}
async function fetchUserData(userId: string) {
  "use step";

  // Full Node.js access: database calls, API requests, file I/O
  const user = await db.query("SELECT * FROM users WHERE id = ?", [userId]);
  return user;
}
```

**Workflow functions** are deterministic orchestrators that coordinate steps. They must be pure functions - during replay, the same step results always produce the same output:

```typescript theme={null}
export async function onboardUser(userId: string) {
  "use workflow";

  const user = await fetchUserData(userId); // Calls step

  // Non-deterministic code would break replay behavior
  if (Math.random() > 0.5) {
    await sendWelcomeEmail(user);
  }

  return `Onboarded ${user.name}!`;
}
```

**The key insight:** Workflows resume from suspension by replaying their code using cached step results from the event log. When a step like `await fetchUserData(userId)` is called:

* **If already executed:** Returns the cached result immediately from the event log
* **If not yet executed:** Suspends the workflow, enqueues the step for background execution, and resumes later with the result

## The Core Challenge

This execution model enables powerful durability features - workflows can suspend for days, survive restarts, and resume from any point. However, it also requires a semantic boundary in the code that tells **the compiler, runtime, and developer** that execution semantics have changed.

The challenge: how do we mark this boundary in a way that:

1. Enables compile-time transformations and validation
2. Prevents accidental use of non-deterministic APIs
3. Allows static analysis of workflow structure
4. Feels natural to JavaScript developers

## Why Directives?

JavaScript directives have precedent for changing execution semantics within a defined scope:

* `"use strict"` (ECMAScript 5, TC39-standardized) changes language rules to make the runtime faster, safer, and more predictable
* `"use client"` and `"use server"` (React Server Components) define an explicit boundary of "where" code gets executed
* `"use workflow"` (Workflow DevKit) defines both "where" code runs (in a deterministic sandbox) and "how" it runs (deterministic, resumable, sandboxed execution)

Directives provide a build-time contract. When the Workflow DevKit sees `"use workflow"`, it:

* Bundles the workflow and its dependencies into code that can be run in a sandbox
* Restricts access to Node.js APIs in that sandbox
* Enables static analysis to generate UML diagrams/visualizations
* Signals to the developer that you are entering a different execution mode

## How Directives Work

The `"use workflow"` and `"use step"` directives trigger different transformations depending on the compilation mode:

### Step Mode Transformation

**Input:**

```typescript theme={null}
export async function createUser(email: string) {
  "use step";
  return { id: crypto.randomUUID(), email };
}
```

**Output (step.js bundle):**

```typescript theme={null}
import { registerStepFunction } from "workflow/internal/private";

export async function createUser(email: string) {
  return { id: crypto.randomUUID(), email };
}

registerStepFunction("step//workflows/user.js//createUser", createUser);
```

**What happens:**

* The `"use step"` directive is removed
* The function body remains intact (no transformation)
* The function is registered with the runtime using a stable ID
* Step functions run with full Node.js access

### Workflow Mode Transformation

**Input:**

```typescript theme={null}
export async function createUser(email: string) {
  "use step";
  return { id: crypto.randomUUID(), email };
}

export async function handleUserSignup(email: string) {
  "use workflow";
  const user = await createUser(email);
  return { userId: user.id };
}
```

**Output (flow\.js bundle):**

```typescript theme={null}
export async function createUser(email: string) {
  return globalThis[Symbol.for("WORKFLOW_USE_STEP")](
    "step//workflows/user.js//createUser"
  )(email);
}

export async function handleUserSignup(email: string) {
  const user = await createUser(email);
  return { userId: user.id };
}
handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup";
```

**What happens:**

* Step function bodies are **replaced** with calls to `globalThis[Symbol.for("WORKFLOW_USE_STEP")]`
* Workflow function bodies remain **intact**—they execute deterministically during replay
* The `WORKFLOW_USE_STEP` symbol is a special runtime hook that:
  1. Checks if the step has already been executed (in the event log)
  2. If yes: Returns the cached result
  3. If no: Triggers a suspension and enqueues the step for background execution

### Client Mode Transformation

**Input:**

```typescript theme={null}
export async function handleUserSignup(email: string) {
  "use workflow";
  const user = await createUser(email);
  return { userId: user.id };
}
```

**Output (your application code):**

```typescript theme={null}
export async function handleUserSignup(email: string) {
  throw new Error("You attempted to execute a workflow directly...");
}
handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup";
```

**What happens:**

* Workflow function bodies are **replaced** with an error throw
* The `workflowId` property is added (same as workflow mode)
* This prevents accidental direct execution while allowing `start()` to identify which workflow to launch

## The useStep Implementation

The `createUseStep` function in `step.ts` shows how step calls work during workflow replay:

```typescript theme={null}
export function createUseStep(ctx: WorkflowOrchestratorContext) {
  return function useStep<Args extends Serializable[], Result>(
    stepName: string,
    closureVarsFn?: () => Record<string, Serializable>
  ) {
    const stepFunction = function (...args: Args): Promise<Result> {
      const { promise, resolve, reject } = withResolvers<Result>();
      const correlationId = `step_${ctx.generateUlid()}`;

      // Add to invocation queue
      ctx.invocationsQueue.set(correlationId, {
        type: 'step',
        correlationId,
        stepName,
        args,
      });

      // Subscribe to events for this step
      ctx.eventsConsumer.subscribe((event) => {
        if (event?.correlationId !== correlationId) {
          return EventConsumerResult.NotConsumed;
        }

        if (event.eventType === 'step_completed') {
          // Remove from queue and resolve with cached result
          ctx.invocationsQueue.delete(correlationId);
          resolve(hydrateStepReturnValue(event.eventData.result));
          return EventConsumerResult.Finished;
        }

        if (event.eventType === 'step_failed') {
          // Remove from queue and reject with error
          ctx.invocationsQueue.delete(correlationId);
          reject(new FatalError(event.eventData.error.message));
          return EventConsumerResult.Finished;
        }

        return EventConsumerResult.Consumed;
      });

      return promise;
    };

    return stepFunction;
  };
}
```

This implementation shows the key mechanism:

1. **Queue the step invocation** with a unique correlation ID
2. **Subscribe to the event log** looking for matching step events
3. **If step\_completed found**: Resolve immediately with cached result
4. **If no matching event**: The promise never resolves, causing a `WorkflowSuspension` to be thrown
5. **The suspension handler** enqueues the step for actual execution

## Determinism and the VM Sandbox

Workflow functions execute in a Node.js VM context with restricted access to non-deterministic APIs. From `workflow.ts`:

```typescript theme={null}
const { context, globalThis: vmGlobalThis, updateTimestamp } = createContext({
  seed: workflowRun.runId,
  fixedTimestamp: +startedAt,
});

// Override non-deterministic APIs
vmGlobalThis.fetch = () => {
  throw new vmGlobalThis.Error(
    'Global "fetch" is unavailable in workflow functions. Use the "fetch" step function from "workflow".'
  );
};

vmGlobalThis.setTimeout = () => {
  throw new WorkflowRuntimeError(
    'Timeout functions are not supported in workflow functions. Use the "sleep" function from "workflow".'
  );
};
```

The VM provides:

* **Seeded random**: `Math.random()` is deterministic based on the run ID
* **Fixed timestamp**: `Date.now()` returns a consistent value during replay
* **Blocked APIs**: Direct access to `fetch`, `setTimeout`, file I/O, etc. is prevented

This ensures that replaying a workflow with the same step results always produces the same execution path.

## Why Not Other Approaches?

We explored several alternatives before settling on directives:

### Generator-Based API

```typescript theme={null}
export const myWorkflow = workflow(function*() {
  const message = yield* run(() => step());
  return `${message}!`;
});
```

**Problems:**

* Unfamiliar syntax for most JavaScript developers
* Can't use Promise.all directly
* Still no compile-time sandboxing

### File System Conventions

```plaintext theme={null}
workflows/
  onboarding.ts
steps/
  send-email.ts
```

**Problems:**

* Too opinionated for diverse ecosystems
* Doesn't support publishable npm packages
* Migration requires restructuring projects

### Decorators

```typescript theme={null}
class MyWorkflow {
  @workflow()
  static async processOrder(orderId: string) {
    // ...
  }
}
```

**Problems:**

* Not yet TC39 standard
* Requires class boilerplate
* Makes workflows look like regular runtime code when they're compile-time declarations

## What Directives Enable

Because `"use workflow"` defines a compile-time semantic boundary, we can provide:

* **Build-Time Validation**: The compiler catches invalid patterns before deployment
* **Static Analysis**: Generate UML/DAG diagrams without executing code
* **Durable Execution**: Workflows can safely suspend and resume
* **Future Optimizations**: Smaller serialized state, smarter scheduling based on workflow structure

## Closing Thoughts

Directives aren't about syntax preference—they're about expressing semantic boundaries. `"use workflow"` tells the compiler, developer, and runtime that this code is deterministic, resumable, and sandboxed.

This clarity enables the Workflow Development Kit to provide durable execution with familiar JavaScript patterns, while maintaining the compile-time guarantees necessary for reliable workflow orchestration.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.