Log inGet started

workflow

Updated October 18, 2026

Some jobs are one thing an agent does. Others have a shape: a decision, then several pieces built at once because they do not touch each other, then somebody looking at the result and saying whether it holds. Written as instructions, that shape becomes a list an agent has to carry in its head, and by the last step the first is gone. Written as a workflow, the shape is code — it runs in the engine, it decides its own phases and branches and fan-out, and nothing that answers it is told what comes next.

The shape on disk

<name>.workflow/
  workflow.js     the program — the primary file, and where all the decisions live
  workflow.yaml   what it is, when to reach for it, the phases it moves through
  README.md
  .metadata

The program

workflow.js is JavaScript, run in the engine. Six things are given to it:

phase(name)              name the stage the run is in
log(text)                say something as it goes
agent(prompt, opts)      ask for one agent's whole job; parks until answered
parallel(thunks)         several asks that do not depend on each other
pipeline(items, ...)     each item through every stage, independently
args                     whatever the run was started with

agent() is the only call that leaves the engine. It parks the run, puts the request where whoever holds credentials can see it, and resumes with the answer. Everything else — the loop that built those prompts, the branch on what came back, the decision to fan out — is the program's, and stays in the engine.

opts.schema states the shape the answer must have:

'string' 'number' 'boolean' 'any'   a value of that type
'string[]' or [ shape ]             a list, every item of that shape
{ field: shape, 'note?': shape }    a table with those fields; a name
                                    ending in ? may be left out

workflow.next hands the schema over with the prompt. workflow.answer refuses a value of any other shape, naming the field and the shape expected there, and the request stays open to be answered again. workflow.correct replaces an answer the program has not read yet, held to the same schema; the program reads an answer within a moment of it arriving, and a correction after that is refused as already consumed. Every list the schema names reaches the program as an array, an empty one included. A schema written outside this notation fails the run at the agent() call that carries it.

Running one

local run = asset.resolve("<name>", "workflow"):start({ ... })

or through the workflow toolbox, which is what an agent uses: workflow.start, then workflow.next / workflow.answer until it is finished. Each request a run asks is announced on tool responses once, when it can be taken, and once more if a claim on it runs out; workflow.status lists every request still waiting.

From the engine shell, the same run is driven with zero workflow:

zero workflow start --name [name]
zero workflow status
zero workflow next --runner <who you are>   (claim the request it is waiting on)
zero workflow renew --request <id>          (keep the claim while its work goes on)
zero workflow answer --request <id> --value <result>
zero workflow fail --request <id> --reason "<why>"

next names who is taking the request, and answer, renew and fail name the request they act on by the id next returned.

What a run is

A run lives in the engine, not in a session. Whoever started it can close the tab, lose the connection or hand over to somebody else; the run stays parked exactly where it was, and the next agent to ask gets the request that was waiting. Each request is one subagent's whole job — the point is that no single context has to hold the entire run.

In the Inspector

Workflow: what the workflow is for and when to reach for it, the args it takes, its phases, its step budget when it declares one, and the size of workflow.js, with Open for workflow.yaml and workflow.js. Nothing here runs it.

  • asset-type
  • reference