A workflow is a multi-step job that the gateway runs on your behalf. Each step calls one tool, and the steps are wired together into a graph: run this, then that, branch on the result, fan out in parallel, wait for a human to approve. The engine persists every workflow and step to SQLite, runs them in the background, retries failures, and can start a workflow now or on a schedule.
This is how you define one, what the steps can do, how to schedule them, and how approval gates work.
The flow definition
A workflow is created from a flow: a JSON object keyed by step name. One key must be start – that is the first step. Every other step is referenced by its key.
A minimal two-step flow:
{
"start": { "name": "fetch", "tool": "fetch_url", "args": { "url": "https://example.com" }, "next": "summarize" },
"summarize": { "name": "summarize", "tool": "youtube_summarize", "args": { "text": "{{step.fetch}}" }, "done": true }
}
Each step spec has three parts:
-
name– the step’s name. -
tool– the gateway tool to call (any tool the workflow’s owner is allowed to use). -
args– the arguments passed to that tool.
The rest of the spec is a transition that says what happens after the step finishes.
Passing data between steps
A step’s args can reference two sources through {{...}} placeholders that are interpolated when the step runs:
-
{{input.key}}– a value from the workflow’s initialinput. -
{{step.name}}– the full result of an earlier step. -
{{step.name.field}}– a single field from an earlier step’s result. -
{{step.name | match:pattern}}– a regex match pulled out of an earlier step’s result.
That is how a later step consumes what an earlier one produced, so a fetch step can hand its output to a summarize step without you wiring anything by hand. Placeholders work inside nested arg objects and lists, not just top-level strings.
Transitions: sequence, choice, parallel, join
The transition key on a step decides the next move:
-
next: "step_name"– run that step next. Straight sequence. -
done: true– the workflow is complete. -
branch: [ { "if": <condition>, "then": "step_name" }, ... ]– evaluate each condition against the finished step’s result and follow the first match. This is conditional routing. -
parallel: ["a", "b", "c"]– fan out: start several steps at once. -
join: "step_name"– fan in: wait until the parallel siblings have all finished, then run the join step once. If siblings are still pending, the join simply waits.
Combining parallel and join lets a workflow do several things concurrently and then converge on a single follow-up step.
What a step can do
A step can call any tool exposed by the gateway, subject to the workflow owner’s permissions. That includes the whole built-in surface (Google Workspace, WhatsApp, browser automation, blog authoring, notifications, file and URL operations, and so on) as well as any tool proxied from a connected external MCP server. Because steps are just tool calls, a workflow is a way to script the same capabilities an agent would call one at a time, but durably and unattended.
Scheduling a workflow
Pass a schedule when you create the workflow and it starts later instead of immediately:
{
"name": "morning-brief",
"flow": { "start": { "name": "brief", "tool": "...", "args": {}, "done": true } },
"schedule": "tomorrow at 9am"
}
The schedule is parsed the same way alarms are, so natural-language times (“tomorrow at 9am”, “in 2 hours”) work as well as an explicit timestamp. A scheduled workflow is created in the scheduled state with its first step held as pending. When the alarm fires, the first step is released and the workflow runs normally. Until then it sits idle and can be cancelled like any other.
Approval gates
A step with no tool is an approval gate. When the engine reaches it, the step is marked awaiting_approval and a push notification is sent naming the workflow and the step ID. The workflow pauses there.
{
"start": { "name": "draft", "tool": "blog_create_draft", "args": {}, "next": "review" },
"review": { "name": "review", "next": "publish" },
"publish": { "name": "publish", "tool": "blog_publish", "args": {}, "done": true }
}
Here review has no tool. The workflow drafts a post, then stops and waits for a person. To release it, approve the step by its ID (workflow_step_approve). The gate is then marked done and the workflow advances to publish. This is how a human stays in the loop on anything sensitive: put a tool-less step in front of it.
There is also a separate release for steps that are simply held pending on an external condition rather than a human decision: marking a step ready (workflow_step_ready) queues it to run. Use approval for “a person must say yes” and ready for “the thing this step was waiting on has happened.”
Running, retries, and watchdog
Once a workflow is running, a background executor polls for steps that are ready, runs each one as its own task, records the result, and works out the next steps. Failures are retried up to three attempts, spaced out by the alarm scheduler, and a watchdog reclaims steps that get stuck. All of this is persisted, so a restart does not lose in-flight workflows.
Step states you will see: pending (waiting to be released), ready (queued to run), running, awaiting_approval, done, failed, cancelled. Workflow states: scheduled, running, completed, cancelled, failed.
Driving it
Everything above is available as gateway tools, so an agent (or you) can manage workflows directly:
-
workflow_create– create and start, or schedule, a workflow from a name and flow. -
workflow_list– list workflows. -
workflow_get– fetch one workflow with its steps and results. -
workflow_cancel– cancel a running or scheduled workflow. -
workflow_step_ready– release a pending step. -
workflow_step_approve– approve a step waiting at an approval gate. -
workflow_template_save,workflow_template_list,workflow_template_delete– save a flow as a reusable template and manage the saved set.
Templates let you keep a flow you use often (a morning brief, a publish pipeline, a data pull) and start fresh runs from it instead of re-sending the whole definition. All workflow tools require the workflow permission, so access to the engine is granted like any other capability.
In short
Define a graph of tool calls keyed by name from a start step. Wire them with next, branch, parallel, and join. Feed input forward through step results. Add a schedule to run it later. Drop in a tool-less step wherever a human should approve. The engine persists it, runs it in the background, retries what fails, and notifies you when it needs a decision.