---
name: openworkflow
description: Use when building durable workflows that survive crashes, need to
  retry failed steps, schedule delayed tasks, or coordinate multi-step
  processes. Reach for this skill when implementing background jobs, data
  pipelines, approval workflows, or any process that must persist progress to a
  database.
metadata:
  mintlify-proj: openworkflow
  version: "1.0"
---

# OpenWorkflow Skill

## Product Summary

OpenWorkflow is a durable workflow engine that runs in your infrastructure. You write workflows in TypeScript/JavaScript, wrap operations in steps (which act as durable checkpoints), and workers execute them against PostgreSQL or SQLite. When a worker crashes, another picks up from the last completed step—no work is repeated. Use it for background jobs, multi-step integrations, data pipelines, and AI workflows that need to keep their progress.

**Key files & commands:**
- `openworkflow.config.ts` — configuration file (backend, workflow directories, worker concurrency)
- `npx @openworkflow/cli init` — initialize a project
- `npx @openworkflow/cli worker start` — run a worker process
- `npx @openworkflow/cli dashboard` — view workflow runs and step history
- `npx @openworkflow/cli doctor` — verify config and backend connection
- Workflows live in `openworkflow/` directory by default (configurable)

**Primary docs:** https://openworkflow.dev

---

## When to Use

Reach for this skill when:

- **Building durable workflows** — processes that must survive crashes and resume from the last completed step
- **Implementing retries** — automatically retry failed steps with exponential backoff without repeating completed work
- **Scheduling delayed tasks** — pause workflows for seconds to months without holding worker capacity (e.g., send follow-up email in 7 days)
- **Coordinating multi-step processes** — order processing, user onboarding, data pipelines, approval workflows
- **Running background jobs** — async work triggered from API routes, webhooks, or cron jobs
- **Waiting for external events** — pause workflows until a signal arrives (webhook callback, human approval, payment confirmation)
- **Composing workflows** — run child workflows from parent workflows and wait for results
- **Parallel operations** — run independent steps concurrently with full durability for each

Do not use for:
- Synchronous request-response operations (use your web framework instead)
- Operations that must complete within a single HTTP request
- Simple one-off tasks without retry or durability requirements

---

## Quick Reference

### CLI Commands

