itechmeat

cloudflare-workflows

1
0
# Install this skill:
npx skills add itechmeat/llm-code --skill "cloudflare-workflows"

Install specific skill from multi-skill repository

# Description

Cloudflare Workflows durable execution playbook: multi-step orchestration, state persistence, retries, sleep/scheduling, waitForEvent, external events, bindings, lifecycle management, limits, pricing. Keywords: Cloudflare Workflows, durable execution, WorkflowEntrypoint, step.do, step.sleep, waitForEvent, sendEvent, retries, NonRetryableError, Workflow binding.

# SKILL.md


name: cloudflare-workflows
description: "Cloudflare Workflows durable execution playbook: multi-step orchestration, state persistence, retries, sleep/scheduling, waitForEvent, external events, bindings, lifecycle management, limits, pricing. Keywords: Cloudflare Workflows, durable execution, WorkflowEntrypoint, step.do, step.sleep, waitForEvent, sendEvent, retries, NonRetryableError, Workflow binding."


Cloudflare Workflows

Workflows provide durable multi-step execution for Workers. Steps persist state, survive restarts, support retries, and can sleep for days.


Quick Start

Create Workflow

// src/index.ts
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from "cloudflare:workers";

interface Env {
  MY_WORKFLOW: Workflow;
}

interface Params {
  userId: string;
  action: string;
}

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const user = await step.do("fetch user", async () => {
      const resp = await fetch(`https://api.example.com/users/${event.payload.userId}`);
      return resp.json();
    });

    await step.sleep("wait before processing", "1 hour");

    const result = await step.do("process action", async () => {
      return { processed: true, user: user.id };
    });

    return result; // Available in instance.status().output
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const instance = await env.MY_WORKFLOW.create({
      params: { userId: "123", action: "activate" },
    });
    return Response.json({ instanceId: instance.id });
  },
};

wrangler.jsonc

{
  "name": "my-workflow-worker",
  "main": "src/index.ts",
  "workflows": [
    {
      "name": "my-workflow",
      "binding": "MY_WORKFLOW",
      "class_name": "MyWorkflow"
    }
  ]
}

Deploy

npx wrangler deploy

Core Concepts

WorkflowEntrypoint

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    // Workflow logic with steps
    return optionalResult;
  }
}

WorkflowEvent

interface WorkflowEvent<T> {
  payload: Readonly<T>; // Immutable input params
  timestamp: Date; // Creation time
  instanceId: string; // Unique instance ID
}

Warning: Event payload is immutable. Changes are NOT persisted across steps. Return state from steps instead.

WorkflowStep

interface WorkflowStep {
  do<T>(name: string, callback: () => Promise<T>): Promise<T>;
  do<T>(name: string, config: StepConfig, callback: () => Promise<T>): Promise<T>;
  sleep(name: string, duration: Duration): Promise<void>;
  sleepUntil(name: string, timestamp: Date | number): Promise<void>;
  waitForEvent<T>(name: string, options: WaitOptions): Promise<T>;
}

See api.md for full type definitions.


Steps

Basic Step

const result = await step.do("step name", async () => {
  const response = await fetch("https://api.example.com/data");
  return response.json(); // State persisted
});

Step with Config

const data = await step.do(
  "call external API",
  {
    retries: {
      limit: 10,
      delay: "30 seconds",
      backoff: "exponential",
    },
    timeout: "5 minutes",
  },
  async () => {
    return await externalApiCall();
  }
);

Default Step Config

const defaultConfig = {
  retries: {
    limit: 5,
    delay: 10000, // 10 seconds
    backoff: "exponential",
  },
  timeout: "10 minutes",
};
Option Type Default Description
retries.limit number 5 Max attempts (use Infinity for unlimited)
retries.delay string/number 10000 Delay between retries
retries.backoff string exponential constant, linear, exponential
timeout string/number 10 min Per-attempt timeout

Sleep & Scheduling

Relative Sleep

await step.sleep("wait before retry", "1 hour");
await step.sleep("short pause", 5000); // 5 seconds (ms)

Duration units: second, minute, hour, day, week, month, year.

Sleep Until Date

const targetDate = new Date("2024-12-31T00:00:00Z");
await step.sleepUntil("wait until new year", targetDate);

// Or with timestamp
await step.sleepUntil("wait until launch", Date.parse("24 Oct 2024 13:00:00 UTC"));

Maximum sleep: 365 days.

Note: step.sleep and step.sleepUntil do NOT count towards the 1024 steps limit.


Wait for Events

Wait in Workflow

const approval = await step.waitForEvent<{ approved: boolean }>("wait for approval", {
  type: "user_approval",
  timeout: "7 days",
});

if (approval.approved) {
  await step.do("proceed", async () => {
    /* ... */
  });
}

Default timeout: 24 hours.

Timeout behavior: Throws error and fails instance. Use try-catch to continue:

try {
  const event = await step.waitForEvent("optional event", { type: "update", timeout: "1 hour" });
} catch (e) {
  // Continue without event
}

