Workflows
A workflow is a plain async function marked
'use workflow'that orchestrates'use step'units. Each step runs once, the runtime records its result, and retries the step on failure. The whole run survives a crash, a redeploy and a wait of minutes or days: it resumes at the first unfinished step instead of starting over. The box (the container running your workspace, on your laptop understackbone devor deployed in your cloud) gives every workflow a start route, a typed contract and a run history, and Studio shows each run step by step.
One workflow's entry: its start route, its steps, what ran last and the timer that starts it.
Use a workflow for work that takes a while or must not run twice by accident: a batch import, a nightly report, a refund that waits for a sign-off, a process that fans out to a dozen services and must finish even if the box restarts in the middle. For an open-ended conversation, use an agent instead.
What you can do with one
| Stage | What you do |
|---|---|
| Build | One file under workflows/: the 'use workflow' function, its 'use step' helpers, and the inputSchema / outputSchema it promises. stackbone add workflow <name> scaffolds it and stackbone dev remounts it on every save. See Write one. |
| Start | From the Playground, the CLI, an HTTP call, a timer the workflow declares, a connector event, or another workflow or agent. All of them make an ordinary run. See Start a run. |
| Follow | Every start is a run under Runs. Open it and read the steps in order, with the input and output of each and how long it took. See Follow every run. |
| Pause | Call requestApproval() where a person must sign off and the run parks in the HITL Inbox; call sleep() and it parks until the time comes. Both survive a restart. See Pause for a person or a timer. |
| Operate | Attach Guardrails, mark the workflow serial when two copies must never overlap, watch its timers under Recurring jobs, and score it from Evals. See Governance and Evaluation. |
Write one
Two directives make a function durable:
import { z } from 'zod';
export const inputSchema = z.object({ email: z.email(), company: z.string() });
export const outputSchema = z.object({ score: z.number(), qualified: z.boolean() });
export async function qualifyLeadWorkflow(input: z.infer<typeof inputSchema>) {
'use workflow';
const score = await scoreLead(input.company); // a durable step
return { score, qualified: score >= 60 };
}
async function scoreLead(company: string) {
'use step';
return company.length * 5;
}The runtime replays the body on every resume, so it stays deterministic: clocks,
randomness, I/O and every stackbone.* call go inside a step. The two schemas
are the contract the box enforces before a run starts, and the Playground
builds its form from them. Kill the runtime mid-run and start it again: the run
resumes at the first unfinished step.
- Workflows: overview: the execution model in full.
- Getting started: scaffold, trigger and watch the first one.
- Examples: an onboarding pipeline, a refund gated by a human approval, a scheduled digest with a long wait.
- Serial execution: one run at a time, in arrival order.
Start a run
Every start makes an ordinary run with its own id and its own step log, whatever started it.
| From | How |
|---|---|
| Studio | Playground: pick the workflow, fill the form the schema generates (or paste JSON), and Run workflow. The receipt links to the run. |
| The CLI | stackbone workflows start <name> --input '{…}'; stackbone workflows list and stackbone workflows schema <name> show what the box exposes. |
| HTTP | POST /api/workflows/<name>/start with the input as the body. A body that misses the schema starts no run. See API. |
| A timer | Export a schedules array next to the workflow and the box arms it at every boot. See Recurring jobs. |
| A connector event | Link an incoming event (a new Gmail message, for example) to the workflow under Triggers. See Integrations. |
| Another workflow or agent | stackbone.workflows.start(name, input) from a step or a tool handler, or startAndWait(...) when the caller needs the result. See an agent that starts a workflow. |
The Playground builds the form from the workflow's own schema, and shows the contract it enforces.
Follow every run
Every start is a row under Runs: its trigger, its duration and its status. Open one and the trace lists the steps in order, with timing and the waits between them; click a step and the inspector prints its input and output.
A finished run: two steps and two durable waits, with the first step's output in the inspector.
From the same screen, Save as test case turns the run into an eval case,
Copy run details copies the trace as JSON, and View run logs shows what
your steps printed. From a terminal,
stackbone runs list,
stackbone runs get <id>
and stackbone logs tail --run <id>
read the same data.
Pause for a person or a timer
requestApproval() parks the run in the HITL Inbox with the payload you
attached, until someone approves or rejects it in Studio or with
stackbone hitl approve <id>; when nobody decides inside its timeout, the
fallback you chose applies and the run carries on. sleep('24h') parks the run
until the time comes. Neither holds a process, and both survive a restart. See
Human-in-the-loop, the example
a refund gated by a human approval,
and Governance for
the inbox.
Team it with an agent
A workflow owns the fixed, auditable outline. When one part of it needs a
model's judgment, a step runs one turn of a sibling
agent with callDeepAgent('support', message),
and the reply becomes a durable checkpoint of the run. See
Workflow agents and the examples
a workflow that delegates a turn to an agent
and draft with an agent, then gate the send on a human.
Note
Check the workflow sdk and workflow cli documentation for more details.
Read more
- Workflows: overview and Getting started.
- Examples: three workflows you can copy.
- API: the start route, its errors and the discovery routes.
- Recurring jobs and Human-in-the-loop.
- Governance: the Studio screens across the whole lifecycle.