--- title: 'Workflows' description: 'Durable functions that run in idempotent steps, survive crashes and redeploys, pause for a person or a timer, and show up in Studio as runs you can read step by step.' position: 2 --- # 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. ![The Catalog entry for the daily-digest workflow in Studio: healthy, job, wdk; Open in Playground, View runs, Copy curl and Copy link actions; the workflow id, an input contract marked as declared, the trigger POST /api/workflows/daily-digest/start, two steps named collectRecipients and deliver, five recent runs marked done, and a "Runs on a timer" block with the cron 0 8 * * * and its last tick.](/images/workflows/workflow-detail.png) _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](/docs/home/features/agents) 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 ` scaffolds it and `stackbone dev` remounts it on every save. See [Write one](#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](#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](#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](#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](/docs/home/features/governance) and [Evaluation](/docs/home/features/evaluation). | ## Write one Two directives make a function durable: ```ts // 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) { '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](/docs/sdk/workflows/overview): the execution model in full. - [Getting started](/docs/sdk/workflows/building-workflows): scaffold, trigger and watch the first one. - Examples: [an onboarding pipeline](/docs/examples/workflows/onboarding-pipeline), [a refund gated by a human approval](/docs/examples/workflows/refund-approval), [a scheduled digest with a long wait](/docs/examples/workflows/scheduled-digest). - [Serial execution](/docs/sdk/workflows/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 --input '{…}'`](/docs/cli/reference/workflows#stackbone-workflows-start); `stackbone workflows list` and `stackbone workflows schema ` show what the box exposes. | | HTTP | `POST /api/workflows//start` with the input as the body. A body that misses the schema starts no run. See [API](/docs/home/features/api#start-and-follow-a-workflow). | | A timer | Export a `schedules` array next to the workflow and the box arms it at every boot. See [Recurring jobs](/docs/home/features/recurring-jobs). | | A connector event | Link an incoming event (a new Gmail message, for example) to the workflow under **Triggers**. See [Integrations](/docs/home/features/integrations#receive-events). | | 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](/docs/examples/workflow-agents/agent-starts-workflow). | ![The Studio Playground with the refund workflow selected: a form generated from its schema with orderId set to A-1002 and amount to 129, a Run workflow button, and the input JSON Schema shown under Contract (schema).](/images/workflows/playground-run-form.png) _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 workflow run's trace in Studio: the run id, done, workflow, 4.48 s in the header; Save as test case, Copy run details and View run logs actions; a waterfall with collectRecipients, a 25 ms wait, deliver, a 2.13 s wait, deliver and Success; and the inspector printing the first step's output, a list of two email addresses.](/images/workflows/run-detail.png) _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`](/docs/cli/reference/runs#stackbone-runs-list), [`stackbone runs get `](/docs/cli/reference/runs#stackbone-runs-get) and [`stackbone logs tail --run `](/docs/sdk/platform/observability#inspecting-runs-from-the-shell) 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 `; 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](/docs/sdk/humans/approval), the example [a refund gated by a human approval](/docs/examples/workflows/refund-approval), and [Governance](/docs/home/features/governance#keep-a-person-in-the-loop) 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](/docs/home/features/agents) with `callDeepAgent('support', message)`, and the reply becomes a durable checkpoint of the run. See [Workflow agents](/docs/sdk/workflow-agents/overview) and the examples [a workflow that delegates a turn to an agent](/docs/examples/workflow-agents/delegate-to-agent) and [draft with an agent, then gate the send on a human](/docs/examples/workflow-agents/draft-then-approve). > [!NOTE] > Check the [workflow sdk](/docs/sdk/workflows/overview) and [workflow cli](/docs/cli/reference/workflows) documentation for more details. ## Read more - [Workflows: overview](/docs/sdk/workflows/overview) and [Getting started](/docs/sdk/workflows/building-workflows). - [Examples](/docs/examples/workflows/onboarding-pipeline): three workflows you can copy. - [API](/docs/home/features/api#start-and-follow-a-workflow): the start route, its errors and the discovery routes. - [Recurring jobs](/docs/home/features/recurring-jobs) and [Human-in-the-loop](/docs/sdk/humans/approval). - [Governance](/docs/home/features/governance): the Studio screens across the whole lifecycle.