Skip to content

Workflow

Work that runs for days and never loses its place: a Workflow is durable code, a state machine, or both. It is the body of a long Verb, and each moment your Customer sees is a state.

kickoff: durable code
15 lines
export default define({
  …
  Deal: {
    …
    kickoff: workflow(async ($, deal) => {
      const { plan } = await $.generative('Draft the kickoff plan for {company.name}', deal,
        { returns: { plan: 'string' } })
      await $.human('Approve the plan', { plan }, { role: 'manager' })
      const at = await $.step('stamp it', () => new Date().toISOString())
      return { plan, at }
    }),
  },
  on: { 'Deal.won': 'Deal.kickoff' },           // Trigger: what reacts to a change
  …
})
kickoff: the machine

Durable code

A won Deal starts the kickoff: a model drafts the plan, a manager approves it, and the run keeps the time it was approved. The on line is its Trigger.

A Verb the on line names starts when its Event happens, once per Event, with no call.

Each $ call is a step: it runs once, and its result is kept. A failed step retries until its limit runs out, and a run survives restarts and waits of weeks.

What $ gives a run
What $ gives a run
CallWhat it does
$.step(name, fn)Runs fn once and keeps its result: a replay reads the result and never runs it again.
$.code(fn, input)Runs a code Function as a step.
$.generative(prompt, input, { returns })Asks a model, and answers the fields returns names.
$.agentic(prompt, { tools, returns })Gives an agent the job and the Sources it may use; it answers when the job is done.
$.human(task, input, { role })Waits for a member who holds the Role to complete the task at /actions/{id}/complete.
$.wait('14d'), $.wait({ event, where })Waits for a duration, or for the first Event that matches where.
$.call('Company.get', input)Calls a Verb of this API, or of a Source that is an API product, and answers its reply.
return outputEnds the run: the value is the Action's output.

Durations read '2d' or '30m', and a schedule reads 'every Mon, Wed, Fri 09:00', run once per Customer in that Customer's timezone. While the manager has not approved, the run is Pending, and its reason says who holds the ball.

Request
curl https://pipeline.apis.dev/actions/act_8 \
  -H "Authorization: Bearer $PIPELINE_KEY"
Response
HTTP/1.1 200 OK
X-Cost: price=0, currency="USD"
X-Request-Id: req_1154ca02bc01e341ac7b

{
  "id": "act_8",
  "verb": "Deal.kickoff",
  "status": "pending",
  "reason": "AwaitingWorker",
  "poll": "/actions/act_8"
}
Why an Action waits
Why an Action waits
ReasonWhat causes it
AwaitingWorkerA $.human step waits for a member who holds its Role.
AwaitingInputA machine waits in a state for an event it accepts.
AwaitingTimeA $.wait duration or an after timer is running.
AwaitingSourceA Source has not answered yet, or a $.wait waits for its Event.
AwaitingActionA $.call waits for another Action to finish.
RetryingA step failed and runs again after its backoff, until its limit runs out.
RunningA step or a Function is working now.

State machine

Same job, other style: write code when the work is a list of steps, and a machine when it waits on people and the clock. A machine names its states, the events each one takes and its timers.

kickoff: a state machine
21 lines
export default define({
  …
  Deal: {
    …
    kickoff: machine({
      initial: 'reviewing',
      states: {
        reviewing: {                      // the draft runs on the way in
          entry: generative('Draft the kickoff plan for {company.name}',
            { returns: { plan: 'string' } }),
          on: { approve: { target: 'approved', role: 'manager' } },
          after: { '2d': 'escalated' },
        },
        escalated: { on: { approve: { target: 'approved', role: 'manager' } } },
        approved:  { type: 'final' },
      },
    }),
  },
  on: { 'Deal.won': 'Deal.kickoff' },           // Trigger: what reacts to a change
  …
})

A machine's events are Verbs on its Noun, each sent by the Role it names; it fires after on its timer, and resumes where it left off.

The machine's state is the Action's state, and accepts lists the events it takes. approve is a Verb on Deal: POST /deals/deal_1/approve, approveDeal and pipeline deals approve.

Request
curl https://pipeline.apis.dev/actions/act_9 \
  -H "Authorization: Bearer $PIPELINE_KEY"
Response
HTTP/1.1 200 OK
X-Cost: price=0, currency="USD"
X-Request-Id: req_e51d811890c4fe07c5e4

{
  "id": "act_9",
  "verb": "Deal.kickoff",
  "status": "pending",
  "reason": "AwaitingInput",
  "state": "reviewing",
  "accepts": ["approve"],
  "poll": "/actions/act_9"
}

An event the current state does not take answers 409, and a member without its Role answers 403.

When a Verb is internal

A Verb that an on line starts and that names no Role is internal: it serves no path, tool or command. While it runs, its Thing is in its activity state, such as drafting.

Request
curl https://filings.apis.dev/filings/filing_1 \
  -H "Authorization: Bearer $FILINGS_KEY"
Response
HTTP/1.1 200 OK
X-Cost: price=0, currency="USD"
X-Request-Id: req_f781a6ad9678c1e5146d

{
  "id": "filing_1",
  "company": "acme.com",
  "year": 2026,
  "status": "drafting",
  "createdAt": "2026-10-24T09:00:00Z",
  "updatedAt": "2026-10-24T09:00:01Z"
}

What you never write

A read, a create, an update, a delete and a lifecycle move answer the record, and a Function or a Workflow answers its Action. Durable runs survive restarts and long waits, and each run stays on the Version it started on.

What you never write
What you never writeWhat does it instead
Code that starts a runThe on line, or a call to the Verb
A route that takes an eventA Verb on the Noun, which the machine accepts
A check for a wrong moveThe lifecycle, which answers 409 before anything runs
A status routeThe Action at /actions/{id}, with its reason while it waits
A history tableThe change feed: every change as an Event, in order

Seen is a state and unseen is a step: the drafted plan is a step, and reviewing, escalated and approved are states.