`sandbox.yao` lives in your assistant directory alongside `package.yao`. Its presence activates CLI Agent mode. The framework reads it at load time, creates a container, and routes execution through Claude Code.
## Minimal Configuration
```json
{
"version": "2.0",
"computer": {
"image": "yaoapp/tai-sandbox-claude:latest"
},
"runner": {
"name": "claude"
}
}
```
`version: "2.0"` is required — it distinguishes the v2 format from the legacy v1 `package.yao` sandbox field.
## Full Reference
```json
{
"version": "2.0",
"computer": {
"image": "yaoapp/tai-sandbox-claude:latest",
"memory": "4GB",
"cpus": 2,
"ports": [3000, 8080],
"user": "sandbox",
"work_dir": "/workspace",
"mount_path": "/workspace",
"mount_mode": "rw",
"vnc": {
"enabled": true,
"password": "$ENV.VNC_PASSWORD",
"resolution": "1920x1080",
"view_only": false
}
},
"runner": {
"name": "claude",
"mode": "cli",
"options": {
"max_turns": 50,
"permission_mode": "bypassPermissions"
}
},
"lifecycle": "session",
"idle_timeout": "30m",
"max_lifetime": "24h",
"stop_timeout": "5s",
"filter": {
"kind": "box"
},
"prepare": [
{ "action": "exec", "cmd": "npm install -g typescript", "once": true },
{ "action": "exec", "cmd": "pip install ruff", "ignore_error": true }
],
"environment": {
"MY_VAR": "value",
"GOPATH": "$ENV.GOPATH"
},
"secrets": {
"GITHUB_TOKEN": "$ENV.GITHUB_TOKEN",
"NPM_TOKEN": "$ENV.NPM_TOKEN"
}
}
```
---
## `computer` Block
Declares the execution environment. The framework creates a container matching these specs on an available Tai node.
| Field | Type | Description |
|-------|------|-------------|
| `image` | string | Container image. Required for `box` kind. |
| `memory` | string | Memory limit: `"4GB"`, `"512MB"`, etc. |
| `cpus` | number | CPU limit (same as Docker `--cpus`). |
| `ports` | array | Ports to expose. Integer array or object array. |
| `user` | string | User inside the container. |
| `work_dir` | string | Working directory. Default: `/workspace`. |
| `mount_path` | string | Where the workspace volume is mounted. Default: same as `work_dir`. |
| `mount_mode` | string | Mount mode: `"rw"` (default) or `"ro"`. |
| `vnc` | bool or object | Enable VNC / desktop. `true` is shorthand for `{ "enabled": true }`. |
**Port formats** — both are valid:
```json
"ports": [3000, 8080]
"ports": [
{ "port": 3000, "host_port": 9000, "protocol": "tcp" }
]
```
**VNC object fields:** `enabled`, `password`, `resolution` (e.g. `"1920x1080"`), `view_only`.
---
## `runner` Block
Selects which Runner handles execution.
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Runner name. Currently: `"claude"` (also `"claude/cli"`). |
| `mode` | string | `"cli"` (default) or `"service"`. |
| `options` | object | Runner-specific options. |
**Claude runner options:**
| Option | Description |
|--------|-------------|
| `max_turns` | Maximum number of autonomous turns Claude Code will take. |
| `permission_mode` | `"bypassPermissions"` skips Claude's interactive permission prompts. |
| `disallowed_tools` | Array of tool names to disable (e.g. `["WebSearch"]`). |
| `allowed_tools` | Array of tool names to enable exclusively. |
---
## `lifecycle` Field
Controls how containers are managed between requests. Default: `"oneshot"`.
| Value | Behavior | Use when |
|-------|----------|---------|
| `oneshot` | A new container is created for every request. | Stateless tasks, CI-style runs. |
| `session` | Same container is reused within the same chat session (`chatID`). A new chat starts a fresh container. | Interactive development, multi-turn tasks. |
| `longrunning` | Container is shared across sessions for the same user + assistant + workspace. Destroyed after `idle_timeout`. | Long projects, persistent dev environments. |
| `persistent` | Container never destroyed automatically. | Shared environments, permanent workspaces. |
`idle_timeout`, `max_lifetime`, and `stop_timeout` accept Go duration strings: `"30m"`, `"2h"`, `"24h"`. The default `stop_timeout` is `"2s"`.
---
## `prepare` Steps
Steps run after the container starts, before the Create Hook. Use them to copy configuration files or install tools.
> **Skills are copied automatically.** If your assistant directory contains a `skills/` folder, the framework copies it to the container during `Runner.Prepare` — no `prepare` step needed.
All steps share three common fields:
| Field | Default | Description |
|-------|---------|-------------|
| `once` | `false` | Skip if the prepare set already ran with the same config hash. All `once: true` steps are skipped together when the hash matches; they all re-run when configuration changes. |
| `ignore_error` | `false` | Continue to next step even if this one fails. |
**`copy`** — Copy a file or directory from the assistant directory into the workspace:
```json
{ "action": "copy", "src": "config/settings.json", "dst": ".config/settings.json" }
```
- `src` is relative to the assistant directory on the host
- `dst` is relative to the workspace root inside the container (`~/` expands to workspace root)
**`exec`** — Run a shell command inside the container:
```json
{ "action": "exec", "cmd": "npm install -g typescript", "once": true }
```
Additional field for `exec`:
| Field | Default | Description |
|-------|---------|-------------|
| `background` | `false` | Run in the background (non-blocking). Useful for starting servers. |
---
## `environment` and `secrets`
Both inject environment variables into the container. Use `$ENV.KEY` to reference host environment variables.
```json
"environment": {
"NODE_ENV": "production",
"API_BASE": "$ENV.API_BASE_URL"
},
"secrets": {
"GITHUB_TOKEN": "$ENV.GITHUB_TOKEN"
}
```
`secrets` keys override `environment` keys with the same name. In practice, put sensitive values in `secrets` and non-sensitive values in `environment`.
> **Planned:** In a future version, `secrets` values will be protected at rest — the agent will not be able to read them as plaintext, but they will still be available to the container at runtime.
---
## `filter` Block
Constrains which Tai node the container runs on. All fields are optional.
```json
"filter": {
"kind": "box",
"min_cpus": 4,
"min_mem": "8GB",
"os": "linux",
"arch": "amd64"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `kind` | string or array | `"box"` (Docker/K8s), `"host"` (bare host), or `["box", "host"]` for either. |
| `image` | string | Require a node that already has this image cached. |
| `vnc` | bool | Require a node with VNC support. |
| `os` | string | Target OS, e.g. `"linux"`, `"windows"`. |
| `arch` | string | CPU architecture, e.g. `"amd64"`, `"arm64"`. |
| `min_cpus` | number | Minimum available CPUs. |
| `min_mem` | string | Minimum available memory, e.g. `"8GB"`. |
| `labels` | object | Match nodes by custom labels. |
---
## Image Reference
| Image | Desktop | VNC | Use case |
|-------|---------|-----|---------|
| `yaoapp/tai-sandbox-claude:latest` | No | No | Code generation, file operations |
| `yaoapp/tai-sandbox-claude-desktop-lite:latest` | Lite (XFCE + Chromium) | Yes | Web automation, light GUI tasks |
| `yaoapp/tai-sandbox-claude-desktop:latest` | Full (XFCE4 + Chromium + CJK) | Yes | Heavy GUI, visual verification |
For VNC-enabled images, set `computer.vnc: true` to start the display server inside the container.
---
## What's Next
The container is running. Learn what "Computer" means and how to interact with it from Hooks.
→ **[Computer](./computer)**