Core concepts

The vocabulary, the run lifecycle, and flowwork's durability guarantees — in one place.

A short, shared vocabulary. The guides build on these terms.

Glossary

TermWhat it is
WorkflowThe template — one workflow({ … }) definition with triggers and steps.
RunOne execution of a workflow (also called an instance). Identified by an execution id.
StepOne node in a workflow. A step, form, signal, webhook, connector, or agent.
HandlerThe JavaScript function a step runs; returns the variable updates to merge.
ctx.varsThe run’s mutable variable bag — seeded from initial + start input, carried across steps.
TriggerHow a run starts: apiTrigger, formTrigger, webhookTrigger, or scheduleTrigger.
Park / resumeA run parks when it needs external input, resumes when the event arrives.
Signal / form / webhookThe three ways an external event resumes a parked run.
ConnectorA durable call out to a service (HTTP, LLM, MCP).
AgentA call into the driving Claude Code session to run a prompt and return a typed result.
QueryA read-only view over a run’s variables; never advances the run.
Event / narrativeThe append-only audit log, and the human-readable progress text on top of it.
WorkspaceThe tenancy unit that owns definitions, runs, and connector credentials.

The run lifecycle

            ┌──────────── start ────────────┐
            ▼                                │
        RUNNING ──claim──▶ IN_FLIGHT ──step ok, more work──▶ RUNNING
            ▲                  │
            │                  ├─ needs input ─▶ WAITING_FORM / WAITING_SIGNAL /
   event resumes               │                 WAITING_WEBHOOK / WAITING_CONNECTOR_JOB /
            │                  │                 WAITING_AGENT          (parked — zero CPU)
            └──────────────────┘
                               ├─ step throws + retryPolicy ─▶ RETRYING ─▶ RUNNING
                               ├─ step throws + onError ──────▶ RUNNING (at the handler step)
                               └─ no work left ───────────────▶ COMPLETED   (terminal)
                                                                FAILED      (terminal)
                                                                CANCELLED   (terminal)

You’ll mostly see these in flowctl run status: RUNNING (executing or ready), WAITING_… (parked on input), COMPLETED / FAILED / CANCELLED (done).

Durability guarantees

flowwork advances a run’s state exactly once — each step’s commit is transactional and guarded, and a crashed worker’s run is automatically reclaimed and continued. But a step’s side effects (an HTTP POST, an email, an LLM call) can run more than once if a worker dies after the side effect but before the commit.

Make side effects idempotent

Because side effects are at-least-once, design connector calls and external actions so that running them twice is safe — use idempotency keys, upserts, or “create if absent” semantics. This is the single most important rule for correct workflows.

A few consequences worth internalizing:

  • Parked runs cost nothing. A WAITING_* run holds no thread and no timer; it’s just a row.
  • Handlers should be deterministic-ish. Put external I/O in connector() steps, not in plain step handlers, so the engine can retry and journal them properly.
  • Variables are the memory. Everything a later step needs must live in ctx.vars (it’s persisted); don’t rely on closures or module globals across steps.

Next: Workflow structure & steps.