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 under stackbone dev or 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:

workflows/qualify-lead.workflow.ts
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.

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

BUILT WITH ❤️ FROM CANADA AND SPAIN