How to Implement Snapshot-Based Sandbox Resume in Open Agents
Open Agents implements snapshot-based sandbox resume by storing a persistent VM handle (either sandboxName or legacy snapshotId) in the session state, then using the Vercel Sandbox SDK's resume flag to reconnect to stopped VMs rather than creating fresh instances.
The vercel-labs/open-agents repository provides a robust implementation of persistent sandbox environments using Vercel's infrastructure. This guide explains how to implement snapshot-based sandbox resume functionality, allowing agent sessions to pause and later reconnect to their exact filesystem state and runtime context.
Architecture Overview
The resume implementation spans three architectural layers: runtime state utilities, HTTP API endpoints, and SDK integration.
Runtime State Handling
The apps/web/lib/sandbox/utils.ts file provides utility functions that manage the durable handle versus volatile runtime state:
hasResumableSandboxState(state): Detects whether a session contains a resumable handlegetResumableSandboxName(state): Extracts the persistent sandbox name for reconnectionclearSandboxState(state): Removes only runtime fields (expiresAt,runtime*) while preserving the durable handleclearSandboxResumeState(state): Wipes everything including the resume handleclearUnavailableSandboxState(state): Handles cases where the sandbox VM no longer exists
API Surface
The apps/web/app/api/sandbox/snapshot/route.ts file implements two HTTP endpoints:
POST /pause
- Stops the running sandbox via
sandbox.stop() - Clears runtime state using
clearSandboxState - Returns the saved snapshot ID or
null
PUT /resume
- Validates that a snapshot is available
- If a named persistent sandbox exists: calls
connectSandbox(..., { resume: true }) - If only a legacy snapshot exists: creates a new persistent sandbox with the old snapshot ID and
resume: true(lazy migration)
Sandbox SDK Integration
The packages/sandbox/vercel/sandbox.ts file exposes the VercelSandbox.connect(name, { resume }) method. This invokes VercelSandboxSDK.get({ name, resume }).
When resume is true, the SDK re-attaches to a stopped VM instead of creating a fresh instance, restoring the filesystem and any mounted state.
Step-by-Step Implementation
Pausing a Sandbox
When implementing the pause functionality, the system:
- Receives a
POSTrequest withsessionId - Calls
connectSandboxto create a sandbox instance - Invokes
sandbox.stop()to stop the VM (Vercel automatically creates a snapshot) - Strips runtime fields via
clearSandboxStatewhile keepingsandboxNameorsnapshotId - Returns the persisted handle to the client
Reference: apps/web/app/api/sandbox/snapshot/route.ts lines 75-84
Resuming a Sandbox
The resume process handles both new-style named sandboxes and legacy snapshots:
- Receives a
PUTrequest withsessionId - Checks for resumable state using
getResumableSandboxName - If a named sandbox exists: calls
connectSandbox(..., { resume: true }) - If only a legacy snapshot exists: creates a new persistent sandbox with the old snapshot ID and
resume: true(lazy migration) - The SDK reconnects to the stopped VM, restoring filesystem and state
Reference: apps/web/app/api/sandbox/snapshot/route.ts lines 94-119
SDK Reconnection
The resume flag is the critical mechanism:
VercelSandbox.connectinvokesVercelSandboxSDK.get({ name, resume })- When
resumeis true, the SDK attaches to the existing stopped VM - Timeout handling derives from stopped session metadata or uses a default reconnect timeout
Reference: packages/sandbox/vercel/sandbox.ts lines 7-25
State Preservation
The utility functions distinguish between durable and volatile state:
clearSandboxStateremoves only runtime fields (expiresAt,runtime*) while preserving durable identifiers such assandboxNameorsnapshotId- This ensures the VM can be stopped to save costs while maintaining the capability to resume later
Reference: apps/web/lib/sandbox/utils.ts lines 27-44
Code Examples
Client-Side Pause Request
// Client side – pause request
await fetch('/api/sandbox/snapshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId: 'abc123' }),
})
.then(r => r.json())
.then(data => {
console.log('Paused, snapshot ID:', data.snapshotId);
});
Implementation: The handler stops the sandbox and clears runtime state while preserving the durable handle. See apps/web/app/api/sandbox/snapshot/route.ts lines 75-84.
Client-Side Resume Request
// Client side – resume request
await fetch('/api/sandbox/snapshot', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId: 'abc123' }),
})
.then(r => r.json())
.then(data => {
if (data.success) console.log('Sandbox resumed');
else console.error('Resume error:', data.error);
});
Implementation: The handler validates available snapshots and calls connectSandbox(..., { resume: true }) to reconnect. See apps/web/app/api/sandbox/snapshot/route.ts lines 94-119.
Programmatic SDK Resume
import { connectSandbox } from '@open-harness/sandbox';
// Reconnect to a stopped sandbox by name
const sandbox = await connectSandbox(
{
type: 'vercel',
sandboxName: 'session_abc123', // persisted name
},
{
resume: true, // <-- critical flag
timeout: 15 * 60 * 1000, // custom timeout if desired
},
);
Implementation: The resume option reaches VercelSandbox.connect, which forwards it to the SDK. See packages/sandbox/vercel/sandbox.ts lines 7-25.
Checking Resumable State
import { getResumableSandboxName, hasResumableSandboxState } from '@/lib/sandbox/utils';
function canResume(state: SandboxState) {
return hasResumableSandboxState(state);
}
// Example usage in an API route
if (!canResume(sessionRecord.sandboxState)) {
return Response.json({ error: 'No sandbox available for resume' }, { status: 404 });
}
Implementation: Utility functions detect and extract durable handles. See apps/web/lib/sandbox/utils.ts.
Key Files and Implementation Details
These files together implement the full snapshot-based sandbox resume workflow used throughout Open Agents.
Summary
- Persistent handles: The system stores either
sandboxName(new style) orsnapshotId(legacy) in the session record to enable snapshot-based sandbox resume. - State separation: Runtime fields like
expiresAtare cleared viaclearSandboxStatewhile durable handles persist, allowing the VM to be stopped without losing the resume capability. - SDK resume flag: Passing
resume: truetoVercelSandbox.connect(orconnectSandbox) triggers the SDK to re-attach to a stopped VM rather than create a fresh instance. - Lazy migration: The resume endpoint handles legacy snapshots by creating new persistent sandboxes with the old snapshot ID and
resume: true, gradually migrating to the named sandbox model. - Error handling:
clearUnavailableSandboxStateprovides graceful fallback when a sandbox VM no longer exists, allowing the system to create a fresh instance instead of failing.
Frequently Asked Questions
What is the difference between named sandboxes and legacy snapshots in Open Agents?
Named sandboxes use a persistent sandboxName identifier stored in the session state, while legacy snapshots rely on a snapshotId. The resume logic in apps/web/app/api/sandbox/snapshot/route.ts handles both: it prioritizes named sandboxes via getResumableSandboxName, but can migrate legacy snapshots by creating a new persistent sandbox with the old snapshot ID and resume: true.
How does the resume flag work in the Vercel Sandbox SDK?
When resume: true is passed to VercelSandbox.connect (implemented in packages/sandbox/vercel/sandbox.ts), the SDK invokes VercelSandboxSDK.get({ name, resume }). This flag instructs the SDK to attach to an existing stopped VM rather than provisioning a new one, restoring the filesystem and runtime state from the last snapshot.
What happens if a sandbox becomes unavailable after pausing?
The system uses clearUnavailableSandboxState from apps/web/lib/sandbox/utils.ts to detect when a sandbox VM no longer exists. When this occurs, the resume handler clears the stale handle from the session state, allowing the system to fall back to creating a fresh sandbox instead of failing with a connection error.
How is runtime state cleared while preserving the resume handle?
The clearSandboxState utility (in apps/web/lib/sandbox/utils.ts) selectively removes volatile fields like expiresAt and runtime* properties while preserving durable identifiers such as sandboxName or snapshotId. This ensures the VM can be stopped to save costs while maintaining the capability to resume later.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →