A sandbox gives a coding agent a real computer to work in: a filesystem, a shell, processes, and a cloned repository. You point a harness adapter (a coding-agent CLI like Grok Build) at it through chat(), and the agent's work (edits, commands, tool calls) streams back to you like any other chat run.
The same code runs on your laptop, in CI, in a Docker container, or on the edge. Only the provider changes.
import { chat } from '@tanstack/ai'
import { grokBuildText } from '@tanstack/ai-grok-build'
import {
createSecrets,
defineSandbox,
defineWorkspace,
githubRepo,
withSandbox,
} from '@tanstack/ai-sandbox'
import { dockerSandbox } from '@tanstack/ai-sandbox-docker'
import { messages, threadId } from './chat-context'
const repoSandbox = defineSandbox({
id: 'repo-agent',
provider: dockerSandbox({ image: 'node:22' }),
workspace: defineWorkspace({
source: githubRepo({ repo: 'TanStack/ai' }),
packageManager: 'pnpm',
setup: ['corepack enable', 'pnpm install'],
scripts: { test: 'pnpm test', typecheck: 'pnpm test:types' },
secrets: createSecrets({
XAI_API_KEY: process.env.XAI_API_KEY ?? '',
}),
}),
lifecycle: { reuse: 'thread', snapshot: 'after-setup', keepAlive: '30m' },
})
chat({
threadId,
adapter: grokBuildText('grok-build'),
messages,
middleware: [withSandbox(repoSandbox)],
})A sandboxed run is the composition of three independent pieces. You can change any one without touching the others.
| Part | What it is | You pick it with |
|---|---|---|
| Provider | Where the agent runs (your host, a container, a cloud VM). | A provider package (dockerSandbox, localProcessSandbox, …) |
| Workspace | What the agent sees: the source repo, package manager, setup commands, secrets. | defineWorkspace({ … }) |
| Harness adapter | Which agent runs and how its output is translated to chat chunks. | grokBuildText, claudeCodeText, codexText, opencodeText, or acpCompatible for any ACP agent |
defineSandbox() binds a provider + workspace (+ optional policy, lifecycle, and hooks) into a reusable definition. withSandbox(definition) is the chat() middleware that turns it on for a run.
chat({ adapter: grokBuildText(), middleware: [withSandbox(repoSandbox)] })
│
├─ withSandbox.setup → ensure the sandbox: resume → restore snapshot → create + bootstrap
├─ adapter.chatStream → spawn `grok` INSIDE the sandbox; stream its events back as AG-UI chunks
└─ withSandbox.onFinish → snapshot / destroy per the lifecycleA harness adapter declares requires: [SandboxCapability], so chat() fails fast at the call site if no middleware provides a sandbox, you can't accidentally run a coding agent with nowhere to run it.
Reach for a sandbox whenever you want an agent to act on a real codebase, not just talk about one. A few shapes this takes:
If you only need the model to read code you already have in memory, you don't need a sandbox, a normal chat() with tools is enough. The sandbox earns its keep the moment the agent needs a filesystem and a shell.
Quick Start gets an agent fixing a bug in a sandbox on your laptop. After that, pick the piece you need:
The Advanced group in the sidebar has the rest: the journal file, takeover from another host, the reaping sweeper, provisioning, observability, Cloudflare, and how to build an adapter.
Two runnable demos: