Skip to content
Sandboxes

Lifecycle & Snapshots

Reuse a sandbox across messages in a thread. A new sandbox clones the repo, installs dependencies, and runs setup again.

The lifecycle config lets you pay it once. Keep one sandbox per thread, take a snapshot after setup, and resume on the next run.

Reuse a sandbox

Add lifecycle to defineSandbox:

ts
import { defineSandbox, defineWorkspace, githubRepo } from '@tanstack/ai-sandbox'
import { dockerSandbox } from '@tanstack/ai-sandbox-docker'

export const sandbox = defineSandbox({
  id: 'repo-agent',
  provider: dockerSandbox({ image: 'node:22' }),
  workspace: defineWorkspace({
    source: githubRepo({ repo: 'owner/app' }),
    setup: ['corepack enable', 'pnpm install'],
  }),
  lifecycle: {
    reuse: 'thread', // one sandbox per threadId
    snapshot: 'after-setup', // snapshot once bootstrap is done
    snapshotMaxAge: '24h', // make a new one when the snapshot is older
    destroyOnComplete: false, // keep the sandbox for the next run
  },
})

Pass the same threadId to chat() on each run. The first run bootstraps. Later runs resume the sandbox or restore the snapshot, and skip setup.

The lifecycle fields

FieldWhat it controls
reuse'thread' (default) keeps one sandbox per threadId. 'none' makes a fresh sandbox for each run.
snapshot'after-setup' takes a snapshot when bootstrap ends. 'after-run' takes one when a run completes. 'none' takes no snapshot. The default is 'after-setup' when the provider supports snapshots, else 'none'.
snapshotMaxAgeA duration such as '24h' or '30m'. An older record is treated as stale and created again.
destroyOnCompleteWhen true, the sandbox is destroyed after the run completes. When false (default), the next run can resume it.
keepAliveA duration hint such as '30m', for how long to keep the sandbox warm. @tanstack/ai-sandbox does not read it. It is a hint for providers and host apps. Provider idle timers are on the provider config.

How it works

When a run needs a sandbox, withSandbox tries these in order:

  1. Resume the running sandbox, if a live one exists for this key.
  2. Restore the latest snapshot, and skip setup.
  3. Create a fresh sandbox and bootstrap it: clone, install, run setup, then take a snapshot.

Each step falls through to the next only when it is not available. A warm thread starts almost at once. A cold one does the full bootstrap.

Providers without snapshot support (for example localProcessSandbox) skip the snapshot step with no error. Providers without a durable disk or snapshots create and bootstrap again on every run. See Providers (Advanced) for what each provider supports.

Note

Provider snapshots make bootstrap faster. To keep the finished files after the sandbox is gone, use Keep Files After Reload. Portable snapshots restore only into a new private sandbox, after the steps above. They never overwrite a live resumed sandbox.

Advanced

Stale snapshots

snapshotMaxAge takes '<n>h' (hours) or '<n>m' (minutes). When the stored record is older than this limit, the sandbox is treated as stale. It is created from scratch: setup runs again, and a new snapshot is taken. If you leave it unset, snapshots stay forever.

The sandbox instance key

A sandbox is found by a compound key:

plaintext
sandboxInstanceKey = hash(threadId + sandbox.id + provider + workspaceHash + tenant?)

The key includes the workspace hash, the provider name, and an optional tenant. So if you change the repo, the setup steps, the provider, or the tenant, you get a different key. Provider options such as the image are not in the key. If you change the image, also change sandbox.id to get a fresh sandbox. That starts a fresh sandbox, and you never resume a stale one that does not match your config. Keep those inputs the same across runs to keep hitting the same warm sandbox.

Providers without a durable disk or snapshots (for example ephemeral containers) keep the same key. But every run pays the bootstrap cost, because there is nothing durable to resume.

Completion, cancel, and disconnect

keepAlive and destroyOnComplete apply when a run completes. They never keep a sandbox alive through an abort.

  • Cancel: closing the agent's IO stream does not stop the agent process. So an explicit cancel always destroys the sandbox, whatever destroyOnComplete says. That is the only sure way to stop the agent from spending tokens.
  • Disconnect: Stop and a closed tab look the same to the server: the connection closes.
    • With durability wired, a disconnect does not complete or destroy anything. The sandbox stays up, the agent keeps working, and the run record is marked detached. A later request can take it over.
    • With no durability wired, a disconnect destroys the sandbox, the same as an abort.

See Takeover & Detached Runs and Durable Runs.