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 handle
  • getResumableSandboxName(state): Extracts the persistent sandbox name for reconnection
  • clearSandboxState(state): Removes only runtime fields (expiresAt, runtime*) while preserving the durable handle
  • clearSandboxResumeState(state): Wipes everything including the resume handle
  • clearUnavailableSandboxState(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:

  1. Receives a POST request with sessionId
  2. Calls connectSandbox to create a sandbox instance
  3. Invokes sandbox.stop() to stop the VM (Vercel automatically creates a snapshot)
  4. Strips runtime fields via clearSandboxState while keeping sandboxName or snapshotId
  5. 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:

  1. Receives a PUT request with sessionId
  2. Checks for resumable state using getResumableSandboxName
  3. If a named sandbox exists: calls connectSandbox(..., { resume: true })
  4. If only a legacy snapshot exists: creates a new persistent sandbox with the old snapshot ID and resume: true (lazy migration)
  5. 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.connect invokes VercelSandboxSDK.get({ name, resume })
  • When resume is 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:

  • clearSandboxState removes only runtime fields (expiresAt, runtime*) 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

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

File Purpose Link
packages/sandbox/vercel/sandbox.ts Core sandbox class; connect method accepts resume flag and restores stopped VMs. https://github.com/vercel-labs/open-agents/blob/main/packages/sandbox/vercel/sandbox.ts
packages/sandbox/vercel/connect.ts High-level factory that builds a sandbox config and decides between connectNamedSandbox and fresh creation. https://github.com/vercel-labs/open-agents/blob/main/packages/sandbox/vercel/connect.ts
apps/web/app/api/sandbox/snapshot/route.ts HTTP API for pause (POST) and resume (PUT) – orchestrates state clearing, snapshot ID handling, and lazy migration. https://github.com/vercel-labs/open-agents/blob/main/apps/web/app/api/sandbox/snapshot/route.ts
apps/web/lib/sandbox/utils.ts State-management helpers: detecting resumable handles, clearing runtime vs. durable state, error classification. https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/sandbox/utils.ts
packages/sandbox/vercel/snapshot-refresh.ts Utility used when building a base snapshot; not directly part of resume but shows how snapshots are created. https://github.com/vercel-labs/open-agents/blob/main/packages/sandbox/vercel/snapshot-refresh.ts

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) or snapshotId (legacy) in the session record to enable snapshot-based sandbox resume.
  • State separation: Runtime fields like expiresAt are cleared via clearSandboxState while durable handles persist, allowing the VM to be stopped without losing the resume capability.
  • SDK resume flag: Passing resume: true to VercelSandbox.connect (or connectSandbox) 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: clearUnavailableSandboxState provides 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →