# How to Implement Persistent Session Resume via Reconnect in Open Agents

> Learn to implement persistent session resume in Open Agents using reconnect. Store sandbox state and use VercelSandbox connect to reattach to running cloud VMs and refresh timeouts.

- Repository: [Vercel Labs/open-agents](https://github.com/vercel-labs/open-agents)
- Tags: how-to-guide
- Published: 2026-04-16

---

**You can resume a disconnected agent session by storing the sandbox's `VercelState` (including its durable `sandboxName`) in your session database, then calling `VercelSandbox.connect()` with that state to reattach to the running cloud VM, verify liveness with a probe, and refresh the timeout.**

Open Agents uses the Vercel Sandbox SDK to provide persistent, cloud-based execution environments that survive page reloads and network drops. Understanding how to implement persistent session resume via reconnect in Open Agents ensures your users never lose work when connections flake, as the underlying VM continues running in the cloud and can be reattached using its unique name.

## Architecture Overview

The reconnect flow operates in three distinct phases orchestrated between your client, API layer, and the Vercel Sandbox infrastructure.

First, after initial sandbox creation, you **persist the minimal state** required for reconnection. Second, when a user returns or reconnects, you **probe the existing VM** to verify it is still alive. Third, you **refresh the database** with any updated timeout information and return a status payload to the client.

This design ensures that even if a user's browser closes completely, the agent's execution context—including variables, filesystem state, and running processes—remains intact in the cloud sandbox.

## Persisting Sandbox State

Before you can reconnect, you must store the sandbox's identifying metadata. Open Agents defines this through the `VercelState` interface.

```typescript
// packages/sandbox/vercel/state.ts
export interface VercelState {
  /** Where to clone from (omit for empty sandbox or when reconnecting/restoring) */
  source?: Source;
  /** Durable persistent sandbox name used for reconnecting/resuming sessions */
  sandboxName?: string;
  /** Legacy runtime sandbox ID from the stable SDK. */
  sandboxId?: string;
  /** Snapshot ID used only for legacy restore/migration flows */
  snapshotId?: string;
  /** Timestamp (ms) when the current runtime session expires */
  expiresAt?: number;
}

```

When you create a sandbox via `VercelSandbox.create`, the SDK either accepts a user-provided name or generates one automatically. This name is stored on the instance and retrieved later via `getState()`.

```typescript
// After creation, persist to your session DB
const state = sandbox.getState(); // Returns VercelState
await updateSession(sessionId, { sandboxState: state });

```

The `sandboxName` acts as the durable identifier that allows the SDK to locate the specific VM instance across disconnections.

## The Reconnect API Endpoint

The public interface for resuming sessions lives in [`apps/web/app/api/sandbox/reconnect/route.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/app/api/sandbox/reconnect/route.ts). This handler accepts a `sessionId` query parameter, validates the stored state, and attempts to reattach to the sandbox.

```typescript
// apps/web/app/api/sandbox/reconnect/route.ts
export async function GET(req: Request): Promise<Response> {
  // 1️⃣ Load the session and verify runtime state exists
  if (!hasRuntimeSandboxState(sessionRecord.sandboxState)) {
    return Response.json({ status: "no_sandbox" });
  }

  // 2️⃣ Attempt to connect and probe the VM
  const sandbox = await connectSandbox(state as SandboxState);
  const probe = await sandbox.exec("pwd", sandbox.workingDirectory, 15_000);
  
  // 3️⃣ Refresh database with latest state (including new expiresAt)
  const refreshedState = sandbox.getState?.() ?? {
    ...state,
    ...(sandbox.expiresAt ? { expiresAt: sandbox.expiresAt } : {})
  };
  
  await updateSession(sessionId, {
    sandboxState: refreshedState,
    sandboxExpiresAt: getSandboxExpiresAtDate(refreshedState),
  });

  // 4️⃣ Return payload for UI synchronization
  return Response.json({
    status: "connected",
    hasSnapshot: hasPausedState,
    expiresAt: sandbox.expiresAt,
    lifecycle: buildLifecyclePayload(updatedSession ?? sessionRecord),
  });
}

```

The endpoint uses `hasRuntimeSandboxState` from [`apps/web/lib/sandbox/utils.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/sandbox/utils.ts) to distinguish between active sandboxes, paused snapshots, and unavailable states. If the probe succeeds, the session continues; if it fails with an unavailable error, the handler clears the stale state and returns `status: "expired"`.

## Connecting to Existing Sandboxes

The core reconnection logic resides in the `VercelSandbox.connect` static method. This function attaches to an existing VM by name and reconstructs the sandbox instance with the appropriate timeout and environment context.

```typescript
// packages/sandbox/vercel/sandbox.ts
static async connect(
  sandboxName: string,
  options: {
    env?: Record<string, string>;
    githubToken?: string;
    hooks?: SandboxHooks;
    /** Remaining timeout in ms for this sandbox session. */
    remainingTimeout?: number;
    /** Ports that were declared at creation time */
    ports?: number[];
    /** Whether to explicitly resume a stopped sandbox */
    resume?: boolean;
  } = {},
): Promise<VercelSandbox> {
  const sdk = await VercelSandboxSDK.get({
    name: sandboxName,
    resume: options.resume ?? false,
  });
  
  await syncGitHubCredentialBrokering(sdk, options.githubToken);
  const session = sdk.currentSession();

  // Derive remaining timeout from live session metadata
  const remainingTimeout =
    options.remainingTimeout ??
    getRemainingTimeoutFromSession(session) ??
    (isStoppedSessionStatus(session.status) ? undefined : DEFAULT_RECONNECT_TIMEOUT_MS);
    
  const startTime = remainingTimeout !== undefined ? Date.now() : undefined;

  return new VercelSandbox(
    sdk,
    session,
    sandboxName,
    session.sessionId,
    DEFAULT_WORKING_DIRECTORY,
    options.env,
    undefined,
    options.hooks,
    remainingTimeout,
    startTime,
    options.ports,
  );
}

```

Key implementation details include:

- **Name-based attachment**: `VercelSandboxSDK.get` locates the VM using the durable `sandboxName`.
- **Timeout preservation**: The method calculates `remainingTimeout` from live session metadata, ensuring the countdown continues from where it left off rather than resetting.
- **Resume flexibility**: The `resume` parameter controls whether to wake a stopped sandbox or simply attach to a running one.

## Handling Timeouts and Lifecycle

Persistent sessions require careful timeout management to prevent resource leaks while allowing reasonable reconnection windows.

When you initially create a sandbox, `VercelSandbox.create` stores the `effectiveTimeout` and schedules a proactive stop. Upon reconnection, `VercelSandbox.connect` accepts a `remainingTimeout` value derived from the live SDK session via `getRemainingTimeoutFromSession`.

If the SDK does not expose a remaining timeout (for example, if the session metadata is stale), the system falls back to `DEFAULT_RECONNECT_TIMEOUT_MS` (5 minutes). This ensures the UI can still display a valid countdown even when exact session metadata is unavailable.

```typescript
// Calculate expiry for database storage
const expiresAt = startTime ? startTime + remainingTimeout : undefined;

```

This approach maintains continuity—if a user disconnects with 10 minutes remaining, they reconnect with approximately 10 minutes left, not a full reset.

## Error Handling and Cleanup

Network partitions and VM terminations require graceful degradation. The reconnect route uses `isSandboxUnavailableError` from [`apps/web/lib/sandbox/utils.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/sandbox/utils.ts) to detect transient failures such as HTTP 410 (Gone) or 404 (Not Found) responses from the sandbox probe.

When an unavailable error occurs:

1. The handler calls `clearUnavailableSandboxState` to remove stale references from the session record.
2. The session lifecycle updates to **hibernated** via `buildHibernatedLifecycleUpdate`.
3. The API returns `status: "expired"` so the UI can prompt for a fresh sandbox or snapshot restoration.

```typescript
// Error handling pattern in route.ts
try {
  const probe = await sandbox.exec("pwd", sandbox.workingDirectory, 15_000);
} catch (error) {
  if (isSandboxUnavailableError(error.message)) {
    await clearUnavailableSandboxState(sessionId);
    return Response.json({ status: "expired" });
  }
  throw error;
}

```

This pattern prevents "ghost" sessions where the database claims a sandbox exists but the underlying VM has been garbage collected.

## Client-Side Implementation

To implement reconnection in your frontend, call the API endpoint when the component mounts or when the user explicitly requests to resume.

```typescript
// Example: Reconnect from a React component
async function reconnect(sessionId: string) {
  const resp = await fetch(`/api/sandbox/reconnect?sessionId=${sessionId}`);
  const data = await resp.json() as {
    status: "connected" | "no_sandbox" | "expired";
    expiresAt?: number;
    hasSnapshot: boolean;
    lifecycle: LifecyclePayload;
  };

  switch (data.status) {
    case "connected":
      // Sandbox is alive - store the expiresAt for your countdown UI
      console.log(`Reconnected. Expires at: ${new Date(data.expiresAt!)}`);
      break;
      
    case "no_sandbox":
      // No runtime VM available - offer to create new or restore from snapshot
      break;
      
    case "expired":
      // VM terminated while offline - show warning and recovery options
      break;
  }
  
  return data;
}

```

The `expiresAt` field enables you to synchronize countdown timers across reconnections, while the `lifecycle` object provides metadata for displaying hibernation status or error states.

## Summary

- **Persist state early**: Store the `VercelState` object (containing `sandboxName` and `expiresAt`) in your session database immediately after sandbox creation.
- **Use the connect method**: Reattach to existing VMs using `VercelSandbox.connect(sandboxName, { remainingTimeout })` rather than creating new instances.
- **Probe for liveness**: Execute a lightweight command like `pwd` to verify the VM is responsive before declaring the session resumed.
- **Handle expiration gracefully**: Implement `isSandboxUnavailableError` checks to clear stale state and return clear status codes (`expired`, `no_sandbox`) to the client.
- **Preserve timeouts**: Pass the `remainingTimeout` from the previous session to maintain consistent resource limits across disconnections.

## Frequently Asked Questions

### What happens if the sandbox expired while the user was offline?

If the underlying VM was terminated due to timeout or infrastructure cleanup, the probe in [`apps/web/app/api/sandbox/reconnect/route.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/app/api/sandbox/reconnect/route.ts) will fail with an unavailable error. The handler catches this, clears the stale `sandboxState` from the database using `clearUnavailableSandboxState`, and returns `status: "expired"`. Your frontend should detect this status and offer to create a new sandbox or restore from a snapshot.

### How long can a session remain disconnected before it expires?

The maximum disconnection time depends on the `remainingTimeout` calculated when the sandbox was created or last reconnected. The fallback `DEFAULT_RECONNECT_TIMEOUT_MS` is 5 minutes, but your specific implementation may configure longer windows through the Vercel Sandbox SDK limits. The `expiresAt` timestamp returned by the reconnect API reflects the exact millisecond when the session will terminate.

### Can I reconnect to a sandbox from a different device or browser?

Yes. Since the `sandboxName` is the only required identifier and it is stored in your centralized session database (not browser local storage), any client that authenticates as the same user and session can retrieve the `VercelState` and call `VercelSandbox.connect`. The VM runs in the cloud, independent of the client device.

### What is the difference between the `resume` and `reconnect` options?

**Reconnect** (`VercelSandbox.connect`) attaches to an existing running or stopped sandbox by name, optionally calculating a new timeout. **Resume** specifically refers to waking a stopped sandbox from hibernation. In the `connect` method options, setting `resume: true` instructs the SDK to explicitly restart a stopped VM, whereas the default `resume: false` simply attaches to whatever state exists (running or stopped) without modification.