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, for the result message, either an output_summary steer for the model that writes it or a fixed output_text template.
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. A tool step’s resolved input is validated against the tool’s own input schema before the call. For MCP tools that schema is the one the server advertises now: a provider whose copy is older than a minute is re-listed before the step runs, so a server-side schema change (a new required field, a removed one) is picked up by the next run without a rebuild. See When the server changes a schema. 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. Reading a run back later. run_id is the run’s row in the run log, and it is on every response — success, failure, any version, and in the run-complete / run-error stream events. Store it: GET /v1/workflow-runs/{run_id} returns the run’s status, its output, its text and its request_id (the same value as traceId) for as long as the workflow exists, so a step that runs days later can pass an earlier run’s id as input and recover what it produced. The row is opened before the run starts, so the id is real the moment the response arrives. result.runId is the same value. The run-log endpoint also accepts a traceId in place of run_id, which is the way back into runs made before run_id existed. The stored output is trimmed to 20 000 characters of JSON (WORKFLOW_SCHEDULE_OUTPUT_MAX_CHARS), replaced past that by { truncated, total_chars, preview }.

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. The message posted to a result thread after a successful run is written after the flow by a model that reads the input, every step’s output and the final output and writes the answer a person wants to read, rendering rich UI components — a chart, a table, KPI cards, the weather widget — where they present the result better than prose. For a user workflow that writer is the agent that built it; for an organization workflow it is the built-in result summary. It is not a step: the run’s result is untouched, and the user sees only the message. output_summary steers it — instructions say what to lead with or chart, model_slug picks the summary’s model, enabled: false turns it off for a plain completion notice with the output attached as JSON. output_text replaces the summary with a fixed template over $.input and $.steps, up to 4,000 characters, which may contain <component id="<stepId>"/> anchors for rich-UI steps. Use it when the message must be exact and unchanging. Set either through the API or the draft editor.

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.