Workflow Builder
The workflow builder is a visual canvas for designing multi-agent pipelines. Add blocks from the palette, connect them with edges, and configure each block in the right-hand panel. Everything runs as a DAG - no cycles allowed.
Canvas overview
The builder has three main areas:
- Canvas - the central workspace where blocks live. Pan with middle-click or trackpad, zoom with scroll wheel.
- Block palette - a popover, not a fixed rail. Hover a block and press its + handle and the palette opens beside that block, grouped by category. Click the block type you want and it is added, already connected to the one you opened it from. Triggers are not in this palette: a workflow gets its trigger when you create it, and you change it from the trigger block itself.
- Config panel - right side. Opens when you select a block. Shows block-specific settings: prompts, model selection, integration credentials, and timeout values.
Block types
Blocks fall into four categories: AI, control flow, integrations, and output.
AI blocks
- Auto Agent - decomposes a goal into Research and Reasoning sub-tasks automatically. See Auto Agent Guide.
- Agent - runs one of your saved agents at a pinned version
- Research - searches your knowledge base and returns findings citing the documents it used
- Reasoning - chain-of-thought analysis
- LLM - direct model call with your own prompt
- Knowledge - retrieves context to ground a downstream block
- Custom A2A - delegates a task to an external A2A agent
- Content Safety - screens text and branches on the verdict
All eight are covered in Agent Blocks.
Control blocks
- Trigger - starting point for the workflow. Eight are available: webhook, schedule, manual, form, chat, Gmail, Slack, and GitHub
- Approval Gate - pauses execution until a human approves or rejects
- Conditional - branches execution based on a condition expression
- Switch - routes execution to one of several branches based on a key
- Loop - runs the blocks after it once per item of an array
- Respond to Webhook - answers the webhook request that started the run
- Set Variable - stores a value under a name for later blocks
- Code - transforms data with a sandboxed JavaScript function
- Merge - combines the outputs of parallel branches into one value
- Transform - reshapes data into named fields, with no model call
- Wait - pauses the run for a duration or until a given time
- Stop and Error - stops the run with a message you write
Wait
Pauses the run, then carries on. Choose For a duration and give a whole number of seconds, minutes, hours, or days, or choose Until a time and give an ISO 8601 timestamp. The timestamp field takes references, so {{trigger.due_at}} works when the time comes in with the trigger. A time already in the past carries straight on.
A waiting run is parked in the database, not held open on a machine. It survives a restart or a deploy, it uses no credits while it waits, and you can cancel it at any point from the run history. The longest a run can wait is 30 days. Wait belongs on the main path of a workflow: inside a parallel branch it stops the run with a message telling you to move it.
Hosted form
The Form trigger gives a workflow its own page to collect input, with no sign-in and nothing to build. Add the fields you want, save the workflow, then share the link from the trigger settings. Each answer arrives as {{trigger.name}}, where name is the field name you set.
Fields can be short text, long text, email, number, a choice from a list, or a tick box, and any of them can be required. Every submission is checked against those fields on the server before a run starts, so an answer that does not fit is refused rather than passed on.
Anyone with the link can submit the form, so treat it as public. Submissions are rate limited per form, and Replace link issues a new URL and stops every copy already shared from working.
Set Variable
Stores a value under a name so any later block can read it, wherever it sits in the graph. Give the block a Variable name and a Value, then reference it downstream as {{vars.name}}. The value field takes references, so you can capture an earlier block's output and reuse it in several places without rewiring.
Operation is Set or Append. Set overwrites what the variable holds. Append adds onto it, which is how you accumulate a result across the iterations of a loop.
Code
Transforms data with a JavaScript function when reshaping output is simpler in code than in a prompt. Write the body in the editor; the Format button tidies it, and the editor autocompletes the references available to you.
The function receives the run so far as input: input.<blockId> for an upstream block's output, plus input.vars and input.trigger. Whatever you return becomes the block's output, referenceable downstream like any other. Returning nothing yields null, and a thrown error fails the run.
The code runs in a sandbox with no network and no filesystem access. It is capped at 5 seconds and 128MB; exceeding either fails the block with a message saying which limit it hit. A Code block uses no credits.
Date.now() and new Date() report the moment the run started, and hold that value for the whole block, so a run stays reproducible and two readings never disagree. Elapsed time cannot be measured inside the block for the same reason.
Merge
Where a workflow fans out, Merge brings it back together. It waits for every incoming branch to finish, then combines their outputs into one value the blocks after it read as {{block.output}}. Branch order follows the order you connected them, not the order they happened to finish, so the same run always produces the same result.
Connect every branch of the split into the Merge. A Merge fed by only some of them would combine whichever of those finished first, so the run is refused by name instead. If you only want part of a split combined, put the Merge after the point where all the branches come back together.
Combine as picks the shape. Append gives a list holding each branch's output. By block gives an object keyed by the branch it came from. Combine merges every branch's fields into one object, with a later branch winning a clash; it needs every branch to produce an object and fails the block by name if one does not.
A branch that produced nothing, such as the arm of a Conditional that did not fire, is left out of the merged value and logged. A Merge block uses no credits.
Transform
Reshapes data between blocks without asking a model to do it. Each row names an output field, a value that takes references like {{block.field}}, and the type the resolved text becomes: text, number, true or false, or JSON. The block builds one object from those rows and passes it downstream.
Because there is no model call, the same inputs always give the same object, it returns immediately, and it uses no credits. A value that will not convert to its declared type fails the block with a message naming the field, rather than passing something wrong downstream. Two rows cannot share a name.
Stop and Error
Ends the run then and there with the Message you write, which takes references so you can quote the value that made you stop. Use it to fail loudly on a bad branch rather than letting a workflow carry on with data it should not trust. The run is marked failed; it cannot be retried past this block or continued.
Report
The Report block gathers the outputs of every block feeding into it and assembles them into one downloadable document, one section per upstream block. Choose Format: Markdown or PDF.
When the run finishes, open the block's output and use the Download button, which shows the format and the file size. Like other output blocks it carries an Error handling section, so you can retry it or route a failure to its error port.
Running a workflow that waits on a trigger
A webhook, schedule or chat workflow does not wait for a real caller before you can see it work. The toolbar control starts it through its own trigger, so what you see is what a real request would produce.
- Webhook - Test sends the body under the Sample button to the workflow's webhook. The sample is filled in for you, and you can replace it with a real request body. It is used for that run only and never saved.
- Schedule - Run now runs the workflow once, immediately. The schedule itself is unchanged, so the next scheduled run still happens when it was going to.
- Chat - Test sends the message under the Sample button to the chat endpoint, exactly as a real message arrives.
A Gmail, Slack, GitHub or form trigger waits on an event we cannot create for you, so its control stays disabled and says why. Fire those from the service they watch, or open the form's own page.
Respond to Webhook
By default a webhook call starts a run and immediately returns an acknowledgement with a run id, so the caller has to poll for the result. Add a Respond to Webhook block and the call waits instead, answering with the body you set. That is what lets a workflow sit inside another system's request, such as a chat backend that answers inline.
Set the body, status code and content type on the block. The body resolves references like {{block.output}}, so you can return whatever an earlier step produced.
What the caller gets back. Reaching the block returns your body and status. A run that finishes without reaching it answers204, which happens when a conditional routes around it. A run that fails answers 500 with the error. A run that pauses at an approval gate answers 202, because it has not finished and may still respond later. A run that outlives the timeout answers 504 and carries on in the background, so you can still poll for it.
A Respond block cannot sit inside a parallel branch, because two branches answering one request has no sensible winner.
Checking a trigger is working
Select a trigger block and its settings show what the trigger is doing. If it has stopped, the reason is shown there and says what to do about it: reconnect the account, choose a connection, upgrade the plan. A trigger saved without a connection says so rather than sitting silent.
Last fired is the last time this trigger started a run. Pressing Run or Test does not count: those are you starting the workflow, not the trigger firing, so a Gmail trigger that stopped a week ago still reads as a week ago however often you test it.
Next fire appears for a schedule and for a Gmail trigger, the two that run on a clock. A webhook, form, chat, manual, GitHub or Slack trigger waits on an event, so there is no next time to show and no row is shown.
Gmail trigger
A Gmail trigger checks the connected mailbox about once a minute and starts one run for each new email matching its search. Pick which emails start a run from the list in the trigger settings: unread mail, mail from a sender, mail carrying a label, or mail with an attachment. Keep it as narrow as you can, because each run uses credits.
If none of those fit, choose A search I write myself and write a Gmail search by hand, the same syntax as the Gmail search box.
Only mail arriving after you switch the trigger on starts a run. Mail already in the mailbox is left alone. If the connected account stops working the trigger pauses itself and shows the reason in its settings.
GitHub trigger
A GitHub trigger starts a run when something happens on one repository. Connect GitHub, choose the repository from the list, and tick the events you want: pushes, pull requests, or issues. Keep at least one ticked, or the trigger has nothing to fire on.
Only repositories you administer appear in the list, because installing a webhook needs admin rights. If the one you want is missing, grant this connection access to it and reopen the trigger.
Slack trigger
A Slack trigger starts a run when someone mentions the app in a channel it has been invited to. Invite it with /invite, then mention it. It does not read every message in the channel.
Integration blocks
- Multiple built-in connectors - see Integrations
- Custom MCP - connect any MCP-compatible tool server
References
Any field that takes a reference reads an earlier value with double braces. Use {{trigger.output}} for what the trigger received, {{trigger.field}} for one field of it, and {{blockId.output}} or {{blockId.field}} for a block that has already run. Whichever trigger starts the run, its payload arrives under the reserved trigger key, so read a form or webhook submission with {{trigger.output}} rather than the trigger block's own id.
A trigger that has one piece of content to hand over gives you that content, so {{trigger.output}} is the message a chat trigger received or the body of an email a Gmail trigger picked up. A webhook or form submission has no single field to pick, so there you get the whole submission and can name a field with {{trigger.field}}.
A reference that names a block of this workflow, or the reserved trigger and vars keys, and cannot be read fails the run and says what it could not find. Braces that name nothing in the workflow are left exactly as you typed them, so text that needs literal double braces still goes through.
Picking a field from the last run
You do not have to remember the path. Open a block and the inspector shows a data pane listing the fields each upstream block actually produced, read from that workflow's most recent run, with the real value beside each one. Click a row and the reference is written at the cursor of whichever field you were last typing in.
The pane says what it does not know rather than guessing, and there are three cases it tells apart:
- The block has never run, so there is no recorded output to read.
- A run was found but its output could not be read, and the pane says why.
- The output was read and contained no fields to offer.
A block whose fields are inferred rather than observed is marked as such on the block, so you can tell a real path from a plausible one. The pane works on every field that takes a reference, and it is available on every plan including Free.
Connecting blocks
The quickest way to join two blocks is the Connector. Hover a block, press its plus button, and choose "Connect to a block" at the top of the palette. A wire follows your cursor and every block you can link to is highlighted; click one to create the connection. For a block that is not yet reaching a trigger, the same entry reads "Connect from a block" so you can pick its upstream source instead. Press Escape or click empty space to cancel.
You can also drag directly from a block's output handle to another block's input handle. Either way the builder validates connections in real time - invalid edges, such as a cycle or a trigger used as a target, are rejected with a message explaining why.
Building without a mouse
The canvas works from the keyboard alone. Tab moves through the blocks, each block's output ports, and its plus button. Every block is one Tab stop and announces its name and type.
- Enter or Space on a block - open its settings, the same panel a click opens
- C on a block - start a connection from it, then Enter on the block it should reach
- Enter on an output port - start the connection from that branch, which is how a Switch case is wired
- Cmd+K, or Enter on a block's plus button - open the block palette, which takes focus in its search field
- Escape - close the palette and hand focus back, or cancel a connection in progress
Parallel fan-out
When a block has multiple outgoing connections, Studio automatically detects the parallel fan-out pattern. Connected downstream blocks execute concurrently. A parallel badge appears on the canvas showing the fan-out degree.
Loops
The Loop block iterates over an array. Point its items source at any upstream output with a reference like {{block.results}}. The blocks between the Loop and the point where its branches rejoin run once per item, in order.
Inside the loop, reference the current item as {{item}} and its position as {{item_index}}. The variable name is configurable in the Loop settings.
After the last item the run continues, and the collected results are available as an array under the Loop block's reference.
A run makes at most 100 iterations. With no Max iterations set, a source of more than 100 items fails the block and says how many it found, rather than running the first hundred and dropping the rest without telling you. Set Max iterations if running part of the source is what you want: the loop then runs that many, and its step records the source size, how many were skipped, and which limit applied.
Keep it simple: loops cannot be nested, a loop body cannot fan out in parallel, and a loop cannot sit inside a parallel branch. Studio reports a clear error for each.
Editing blocks
Select a block and use the standard editing shortcuts. Copy, cut and paste work on the selected blocks and never carry their wires: a paste always drops a detached duplicate, offset slightly from the original, so you can rewire it where you need it. Duplicate is a one-step copy and paste in place. Undo and redo cover every structural change - add, delete, move, connect, reconnect, paste and cut - though not edits you make inside the config panel.
Find in workflow
Press Cmd+F (Ctrl+F on Windows and Linux) to open the find box. Type part of a block name or type to filter; matching blocks are highlighted on the canvas and a counter shows your position, such as "2 of 5". Press Enter for the next match and Shift+Enter for the previous one - the canvas centres on each match as you step through. Press Escape to close the box.
Keyboard shortcuts
On Windows and Linux use Ctrl in place of Cmd. The keypad icon in the toolbar opens the same list inside the builder.
- Cmd+Z - undo
- Cmd+Shift+Z or Cmd+Y - redo
- Cmd+C - copy the selected blocks
- Cmd+X - cut the selected blocks
- Cmd+V - paste
- Cmd+D - duplicate the selected block
- Cmd+F - find a block
- Cmd+K - open the block palette
- Backspace or Delete - delete the selected block
- Escape - close the open panel
Autosave
Every change is autosaved to your project. There is no save button - close the tab and reopen the workflow to pick up exactly where you left off. Simply opening a workflow saves nothing, so a tab you leave open cannot write over work done elsewhere.
If the workflow is changed somewhere else while you have it open, in another tab or through the API, Studio stops saving and tells you. Your canvas is left exactly as you had it, so copy anything you still need, then reload to pick up the latest version.
Version history
Because saving is automatic, Studio keeps the earlier versions for you. Open the kebab menu in the workflow toolbar and choose Version history to see them, newest first, each with the date and time it was replaced.
Versions are kept by meaning, not by keystroke. Dragging a block to a tidier spot, or pinning one, does not create a version. Changing a prompt, adding or removing a connection, or deleting a block does. That keeps the list short enough to read, so you can find the save you actually want to go back to.
Press Restore on any entry and the canvas returns to that state. Restoring is itself a save, so the version it replaced is kept too: you can always come forward again, including when a colleague edited the workflow in the meantime. A workflow keeps its most recent 50 versions.
Next: Integrations or Auto Agent