Workflow structure & steps

The workflow() object, the step builder, the ctx object, handler returns, and control directives.

A workflow is a single .js file with exactly one workflow({ … }) call. Step logic lives in plain standalone functions, referenced positionally by the step builders.

The workflow() object

function validate(ctx) { return { validated: true }; }
function charge(ctx)   { return { charged: true }; }

workflow({
  name: "OrderWorkflow",                  // required — becomes the definition name
  version: "1",                           // optional display label
  description: "process orders",
  initial: { order_id: null, total: 0 },  // starting variables; --input is merged over this
  inputSchema: { /* JSON Schema */ },     // optional; validates trigger input at start
  triggers: [ apiTrigger("create-order") ],   // at least one trigger required
  steps: [
    step("validate", { start: true, next: "charge" }, validate),
    step("charge",   { next: "end" }, charge),
  ],
});

workflow() is called once. Keys it reads: name (required), version, description, initial, inputSchema, outcomeTemplates, inboxTemplates, result, triggers, steps.

Structural rules (enforced at registration):

  • Exactly one step has start: true.
  • Step names are unique.
  • Every next / branch / loop / foreach / parallel target is another step’s name, or the literal "end" to terminate.

The ctx object

Every handler receives ctx:

  • ctx.vars — the mutable variable bag, seeded from initial + the start --input. Mutate it in place (ctx.vars.x = 1) and/or return an object; the returned object is overlaid on ctx.vars after the handler runs. Both styles compose.
  • ctx.error — populated only inside an onError handler, as { message, step_name, exception }. It’s null everywhere else.
function h(ctx)       { return { k: v }; }   // step handler; return = variable updates
function h(ctx, data) { /* … */ }            // form/signal/webhook/agent; data = inbound payload

The step builder

step(name, opts, fn) is the general processing node. The handler fn is the third positional argument and is always required — even for a step that only routes.

step("validate", { start: true, next: "charge", label: "Validate order" }, validate);

Common opts: start, next, label, userVisible, narrative, and the outgoing-flow options branches / loop / foreach / parallel (see Control flow), plus onError / retryPolicy (see Errors & retries). A step uses at most one of next / branches / loop / foreach / parallel.

Every step needs a handler

Omitting the handler fails the run with step “<name>” has no handler function — even pure routers. A no-op function decide(ctx) { return {}; } is fine.

Control directives (action)

Return one of these from a handler to override the declared flow. The global action object is injected into every handler:

return action.transition("other_step");   // jump to a named step instead of `next`
return action.end();                        // end the run now (COMPLETED)
return action.fail("reason");               // mark FAILED — only on a terminal step (see note)

transition and end are returned from the handler and fully control the flow.

action.fail is terminal-only; action.log is a no-op today

action.fail(“reason”) only marks the run FAILED when it’s returned from a terminal step (one whose flow resolves to “end”); on a step that routes onward it’s silently dropped, and the reason string isn’t surfaced yet. To fail from the middle of a flow, route (via onError or a branch) to a dedicated terminal step that calls action.fail(). Separately, action.log() exists but is currently a no-op — it emits and persists nothing.

Registering & versioning

flowctl def create --file order.js --version 2 -q   # register; prints the definition id
flowctl def graph <def> -o json                     # verify nodes/edges before running

version: in the DSL is a display label; the authoritative version is the --version N flag on def create (default 1). Re-registering the same name with a new --version creates a new version and makes it active.

Next: Control flow.