Send Event from Worker

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { instanceId, approved } = await request.json();
    const instance = await env.MY_WORKFLOW.get(instanceId);

    await instance.sendEvent({
      type: "user_approval", // Must match waitForEvent type
      payload: { approved },
    });

    return new Response("Event sent");
  },
};

Event buffering: Events can be sent before Workflow reaches waitForEvent. They are buffered and delivered when the matching step executes.

See events.md for REST API.


Error Handling

NonRetryableError

import { NonRetryableError } from "cloudflare:workflows";

await step.do("validate input", async () => {
  if (!event.payload.data) {
    throw new NonRetryableError("Missing required data");
  }
  return event.payload.data;
});

Catch and Continue

try {
  await step.do("risky operation", async () => {
    await riskyApiCall();
  });
} catch (error) {
  await step.do("handle failure", async () => {
    await sendAlertEmail(error.message);
  });
}

Workflow States

Status Description
queued Waiting to start
running Actively executing
paused Manually paused
waiting Sleeping or waiting for event
waitingForPause Pause requested, waiting to take effect
complete Successfully finished
errored Failed (uncaught exception or retry limit)
terminated Manually terminated

Workflow Bindings

Create Instance

const instance = await env.MY_WORKFLOW.create({
  id: "order-12345", // Optional custom ID (max 100 chars)
  params: { orderId: 12345 },
});
console.log(instance.id);

Create Batch

const instances = await env.MY_WORKFLOW.createBatch([{ params: { userId: "1" } }, { params: { userId: "2" } }, { params: { userId: "3" } }]);
// Up to 100 instances per batch

Get Instance

const instance = await env.MY_WORKFLOW.get("order-12345");

Instance Methods

await instance.pause(); // Pause execution
await instance.resume(); // Resume paused
await instance.terminate(); // Stop permanently
await instance.restart(); // Restart from beginning

const status = await instance.status();
console.log(status.status); // "running", "complete", etc.
console.log(status.output); // Return value from run()
console.log(status.error); // { name, message } if errored

await instance.sendEvent({
  type: "approval",
  payload: { approved: true },
});

Rules of Workflows

โœ… DO

  • Make steps granular and self-contained
  • Return state from steps (only way to persist)
  • Name steps deterministically
  • await all step calls
  • Keep step return values under 1 MiB
  • Use idempotent operations in steps
  • Base conditions on event.payload or step returns

โŒ DON'T

  • Store state outside steps (lost on restart)
  • Mutate event.payload (changes not persisted)
  • Use non-deterministic step names (Date.now(), Math.random())
  • Skip await on step calls
  • Put entire logic in one step
  • Call multiple unrelated services in one step
  • Do heavy CPU work in single step

Step State Persistence

// โœ… Good: Return state from step
const userData = await step.do("fetch user", async () => {
  return await fetchUser(userId);
});

// โŒ Bad: State stored outside step (lost on restart)
let userData;
await step.do("fetch user", async () => {
  userData = await fetchUser(userId); // Will be lost!
});

Wrangler Commands

# List instances
wrangler workflows instances list my-workflow

# Describe instance
wrangler workflows instances describe my-workflow --id <instance-id>

# Terminate instance
wrangler workflows instances terminate my-workflow --id <instance-id>

# Trigger new instance
wrangler workflows trigger my-workflow --params '{"key": "value"}'

# Delete Workflow
wrangler workflows delete my-workflow

Limits

Feature Free Paid
CPU time per step 10 ms 30 sec (max 5 min)
Wall clock per step Unlimited Unlimited
State per step 1 MiB 1 MiB
Event payload 1 MiB 1 MiB
Total state per instance 100 MB 1 GB
Max sleep duration 365 days 365 days
Max steps per Workflow 1024 1024
Concurrent instances 25 10,000
Instance creation rate 100/sec 100/sec
Queued instances 100,000 1,000,000
Subrequests per instance 50/req 1000/req
Instance ID length 100 chars 100 chars
Retention (completed) 3 days 30 days

Note: Instances in waiting state (sleeping, waiting for event) do NOT count against concurrency limits.

Increase CPU Limit

{
  "limits": {
    "cpu_ms": 300000 // 5 minutes
  }
}

Pricing

Based on Workers Standard pricing:

Metric Free Paid
Requests 100K/day (shared) 10M/mo included, +$0.30/M
CPU time 10 ms/invocation 30M ms/mo included, +$0.02/M ms
Storage 1 GB 1 GB included, +$0.20/GB-mo

Storage notes:

  • Calculated across all instances (running, sleeping, completed)
  • Deleting instances frees storage (updates within minutes)
  • Free plan: instance errors if storage limit reached

See pricing.md for details.


Prohibitions

  • โŒ Do not mutate event.payload (changes not persisted)
  • โŒ Do not store state outside steps
  • โŒ Do not use non-deterministic step names
  • โŒ Do not exceed 1 MiB per step return
  • โŒ Do not skip await on step calls
  • โŒ Do not rely on in-memory state between steps

References

Cross-References (Skills)

  • cloudflare-workers โ€” Worker development
  • cloudflare-queues โ€” Message queue integration
  • cloudflare-r2 โ€” Large state storage
  • cloudflare-kv โ€” Key-value references

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.