| Command | Purpose |
|---------|---------|
| `npx @openworkflow/cli init --backend sqlite` | Initialize project with SQLite backend |
| `npx @openworkflow/cli init --backend postgres --yes` | Unattended setup with PostgreSQL |
| `npx @openworkflow/cli worker start` | Start a worker process |
| `npx @openworkflow/cli worker start --concurrency 10` | Start worker with 10 concurrent runs |
| `npx @openworkflow/cli dashboard` | Open web dashboard (http://localhost:3000) |
| `npx @openworkflow/cli dashboard --port 4000` | Dashboard on custom port |
| `npx @openworkflow/cli doctor` | Verify config, backend, and workflows |

### Workflow Definition Pattern

```ts
import { defineWorkflow } from "openworkflow";

export const myWorkflow = defineWorkflow(
  { name: "my-workflow" },  // Required: unique name
  async ({ input, step, version, run }) => {
    // input: data passed when starting workflow
    // step: API for defining steps
    // version: workflow version if specified
    // run: read-only metadata (run.id, etc.)
    
    const result = await step.run({ name: "step-name" }, async () => {
      // Your operation here
      return data;
    });
    
    return { /* workflow output */ };
  }
);
```

### Step Types

| Step Type | Purpose | Example |
|-----------|---------|---------|
| `step.run()` | Execute code and memoize result | `await step.run({ name: "fetch-user" }, async () => db.users.find(id))` |
| `step.sleep()` | Pause workflow durably | `await step.sleep("wait-7d", "7d")` |
| `step.runWorkflow()` | Start child workflow and wait | `await step.runWorkflow(childSpec, input)` |
| `step.sendSignal()` | Send signal to waiting workflows | `await step.sendSignal({ signal: "approval:123", data: {...} })` |
| `step.waitForSignal()` | Pause until signal arrives | `await step.waitForSignal({ signal: "approval:123", timeout: "7d" })` |

### Configuration Essentials

```ts
// openworkflow.config.ts
import { defineConfig } from "@openworkflow/cli";
import { BackendPostgres } from "openworkflow/postgres";
import { BackendSqlite } from "openworkflow/sqlite";

export default defineConfig({
  // Backend: PostgreSQL for production, SQLite for dev
  backend: await BackendPostgres.connect(process.env.OPENWORKFLOW_POSTGRES_URL!),
  
  // Directories to scan for workflows
  dirs: ["./openworkflow"],
  
  // Ignore patterns (e.g., test files, runners)
  ignorePatterns: ["**/*.test.*", "**/*.run.*"],
  
  // Worker settings
  worker: {
    concurrency: 10,           // Concurrent workflow runs
    leaseDurationMs: 30_000,   // Heartbeat lease duration
  },
});
```

### Duration Format

Used in `step.sleep()`, `step.waitForSignal()`, timeouts, and scheduling:

```ts
"500ms"   // milliseconds
"5s"      // seconds
"2m"      // minutes
"1h"      // hours
"1d"      // days
"1w"      // weeks
"1mo"     // months
"1y"      // years
```

### Workflow Run States

| State | Meaning |
|-------|---------|
| `pending` | Created, waiting for worker to claim |
| `running` | Executing, or durably parked (sleeping/waiting for signal) |
| `completed` | Finished successfully |
| `failed` | Failed after exhausting retries or hitting deadline |
| `canceled` | Explicitly canceled, will not continue |

---

## Decision Guidance

### When to Use SQLite vs PostgreSQL

| Scenario | Use |
|----------|-----|
| Local development, testing, single-server deployment | **SQLite** — no external database needed |
| Production with multiple workers, high throughput, team deployment | **PostgreSQL 14+** — required for coordination |
| Hybrid: dev uses SQLite, production uses PostgreSQL | Use `NODE_ENV` to switch in config |

### When to Use step.run() vs step.sleep() vs step.waitForSignal()

| Use Case | Step Type | Why |
|----------|-----------|-----|
| Call API, query database, send email | `step.run()` | Executes immediately, memoizes result |
| Pause for a known duration (7 days, 1 hour) | `step.sleep()` | Frees worker slot, resumes automatically |
| Wait for external event (approval, webhook, payment) | `step.waitForSignal()` | Pauses until signal sent, with optional timeout |

### When to Use Parallel vs Sequential Steps

| Pattern | Use |
|---------|-----|
| Independent operations (fetch user + fetch orders) | `Promise.all([step.run(...), step.run(...)])` |
| Dependent operations (fetch user, then fetch their orders) | Sequential: `const user = await step.run(...); const orders = await step.run(...)` |
| Want all results even if some fail | `Promise.allSettled([...])` |
| One failure should stop the workflow | `Promise.all([...])` |

### When to Use Child Workflows vs Inline Steps

| Scenario | Use |
|----------|-----|
| Reusable process (payment, shipping, report generation) | **Child workflow** — compose workflows, test independently |
| One-off steps in a single workflow | **Inline steps** — simpler, no extra coordination |
| Need to run same workflow from multiple parents | **Child workflow** — `step.runWorkflow()` |

---

## Workflow

### Typical Task: Build and Deploy a Durable Workflow

1. **Understand the project**
   - Read `openworkflow.config.ts` to find backend type, workflow directories, and worker settings
   - Check `.env` for database URL and namespace
   - Verify `package.json` has `@openworkflow/cli` and `openworkflow` installed

2. **Check existing workflows**
   - Search the configured `dirs` (default: `./openworkflow/`) for existing workflow files
   - Review step names, retry policies, and patterns used
   - Identify reusable workflows for composition

3. **Define your workflow**
   - Create a new file in `openworkflow/` directory (e.g., `openworkflow/my-workflow.ts`)
   - Use `defineWorkflow({ name: "unique-name" }, async ({ input, step }) => {...})`
   - Wrap each operation in `step.run()`, `step.sleep()`, or other step types
   - Export the workflow for use by workers and client code
   - Keep workflow logic deterministic — avoid `Math.random()`, `Date.now()`, API calls outside steps

4. **Enqueue a workflow run**
   - Import the workflow and client: `import { ow } from "./openworkflow/client"`
   - Call `ow.runWorkflow(workflow.spec, input)` from API route, webhook, or script
   - Optionally pass `{ availableAt: "5m" }` to schedule for later
   - Optionally pass `{ idempotencyKey: "..." }` to prevent duplicates

5. **Start a worker**
   - Run `npx @openworkflow/cli worker start` in a separate terminal or background process
   - Worker loads config, discovers workflows, and polls database for runs
   - Leave running while testing or in production

6. **Verify the workflow**
   - Run `npx @openworkflow/cli doctor` to check config and backend connection
   - Trigger a test run manually or via your application
   - Open dashboard: `npx @openworkflow/cli dashboard` → http://localhost:3000
   - Inspect run status, step history, and results
   - Check for errors in step attempts and retry counts

7. **Deploy to production**
   - Set `OPENWORKFLOW_POSTGRES_URL` and `OPENWORKFLOW_NAMESPACE_ID` environment variables
   - Run migrations (automatic on backend connect)
   - Deploy worker as a long-running process (container, systemd, etc.)
   - Run multiple worker instances for high availability
   - Monitor via dashboard or Prometheus metrics

---

## Common Gotchas

- **Non-deterministic workflow logic** — Avoid `Math.random()`, `Date.now()`, or API calls directly in the workflow function body. Wrap them in `step.run()` so results are memoized on replay.

- **Changing step names after workflows are in-flight** — Completed steps won't be found in history, causing them to re-execute. Use [versioning](/docs/versioning) to safely change logic.

- **Signals are not buffered** — If you send a signal before any workflow is waiting for it, the signal is lost. Ensure the waiting workflow is already running.

- **Step results must be JSON-serializable** — Avoid returning functions, circular references, or class instances with methods. Offload large payloads to external storage and return only a reference.

- **Child workflow timeout doesn't cancel the child** — When a parent's `step.runWorkflow()` timeout is reached, the parent step fails but the child keeps running. The child is not automatically canceled.

- **Workflow-level errors don't retry by default** — Only errors thrown inside `step.run()` trigger retries. Errors outside steps fail the workflow unless you set a `retryPolicy` on the workflow spec.

- **Forgetting to start a worker** — Workflow runs stay `pending` if no worker is running. Always start a worker before testing.

- **Database connection issues** — Run `npx @openworkflow/cli doctor` to verify backend connection before troubleshooting workflow failures.

- **Step attempt cap of 1000** — Each workflow run has a hard limit of 1000 total step attempts. If exceeded, the run fails immediately and does not retry.

- **Lease duration too short** — If `leaseDurationMs` is very short, crashed workers are reclaimed quickly but heartbeat writes increase. Start with 30s default.

---

## Verification Checklist

Before submitting workflow code or deploying to production:

- [ ] **Config is valid** — Run `npx @openworkflow/cli doctor` and resolve any errors
- [ ] **Workflow is deterministic** — No `Math.random()`, `Date.now()`, or direct API calls in workflow body; all non-deterministic operations wrapped in steps
- [ ] **Step names are stable** — Step names don't change between code versions (use versioning if logic changes)
- [ ] **Step results are JSON-serializable** — No functions, circular refs, or class instances with methods
- [ ] **Worker is running** — `npx @openworkflow/cli worker start` is active in a separate terminal or background process
- [ ] **Test run completes** — Trigger a test workflow and verify it reaches `completed` status in the dashboard
- [ ] **Retry policy is appropriate** — Check `initialInterval`, `backoffCoefficient`, `maximumInterval`, `maximumAttempts` match your use case
- [ ] **Parallel steps are independent** — Steps in `Promise.all()` don't depend on each other's results
- [ ] **Child workflows are tested** — If using `step.runWorkflow()`, verify child workflow runs independently
- [ ] **Signals have unique names** — Signal names are scoped to namespace and include entity IDs to avoid collisions
- [ ] **Dashboard shows expected history** — Open http://localhost:3000 and inspect step results, errors, and retry attempts
- [ ] **Environment variables are set** — `OPENWORKFLOW_POSTGRES_URL`, `OPENWORKFLOW_NAMESPACE_ID` configured for production
- [ ] **Multiple workers can run** — Verify no single-worker assumptions; test with 2+ worker instances

---

## Resources

**Comprehensive page navigation:** https://openworkflow.dev/llms.txt

**Critical documentation:**
- [Overview](https://openworkflow.dev/docs/overview) — Mental model: workflows, steps, workers, and crash recovery
- [Workflows](https://openworkflow.dev/docs/workflows) — Define workflows, run them, configure retry policies and versioning
- [Steps](https://openworkflow.dev/docs/steps) — Step types, memoization, error handling, and best practices
- [Configuration](https://openworkflow.dev/docs/configuration) — Config file reference, backend setup, worker concurrency
- [CLI Reference](https://openworkflow.dev/docs/cli) — All CLI commands and options
- [Production](https://openworkflow.dev/docs/production) — Deploy workers, scale horizontally, monitor with Prometheus
- [Retries](https://openworkflow.dev/docs/retries) — Automatic retry behavior, retry policies, error handling
- [Sleeping](https://openworkflow.dev/docs/sleeping) — Pause workflows durably with `step.sleep()`
- [Signals](https://openworkflow.dev/docs/signals) — Wait for external events and coordinate workflows
- [Child Workflows](https://openworkflow.dev/docs/child-workflows) — Compose workflows and reuse logic
- [Parallel Steps](https://openworkflow.dev/docs/parallel-steps) — Run independent operations concurrently

---

> For additional documentation and navigation, see: https://openworkflow.dev/llms.txt