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.
Add lifecycle to defineSandbox:
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.
| Field | What 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'. |
| snapshotMaxAge | A duration such as '24h' or '30m'. An older record is treated as stale and created again. |
| destroyOnComplete | When true, the sandbox is destroyed after the run completes. When false (default), the next run can resume it. |
| keepAlive | A 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. |
When a run needs a sandbox, withSandbox tries these in order:
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.
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.
A sandbox is found by a compound key:
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.
keepAlive and destroyOnComplete apply when a run completes. They never keep a sandbox alive through an abort.
See Takeover & Detached Runs and Durable Runs.