Skip to main content
A workflow is one JavaScript file. Maestro runs it in a restricted context: the script sees JavaScript’s built-ins and the functions on this page, and nothing else. process, require, fetch, console and setTimeout are not defined. The script body is async. await and a top-level return are allowed, and the returned value is the result of the run.

The meta block

Every script starts with export const meta = { ... }.
The block must be a pure literal: no variables, function calls, spreads or template strings with expressions. Maestro reads it without running the script. An unknown key, such as a misspelled maxAgent, is ignored and reported in the log when the run starts.

The functions

agent(prompt, opts?)

Starts one agent with a clean context. Agents do not see each other; data moves between them only through your script.
The options are described in Agents and tools.

parallel(thunks)

Takes an array of functions, runs them together and waits for all of them.
Pass functions, not promises: () => agent(...), not agent(...).

pipeline(items, ...stages)

Sends each item through the stages on its own. There is no barrier between stages: one item can be in stage three while another is still in stage one. A stage receives (prev, item, index). In the first stage prev is the item itself.
Use pipeline when each item has its own chain of steps. Use parallel when you need every result before going on.

workflow(nameOrRef, args?)

Runs another workflow as a child and returns its result. The child shares the parent’s concurrency limit, agent count and budget. Nesting goes one level deep: calling workflow() inside a child is an error. The child’s own meta.maxAgents is not applied. A name resolves to .maestro/workflows/<name>.js.

When a call returns null and when it throws

Maestro separates two kinds of failure. Something that went wrong with one agent gives you null, so the rest of the run can go on. A mistake in the script or its configuration throws, so you find out. Inside parallel and pipeline:
  • A thunk or stage that throws because of an agent, the budget or a schema becomes null in its slot, and the failure is logged. In a pipeline the item’s remaining stages are skipped.
  • A programming or configuration error rejects the whole call: ReferenceError, SyntaxError, a non-deterministic call, an unknown tool or phase, a write tool without isolation, a nested workflow, two writers on one branch, or a journal that cannot be written.
  • More than 4096 items in one call rejects.
  • A stage that returns null is a normal value and goes on to the next stage.
Always check for null:

Determinism

A script must do the same thing every time it runs with the same answers, because resume replays it. So these throw inside a script:
  • Date.now()
  • Math.random()
  • new Date() without arguments
If you need a timestamp, pass it in args: new Date(args.ts) works.

Patterns

Find, then verify. Finders propose, skeptics try to refute, and only what survives is kept. The first workflow in Getting started does this. Loop until dry. Ask for new items, give the next agent the list so far, and stop after a round that adds nothing. examples/workflows/find-edge-cases.js does this. Fan out writers. Several isolated agents each change code on their own branch, and one coordinator collects the branches. examples/workflows/fanout-writers.js does this; see Agents and tools. Put limits in code. Do not ask an agent to “stay under 10 agents”. Set maxAgents and a budget.