A **Computer** is the execution environment where Claude Code runs. It is an abstraction that can be either a container (Box) or a host machine (Host).
```
Computer
├── Box — Docker/Kubernetes container on a Tai node
└── Host — bare host machine, no container
```
The framework selects and manages the Computer automatically based on `sandbox.yao`. Your Hooks interact with it through `ctx.computer`.
## How a Computer is Created
When a request arrives:
1. The framework reads `sandbox.yao`
2. It finds an available **Tai node** that matches the `filter` constraints
3. Based on `lifecycle`, it either creates a new container or reuses an existing one
4. The container starts with the configured image, mounts the workspace volume, and runs `entrypoint.sh`
The `entrypoint.sh` inside the container automatically starts:
- `tai relay` — gRPC bridge back to Yao
- `tai a2o` — OpenAI-compatible proxy for Claude's model calls
- VNC stack (if `computer.vnc` is enabled): Xvfb, window manager, x11vnc, websockify
You do not configure any of this manually. It happens when the container image starts.
## `ctx.computer` API
`ctx.computer` is available in both Create and Next Hooks when a sandbox is configured.
### `ctx.computer.id`
The Computer ID — Box ID for containers, Node ID for host machines.
```typescript
const id = ctx.computer.id;
```
### `ctx.computer.Exec(cmd)`
Execute a command inside the container. Returns `{ stdout, stderr, exit_code }`.
```typescript
// String form (split on whitespace)
const result = ctx.computer.Exec("git status");
// Array form (exact arguments, no shell expansion)
const result = ctx.computer.Exec(["git", "log", "--oneline", "-10"]);
if (result.exit_code !== 0) {
ctx.Send(`Command failed: ${result.stderr}`);
}
ctx.Send(result.stdout);
```
### `ctx.computer.VNC()`
Returns the VNC WebSocket URL for this container. Throws if VNC is not available on the node.
Only call this when `computer.vnc` is enabled in `sandbox.yao` and the image supports VNC.
```typescript
const vncUrl = ctx.computer.VNC();
// e.g. "ws://tai-node:16080/vnc/container-id"
ctx.Send({
type: "action",
props: {
name: "navigate",
payload: { route: `/sandbox/vnc?url=${encodeURIComponent(vncUrl)}` },
},
});
```
### `ctx.computer.Proxy(port, path?)`
Returns a proxied URL to access a service running inside the container.
```typescript
// Access a dev server on port 3000
const devUrl = ctx.computer.Proxy(3000);
// Access a specific path
const apiUrl = ctx.computer.Proxy(8080, "/api/health");
ctx.Send(`Preview: ${devUrl}`);
```
Use this after a `prepare` exec step starts a background server, or after Claude Code starts a dev server.
### `ctx.computer.Info()`
Returns metadata about the Computer.
```typescript
const info = ctx.computer.Info();
// {
// kind: "box",
// node_id: "node-abc",
// tai_id: "tai-xyz",
// status: "online",
// system: { os: "linux", arch: "amd64", hostname: "...", num_cpu: 4, shell: "/bin/sh" },
// box_id: "box-123", // box only
// container_id: "sha256...", // box only
// image: "yaoapp/tai-sandbox-claude:latest", // box only
// policy: "session" // box only
// }
```
---
## VNC and Desktop Environments
When `computer.vnc` is enabled, the container runs a full Linux desktop accessible via VNC or a browser-based noVNC client.
### Enable VNC
```json
{
"computer": {
"image": "yaoapp/tai-sandbox-claude-desktop-lite:latest",
"vnc": {
"enabled": true,
"resolution": "1920x1080",
"password": "$ENV.VNC_PASSWORD"
}
}
}
```
Shorthand — `"vnc": true` is equivalent to `"vnc": { "enabled": true }`.
### Choosing an Image
| Image | Desktop | Chromium | When to use |
|-------|---------|----------|-------------|
| `yaoapp/tai-sandbox-claude:latest` | No | No | Code generation, file operations, no GUI needed |
| `yaoapp/tai-sandbox-claude-desktop-lite:latest` | XFCE (lite) | Yes | Web automation, screenshot tasks, light GUI |
| `yaoapp/tai-sandbox-claude-desktop:latest` | XFCE4 (full) + CJK fonts | Yes | Heavy GUI tasks, visual verification, CJK content |
### Accessing the Desktop from a Hook
```typescript
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
// VNC() throws if VNC is not available — only call when vnc is enabled in sandbox.yao
const vncUrl = ctx.computer.VNC();
ctx.Send({
type: "action",
props: {
name: "navigate",
payload: { route: `/sandbox/vnc?url=${encodeURIComponent(vncUrl)}` },
},
});
return { messages };
}
```
---
## Port Proxying
If Claude Code or a prepare step starts a server inside the container, access it via `ctx.computer.Proxy`:
```json
{
"computer": {
"ports": [3000]
},
"prepare": [
{
"action": "exec",
"cmd": "npm run dev",
"background": true
}
]
}
```
```typescript
// Next Hook — after Claude Code finishes
const previewUrl = ctx.computer.Proxy(3000);
ctx.Send(`App running at: ${previewUrl}`);
```
---
## Workspace Volume
Every Computer has a workspace volume mounted at `work_dir` (default `/workspace`). This is where Claude Code reads and writes files. The `ctx.workspace` API gives Hooks access to the same volume.
```
Container (/workspace) ←→ Tai volume ←→ ctx.workspace (in Hook)
```
Changes made by Claude Code in `/workspace` are immediately visible via `ctx.workspace.ReadFile(...)` in the Next Hook.
---
## What's Next
Your agent can run code and access the desktop. Now add reusable capabilities.
→ **[Skills](./skills)**