Workflows
Orchestrate
An agent reasons; a workflow orchestrates. Reach for a workflow when a task is multi-step, needs branching or parallelism, or must pause for a human and resume later. Workflows are deterministic and durable — they persist their state (a snapshot) and pick up exactly where they left off.
A workflow with a human gate is the canonical example — a step can suspend the run, persist a snapshot, and resume later without re-running prior steps:
A human-in-the-loop approval workflow
A step is the unit of work — typed input/output (Zod, Valibot, or ArkType) and an execute function.
import { createStep, createWorkflow } from '@mastra/core/workflows';import { z } from 'zod';
const fetchUser = createStep({ id: 'fetch-user', inputSchema: z.object({ userId: z.string() }), outputSchema: z.object({ name: z.string(), tier: z.string() }), execute: async ({ inputData }) => { // ...load the user return { name: 'Ada', tier: 'pro' }; },});
const greet = createStep({ id: 'greet', inputSchema: z.object({ name: z.string(), tier: z.string() }), outputSchema: z.object({ message: z.string() }), execute: async ({ inputData }) => ({ message: `Welcome back, ${inputData.name} (${inputData.tier}).`, }),});Compose with control flow
Section titled “Compose with control flow”Chain steps, branch on data, or run work in parallel — then always terminate with .commit().
export const onboardingWorkflow = createWorkflow({ id: 'onboarding', inputSchema: z.object({ userId: z.string() }), outputSchema: z.object({ message: z.string() }),}) .then(fetchUser) .then(greet) .commit();Register it on the runtime alongside agents:
export const mastra = new Mastra({ agents: { /* ... */ }, workflows: { onboardingWorkflow },});And run it through the runtime:
const run = await mastra.getWorkflow('onboarding').createRun();const result = await run.start({ inputData: { userId: 'u_123' } });Human-in-the-loop (suspend / resume)
Section titled “Human-in-the-loop (suspend / resume)”A step declares a suspendSchema and resumeSchema. Call suspend(...) to pause and persist a snapshot; resume the run later with resumeData — prior steps do not re-run.
import { createWorkflow, createStep } from '@mastra/core/workflows';import { z } from 'zod';
const approvalStep = createStep({ id: 'user-approval', inputSchema: z.object({ documentId: z.string() }), suspendSchema: z.object({ reason: z.string() }), resumeSchema: z.object({ approved: z.boolean(), comment: z.string().optional() }), outputSchema: z.object({ status: z.string() }), execute: async ({ inputData, resumeData, suspend }) => { if (resumeData?.approved === undefined) { return await suspend({ reason: `Approve doc ${inputData.documentId}` }); } return { status: resumeData.approved ? 'Approved' : 'Rejected' }; },});
export const approvalWorkflow = createWorkflow({ id: 'approval-workflow', inputSchema: z.object({ documentId: z.string() }), outputSchema: z.object({ status: z.string() }),}) .then(approvalStep) .commit();// Start the run, then resume the suspended step later.const run = await mastra.getWorkflow('approval-workflow').createRun();const first = await run.start({ inputData: { documentId: 'DOC-1' } });
if (first.status === 'suspended') { const done = await run.resume({ step: approvalStep, resumeData: { approved: true, comment: 'LGTM' }, }); console.log(done.status, done.result);}Because the snapshot is persisted by the runtime’s storage, a suspended run survives restarts and deploys — the durable backbone for review gates and async approvals.
Reference: Workflows overview · Control flow · Suspend & resume
Next: Deployment — take it from dev to production.