Snapshots
In Mastra, a snapshot is a serializable representation of a workflow’s complete execution state at a specific point in time. Snapshots capture all the information needed to resume a workflow from exactly where it left off, including:
- The current state of each step in the workflow
- The outputs of completed steps
- The execution path taken through the workflow
- Any suspended steps and their metadata
- The remaining retry attempts for each step
- Additional contextual data needed to resume execution
Snapshots are automatically created and managed by Mastra whenever a workflow is suspended, and are persisted to the configured storage system.
The Role of Snapshots in Suspend and Resume
Snapshots are the key mechanism enabling Mastra’s suspend and resume capabilities. When a workflow step calls await suspend()
:
- The workflow execution is paused at that exact point
- The current state of the workflow is captured as a snapshot
- The snapshot is persisted to storage
- The workflow step is marked as “suspended” with a status of
'suspended'
- Later, when
resume()
is called on the suspended step, the snapshot is retrieved - The workflow execution resumes from exactly where it left off
This mechanism provides a powerful way to implement human-in-the-loop workflows, handle rate limiting, wait for external resources, and implement complex branching workflows that may need to pause for extended periods.
Snapshot Anatomy
A Mastra workflow snapshot consists of several key components:
export interface WorkflowRunState {
// Core state info
value: Record<string, string>; // Current state machine value
context: { // Workflow context
steps: Record<string, { // Step execution results
status: 'success' | 'failed' | 'suspended' | 'waiting' | 'skipped';
payload?: any; // Step-specific data
error?: string; // Error info if failed
}>;
triggerData: Record<string, any>; // Initial trigger data
attempts: Record<string, number>; // Remaining retry attempts
inputData: Record<string, any>; // Initial input data
};
activePaths: Array<{ // Currently active execution paths
stepPath: string[];
stepId: string;
status: string;
}>;
// Metadata
runId: string; // Unique run identifier
timestamp: number; // Time snapshot was created
// For nested workflows and suspended steps
childStates?: Record<string, WorkflowRunState>; // Child workflow states
suspendedSteps?: Record<string, string>; // Mapping of suspended steps
}
How Snapshots Are Saved and Retrieved
Mastra persists snapshots to the configured storage system. By default, snapshots are saved to a LibSQL database, but can be configured to use other storage providers like Upstash.
The snapshots are stored in the workflow_snapshots
table and identified uniquely by the run_id
for the associated run when using libsql.
Utilizing a persistence layer allows for the snapshots to be persisted across workflow runs, allowing for advanced human-in-the-loop functionality.
Read more about libsql storage and upstash storage here.
Saving Snapshots
When a workflow is suspended, Mastra automatically persists the workflow snapshot with these steps:
- The
suspend()
function in a step execution triggers the snapshot process - The
WorkflowInstance.suspend()
method records the suspended machine persistWorkflowSnapshot()
is called to save the current state- The snapshot is serialized and stored in the configured database in the
workflow_snapshots
table - The storage record includes the workflow name, run ID, and the serialized snapshot
Retrieving Snapshots
When a workflow is resumed, Mastra retrieves the persisted snapshot with these steps:
- The
resume()
method is called with a specific step ID - The snapshot is loaded from storage using
loadWorkflowSnapshot()
- The snapshot is parsed and prepared for resumption
- The workflow execution is recreated with the snapshot state
- The suspended step is resumed, and execution continues
Storage Options for Snapshots
Mastra provides multiple storage options for persisting snapshots.
A storage
instance is configured on the Mastra
class, and is used to setup a snapshot persistence layer for all workflows registered on the Mastra
instance.
This means that storage is shared across all workflows registered with the same Mastra
instance.
LibSQL (Default)
The default storage option is LibSQL, a SQLite-compatible database:
import { Mastra } from '@mastra/core/mastra';
import { DefaultStorage } from '@mastra/core/storage/libsql';
const mastra = new Mastra({
storage: new DefaultStorage({
config: {
url: "file:storage.db", // Local file-based database
// For production:
// url: process.env.DATABASE_URL,
// authToken: process.env.DATABASE_AUTH_TOKEN,
}
}),
workflows: {
weatherWorkflow,
travelWorkflow,
}
});
Upstash (Redis-Compatible)
For serverless environments:
import { Mastra } from '@mastra/core/mastra';
import { UpstashStore } from "@mastra/upstash";
const mastra = new Mastra({
storage: new UpstashStore({
url: process.env.UPSTASH_URL,
token: process.env.UPSTASH_TOKEN,
}),
workflows: {
weatherWorkflow,
travelWorkflow,
}
});
Best Practices for Working with Snapshots
-
Ensure Serializability: Any data that needs to be included in the snapshot must be serializable (convertible to JSON).
-
Minimize Snapshot Size: Avoid storing large data objects directly in the workflow context. Instead, store references to them (like IDs) and retrieve the data when needed.
-
Handle Resume Context Carefully: When resuming a workflow, carefully consider what context to provide. This will be merged with the existing snapshot data.
-
Set Up Proper Monitoring: Implement monitoring for suspended workflows, especially long-running ones, to ensure they are properly resumed.
-
Consider Storage Scaling: For applications with many suspended workflows, ensure your storage solution is appropriately scaled.
Advanced Snapshot Patterns
Custom Snapshot Metadata
When suspending a workflow, you can include custom metadata that can help when resuming:
await suspend({
reason: "Waiting for customer approval",
requiredApprovers: ["manager", "finance"],
requestedBy: currentUser,
urgency: "high",
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
This metadata is stored with the snapshot and available when resuming.
Conditional Resumption
You can implement conditional logic based on the suspend payload when resuming:
run.watch(async ({ context, activePaths }) => {
for (const path of activePaths) {
const approvalStep = context.steps?.approval;
if (approvalStep?.status === 'suspended') {
const payload = approvalStep.suspendPayload;
if (payload.urgency === "high" && currentUser.role === "manager") {
await resume({
stepId: 'approval',
context: { approved: true, approver: currentUser.id },
});
}
}
}
});