Skip to content

Persistent Sandboxes

Persistent sandboxes let your agent maintain state across multiple user messages. Instead of creating and destroying a fresh sandbox on every invocation, the sandbox is paused after each execution and resumed on the next — preserving the filesystem, installed packages, cloned repos, and any running processes.

When to Use Persistent Sandboxes

Use persistent sandboxes when your agent needs to:

  • Maintain filesystem state — a coding agent that clones a repo and makes incremental changes
  • Preserve authentication — CLI tools that store credentials on disk (e.g., gh auth, claude auth)
  • Accumulate context — an agent that builds up a working environment over multiple interactions
  • Run long-lived processes — background services that the agent interacts with across messages

Don't use persistent sandboxes for simple request/response tools (API calls, data lookups, one-shot transformations). The default ephemeral sandbox is faster and cheaper for these cases.

How It Works

Lifecycle

First message:    CREATE sandbox → EXECUTE handler → PAUSE sandbox

Second message:   RESUME sandbox → EXECUTE handler → PAUSE sandbox

Third message:    RESUME sandbox → EXECUTE handler → PAUSE sandbox

End session:      RESUME sandbox → EXECUTE handler → KILL sandbox
                  (handler sets requestKill: true)

What's Preserved

Preserved across pause/resumeNot preserved after kill
Filesystem (files, cloned repos)Everything
Installed packages
Auth credentials on disk
Environment variables
Session metadata (in DB)Session metadata is preserved

Billing

  • While running: Standard E2B compute rates apply
  • While paused: Free — no compute charges
  • Auto-cleanup: Sessions paused longer than maxSessionAge are automatically killed

Setup

1. Add runtime.persistent to Your Manifest

yaml
manifest:
  trigger: auto
  runtime:
    persistent: true
    timeoutMs: 300000       # 5 min per invocation
    maxSessionAge: 86400000 # auto-kill after 24 hours
  tool:
    name: my_agent
    description: An agent with persistent state
    parameters:
      type: object
      properties:
        prompt:
          type: string
          description: What to do
      required: [prompt]

2. Use Session Metadata in Your Handler

Session metadata lets your handler track state across invocations. It's stored in the database and available even after a sandbox is killed.

js
async function handler(input, context) {
  const meta = await context.getSessionMetadata();

  if (!meta.initialized) {
    // First invocation — set up the environment
    // Clone repo, install dependencies, etc.
    await context.updateSessionMetadata({
      initialized: true,
      setupAt: Date.now(),
    });
  }

  // Do work...

  await context.updateSessionMetadata({
    lastRun: Date.now(),
    totalRuns: (meta.totalRuns || 0) + 1,
  });

  return { result: 'Done' };
}

module.exports = { handler };

3. Implement Session End

Let users end their session by setting the requestKill metadata flag:

js
if (input.action === 'end_session') {
  await context.updateSessionMetadata({ requestKill: true });
  return { result: 'Session ended.' };
}

After execution completes, the platform will kill the sandbox instead of pausing it.

4. (Optional) Create a Custom Sandbox Template

If your agent needs specific CLI tools or packages pre-installed, create a sandbox template:

  1. Go to Developer Portal → Templates tab
  2. Enter a setup script:
bash
npm install -g @anthropic-ai/claude-code
apt-get install -y git curl jq
mkdir -p /home/user/workspace
  1. Click Create & Build Template (builds in 1-3 minutes)
  2. Assign the template to your agent in Settings → Sandbox Template

Complete Example

Here's a minimal persistent agent that maintains a counter across messages:

yaml
---
name: counter-agent
displayName: Persistent Counter
tier: tool
version: 1.0.0
manifest:
  trigger: auto
  runtime:
    persistent: true
    timeoutMs: 60000
  tool:
    name: counter
    description: A counter that persists across messages
    parameters:
      type: object
      properties:
        action:
          type: string
          enum: [increment, get, reset, end]
          description: What to do with the counter
      required: [action]
---

async function handler(input, context) {
  const { action } = input;
  const meta = await context.getSessionMetadata();

  if (action === 'end') {
    await context.updateSessionMetadata({ requestKill: true });
    return { result: 'Counter session ended.', finalCount: meta.count || 0 };
  }

  if (action === 'reset') {
    await context.updateSessionMetadata({ count: 0 });
    return { result: 'Counter reset to 0.' };
  }

  if (action === 'increment') {
    const newCount = (meta.count || 0) + 1;
    await context.updateSessionMetadata({ count: newCount });
    return { result: `Counter incremented to ${newCount}.`, count: newCount };
  }

  // action === 'get'
  return { count: meta.count || 0 };
}

module.exports = { handler };

Context Preservation

When a session is killed (either by the user or by TTL), the sandbox filesystem is lost. However, session metadata persists in the database. Use this to restore context in new sessions:

js
const meta = await context.getSessionMetadata();

if (meta.previousSessionContext) {
  // Use the saved context to inform the new session
  console.log('Restoring context:', meta.previousSessionContext);
}

Before ending a session, save a summary:

js
if (action === 'end_session') {
  await context.updateSessionMetadata({
    requestKill: true,
    previousSessionContext: `Worked on repo ${meta.repoUrl}. Last task: ${meta.lastTask}.`,
  });
  return { result: 'Session ended.' };
}

API Reference

APIDescription
context.getSessionMetadata()Read session metadata
context.updateSessionMetadata(data)Merge data into session metadata
manifest.runtime.persistentEnable persistent sandboxes
manifest.runtime.timeoutMsPer-invocation timeout
manifest.runtime.maxSessionAgeAuto-cleanup TTL

Built with VitePress