A sandbox gives the agent a filesystem and a shell. chat() alone can only talk about code.
A sandbox gives the agent a real computer to work in: a cloned repo, a shell, and processes. The agent runs inside it, and its edits, commands, and tool calls stream back to you like any other chat() run.
This runs a Grok Build agent in a Docker container. You need Docker running and an XAI_API_KEY.
npm i @tanstack/ai @tanstack/ai-grok-build @tanstack/ai-sandbox @tanstack/ai-sandbox-dockerpnpm add @tanstack/ai @tanstack/ai-grok-build @tanstack/ai-sandbox @tanstack/ai-sandbox-dockeryarn add @tanstack/ai @tanstack/ai-grok-build @tanstack/ai-sandbox @tanstack/ai-sandbox-dockerbun add @tanstack/ai @tanstack/ai-grok-build @tanstack/ai-sandbox @tanstack/ai-sandbox-dockerimport { chat } from '@tanstack/ai'
import { GROK_CLI_INSTALL_COMMAND, grokBuildText } from '@tanstack/ai-grok-build'
import {
createSecrets,
defineSandbox,
defineWorkspace,
githubRepo,
withSandbox,
} from '@tanstack/ai-sandbox'
import { dockerSandbox } from '@tanstack/ai-sandbox-docker'
const repoSandbox = defineSandbox({
id: 'repo-agent',
provider: dockerSandbox({ image: 'node:22' }),
workspace: defineWorkspace({
source: githubRepo({ repo: 'TanStack/ai' }),
setup: [GROK_CLI_INSTALL_COMMAND],
secrets: createSecrets({
XAI_API_KEY: process.env.XAI_API_KEY ?? '',
}),
}),
})
const stream = chat({
threadId: 'overview-demo',
adapter: grokBuildText('grok-build'),
messages: [{ role: 'user', content: 'List the packages in this repo.' }],
middleware: [withSandbox(repoSandbox)],
})
for await (const chunk of stream) {
if (chunk.type === 'TEXT_MESSAGE_CONTENT') process.stdout.write(chunk.delta)
}The agent clones the repo into a container, reads it, and streams its answer to your terminal. Your host filesystem stays untouched.
For a full walkthrough that fixes a bug and prints the diff, go to Quick Start.
A sandboxed run has three parts. You can change one without changing the others.
| Part | What it decides | You pick it with |
|---|---|---|
| Provider | Where the agent runs: your host, a container, a microVM, or a cloud VM. | A provider such as dockerSandbox, sbxSandbox, or localProcessSandbox. See Providers. |
| Workspace | What the agent sees: the repo, the package manager, the setup commands, and the secrets. | defineWorkspace({ ... }). See Workspace. |
| Harness adapter | Which agent runs, and how its output turns into chat chunks. | grokBuildText, claudeCodeText, codexText, opencodeText, or acpCompatible. See Harnesses. |
defineSandbox() binds a provider and a workspace into one reusable definition. You can also add a policy, a lifecycle, and hooks. withSandbox(definition) is the chat() middleware that turns the sandbox on for a run.
A run goes through three steps:
chat({ adapter: grokBuildText(), middleware: [withSandbox(repoSandbox)] })
|
|- withSandbox.setup get a sandbox: resume it, or restore a snapshot, or create and bootstrap
|- adapter.chatStream start `grok` INSIDE the sandbox, stream its events back as AG-UI chunks
'- withSandbox.onFinish snapshot or destroy, as the lifecycle saysEvery harness adapter declares requires: [SandboxCapability]. If no middleware gives it a sandbox, chat() fails at the call site. A coding agent never runs without a place to run.
The provider decides where the agent runs, not how it signs in. The default authMode is 'api-key'. Set 'host' when the machine already has a CLI login. See Harness Auth.
Use a sandbox when the agent must act on a real codebase:
If the model only needs to read code that you already have in memory, you do not need a sandbox. A normal chat() with tools is enough.
Start here:
Add these when you need them:
For production:
The Sandboxes (Advanced) group in the sidebar has the rest: the run journal, takeover, reaping, provisioning, observability, Cloudflare, and the provider details.