Skip to main content
An agent decides what to do next on every turn. A workflow does not: it runs the same steps in the same order every time, and the only thing a model decides is the content of the steps you gave it. That is the whole reason to reach for one.

When a workflow beats an agent

  • The sequence must not vary. Fetch, compute, write — always in that order, never skipping the check in the middle.
  • The computation must be exact. Sums, groupings and date arithmetic belong in code, not in a model’s head.
  • Nobody is there to steer. Scheduled and unattended runs need a deterministic path; see User workflows and schedules for the end-user variant.
When judgement is the point — an open question, an unknown number of tool calls — keep it in an agent, and hand the agent the workflow as a tool for the parts that must not vary. See Giving an agent a workflow.

The shape of a definition

A workflow is a JSON document with three parts: steps, a map of named steps; flow, the ordered list of operators that run them; and an optional output_text template for the result message.
GET /v1/workflows/definition-schema returns the full JSON Schema, with a definition per step kind, if you generate or validate definitions in your own tooling.

Step kinds

Agent steps are self-contained. The model, its settings (temperature, reasoning effort) and its instructions live on the step. An agent step does not reference one of your saved agents, so editing an agent can never change what a workflow does, and none of a saved agent’s memory, tools or sub-agents apply. Give it an output_schema and the step returns the parsed object directly; without one it returns { text, object, raw }. Tool steps reference tools by stable id, not by name — a renamed tool or provider keeps working. A tool step can carry a loop block to walk every page, fan out over a list of values and project fields, exactly as an agent with loop_responses would; see Working with large APIs. Code steps run in a worker with a blank environment: standard ECMAScript globals plus console, and nothing else — no fetch, require, process, filesystem or timers. Use a tool step to reach anything outside. The code receives its input mapping as input, may use await, and must return a JSON-serialisable value, which becomes $.steps.<id> with no envelope. The default timeout is five seconds, the ceiling thirty. In the Studio, the keys of every return { … } literal feed the reference autocomplete for later steps, so a step that returns { sum, count } offers $.steps.total.sum to whatever comes next.

Flow operators

flow is a list of operators. Each references steps by id. Conditions use a small expression language — eq, ne, gt, gte, lt, lte, truthy, not, and, or — over the same references as everything else, so a branch can gate on $.steps.fetch._loop.complete or on a code step’s result. Control-flow positions reference a single step. To nest a sequence inside a branch or a loop, put the sequence in its own workflow and reference it with a workflow step.

Parallel steps

In the Studio a parallel operator is a gate node: the previous operator attaches to the gate, members fan out from it, and the next operator fans in from all of them. Fill it three ways — drop a step from the palette onto the gate, connect an existing step node to it, or add members from the gate’s config panel. Later steps read each member’s result at $.steps.<name> as usual.

Wiring data

Every reference is a path rooted at $: Index arrays with numeric brackets — $.steps.fetch.items[0].id. In a step’s input, mapping or condition, write a reference as { "ref": "$.…" }; in an agent step’s prompt and in output_text, embed it as {{ $.… }}. Non-string values render as JSON and an unresolved reference renders as an empty string. In the Studio, references are picked from a tree rather than typed, and display without the $. prefix (steps.fetch.items[0].id); the prefix is added back when the draft is saved. Renaming a step rewrites every reference to it — in inputs, conditions and prompt templates — so ids are safe to change as a workflow grows. Publishing checks the wiring: a reference to a step that runs later, or to a step that does not exist, is rejected with the offending path named.

Running one

There are three ways:
  1. From the Studio. The run panel renders a form from input_schema (or a JSON editor when there is none) and streams the run onto the canvas — each node shows its status live. When a step misbehaves, the first thing to open is Input from last run under that step’s configuration, which shows exactly what the step received.
  2. From the API. POST /v1/workflows/{id}/run with input. Add "stream": true for server-sent events — one event per step start and result, ending in run-complete or run-error — or "notify_thread": true to file the result as a conversation the user can read and reply to. Select a version with ?version=active|draft|v3.
  3. As a tool an agent calls. See Giving an agent a workflow.
text is the rendered output_text. A run of the draft is validated first and rejected with the issues listed if the draft is not runnable; a failed run answers 500 with the step error.

Lifecycle

Workflows use the same draft → publish → activate model as agents. Saving a draft only checks structure, so half-built graphs save fine. Publishing runs the full validation — unknown or forward references, orphan steps, cycles, code that does not compile — and then does a trial build without running anything. Activating picks which published version serves. See Versions, environments and deploying. output_text is the message posted after a successful run: a template over $.input and $.steps, up to 4,000 characters, which may contain <component id="<stepId>"/> anchors for rich-UI steps. Without it the result message is a fixed completion notice with the output attached as JSON. Set it through the API or the draft editor; it is what a user reads in a result thread.

Credentials and billing

A run carries the calling user’s stored tool credentials — the same store a token mint or PATCH /v1/users/{userId} writes — and they reach both HTTP and MCP tool steps. There is no fallback to the organization’s credentials: a step whose provider the user has no access to fails, rather than quietly running as someone else. With the organization API key a runtimeAuth map in the request body wins over the store, which is how a server-side integration runs a workflow on a user’s behalf. Every run is one trace, billed to the group named on the request (or the user’s group), gated by the same usage limits as chat, and enriched after the fact with cost and participants — the tools and agent steps that ran. See Traces, cost and billing groups.