Suspend & Resume
Workflows can be paused at any step to collect additional data, wait for API callbacks, throttle expensive operations, or wait for Human in the Loop input. When a workflow is suspended, its current state can be persisted as a snapshot and later resumed from the same point. Snapshots are stored in the configured storage system and persist across deployments and application restarts.
New to suspend and resume? Watch these official video tutorials:
- Mastering Human-in-the-Loop with Suspend & Resume - Learn how to suspend workflows and accept user inputs
- Building Multi-Turn Chat Interfaces with React - Implement multi-turn human-involved interactions with a React chat interface
Workflow status types
When running a workflow, its status can be one of the following:
running- The workflow is currently runningsuspended- The workflow is suspendedsuccess- The workflow has completedfailed- The workflow has failed
Suspending a workflow with suspend()
To pause execution at a specific step until user input is received, use the suspend function to temporarily halt the workflow, allowing it to resume only when the necessary data is provided.

const step1 = createStep({
id: "step-1",
inputSchema: z.object({
input: z.string(),
}),
outputSchema: z.object({
output: z.string(),
}),
resumeSchema: z.object({
city: z.string(),
}),
execute: async ({ resumeData, suspend }) => {
const { city } = resumeData ?? {};
if (!city) {
return await suspend({});
}
return { output: "" };
},
});
export const testWorkflow = createWorkflow({
// ...
})
.then(step1)
.commit();
For more details, check out the Suspend workflow example.
Identifying suspended steps
To resume a suspended workflow, inspect the suspended array in the result to determine which step needs input:
import { mastra } from "./mastra";
const run = await mastra.getWorkflow("testWorkflow").createRunAsync();
const result = await run.start({
inputData: {
city: "London",
},
});
console.log(JSON.stringify(result, null, 2));
if (result.status === "suspended") {
const resumedResult = await run.resume({
step: result.suspended[0],
resumeData: {
city: "Berlin",
},
});
}
In this case, the logic resumes the first step listed in the suspended array. A step can also be defined using it's id, for example: 'step-1'.
{
"status": "suspended",
"steps": {
// ...
"step-1": {
// ...
"status": "suspended"
}
},
"suspended": [["step-1"]]
}
See Workflow Output for more details.
Providing user feedback with suspend
When a workflow is suspended, feedback can be surfaced to the user through the suspendSchema. Include a reason in the suspend payload to explain why the workflow paused.
import { createWorkflow, createStep } from "@mastra/core/workflows";
import { z } from "zod";
const step1 = createStep({
id: "step-1",
inputSchema: z.object({
value: z.string(),
}),
resumeSchema: z.object({
confirm: z.boolean(),
}),
suspendSchema: z.object({
reason: z.string(),
}),
outputSchema: z.object({
value: z.string(),
}),
execute: async ({ resumeData, suspend }) => {
const { confirm } = resumeData ?? {};
if (!confirm) {
return await suspend({
reason: "Confirm to continue",
});
}
return { value: "" };
},
});
export const testWorkflow = createWorkflow({
// ...
})
.then(step1)
.commit();
In this case, the reason provided explains that the user must confirm to continue.
{
"step-1": {
// ...
"status": "suspended",
"suspendPayload": {
"reason": "Confirm to continue"
}
}
}
See Workflow Output for more details.
Resuming a workflow with resume()
A workflow can be resumed by calling resume and providing the required resumeData. You can either explicitly specify which step to resume from, or when exactly one step is suspended, omit the step parameter and the workflow will automatically resume that step.
import { mastra } from "./mastra";
const run = await mastra.getWorkflow("testWorkflow").createRunAsync();
const result = await run.start({
inputData: {
city: "London",
},
});
console.log(JSON.stringify(result, null, 2));
if (result.status === "suspended") {
const resumedResult = await run.resume({
step: "step-1",
resumeData: {
city: "Berlin",
},
});
console.log(JSON.stringify(resumedResult, null, 2));
}
You can also omit the step parameter when exactly one step is suspended:
const resumedResult = await run.resume({
resumeData: {
city: "Berlin",
},
// step parameter omitted - automatically resumes the single suspended step
});
You can pass runtimeContext as an argument to both the start and resume commands.
import { RuntimeContext } from "@mastra/core/runtime-context";
const runtimeContext = new RuntimeContext();
const result = await run.start({
step: "step-1",
inputData: {
city: "London",
},
runtimeContext,
});
const resumedResult = await run.resume({
step: "step-1",
resumeData: {
city: "New York",
},
runtimeContext,
});
See Runtime Context for more information.
Resuming nested workflows
To resume a suspended nested workflow pass the workflow instance to the step parameter of the resume function.
const dowhileWorkflow = createWorkflow({
id: "dowhile-workflow",
inputSchema: z.object({ value: z.number() }),
outputSchema: z.object({ value: z.number() }),
})
.dountil(
createWorkflow({
id: "simple-resume-workflow",
inputSchema: z.object({ value: z.number() }),
outputSchema: z.object({ value: z.number() }),
steps: [incrementStep, resumeStep],
})
.then(incrementStep)
.then(resumeStep)
.commit(),
async ({ inputData }) => inputData.value >= 10,
)
.then(
createStep({
id: "final",
inputSchema: z.object({ value: z.number() }),
outputSchema: z.object({ value: z.number() }),
execute: async ({ inputData }) => ({ value: inputData.value }),
}),
)
.commit();
const run = await dowhileWorkflow.createRunAsync();
const result = await run.start({ inputData: { value: 0 } });
if (result.status === "suspended") {
const resumedResult = await run.resume({
resumeData: { value: 2 },
step: ["simple-resume-workflow", "resume"],
});
console.log(JSON.stringify(resumedResult, null, 2));
}
Sleep & Events
Workflows can also pause execution for timed delays or external events. These methods set the workflow status to waiting rather than suspended, and are useful for polling, delayed retries, or event-driven processes.
Available methods:
.sleep(): Pause for a specified number of milliseconds.sleepUntil(): Pause until a specific date.waitForEvent(): Pause until an external event is received.sendEvent(): Send an event to resume a waiting workflow