Skip to content

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 human-in-the-loop approval workflow 1. Submit (input validated) → 2. Review (suspend → wait) → 3. Approve (resume) → 4. Finalize (commit) Submit input validated Review suspend → wait Approve resume Finalize commit
Each box is a step. The run suspends at Review (snapshot persisted) and resumes when a human approves — no earlier step re-runs.

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}).`,
}),
});

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' } });

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.