- Add new gRPC endpoint for Heartbeat in the Yao service, enabling communication with the sandbox. - Update Makefile to include a dedicated unit test target for Sandbox V2, ensuring proper testing of new features. - Enhance CI workflows to incorporate Sandbox V2 tests, allowing for dual-mode testing (local and remote) with Docker. - Modify .gitignore to exclude specific Docker files while allowing shell scripts for Sandbox V2. - Update documentation in DESIGN.md to reflect the new architecture and capabilities of the Sandbox V2. These changes enhance the Yao SDK's functionality, providing improved support for sandbox operations and testing. |
||
|---|---|---|
| .. | ||
| bridge | ||
| docker | ||
| ipc | ||
| proxy | ||
| v2 | ||
| vncproxy | ||
| config.go | ||
| config_test.go | ||
| DESIGN-PLAYWRIGHT-VNC.md | ||
| DESIGN.md | ||
| errors.go | ||
| helpers.go | ||
| helpers_test.go | ||
| manager.go | ||
| manager_test.go | ||
| PLAN.md | ||
| README.md | ||
| SPEC.md | ||
| types.go | ||
Yao Sandbox
Sandbox provides persistent Docker containers as isolated execution environments for external CLI agents like Claude Code.
Overview
The sandbox module enables Yao to safely run external AI coding agents (like Claude CLI) in isolated Docker containers. Each user+chat session gets its own container with:
- Persistent workspace for code and dependencies
- IPC communication via Unix sockets
- Resource limits (CPU, memory)
- Security isolation
- VNC remote desktop for visual transparency (optional)
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Yao Server │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Sandbox Manager │ │
│ │ │ │
│ │ - GetOrCreate(userID, chatID) → container │ │
│ │ - Exec/Stream commands in container │ │
│ │ - Filesystem operations (read, write, copy) │ │
│ │ │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────┴────────────────────────────────┐ │
│ │ VNC Proxy Service │ │
│ │ │ │
│ │ - GET /v1/sandbox/{id}/vnc → VNC status │ │
│ │ - GET /v1/sandbox/{id}/vnc/client → noVNC page │ │
│ │ - GET /v1/sandbox/{id}/vnc/ws → WebSocket proxy │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ sandbox- │ │ sandbox- │ │ sandbox- │ │ sandbox- │ │
│ │ claude │ │ browser │ │ desktop │ │ chrome │ │
│ │ (No VNC) │ │ (VNC) │ │ (VNC) │ │ (VNC) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ─────┴────────────┴────────────┴────────────┴──── │
│ Unix Socket IPC │
│ (one socket per container) │
└───────────────────────────────────────────────────────────────┘
Quick Start
Build Docker Images
cd sandbox/docker
# Build base image
./build.sh claude
# Build VNC-enabled images
./build.sh browser # Browser (Playwright) + Fluxbox + VNC
./build.sh desktop # XFCE Desktop + VNC
./build.sh chrome # Real Chrome + CDP + VNC (amd64 only)
# Build all images
./build.sh all
Usage
import "github.com/yaoapp/yao/sandbox"
// Create manager
config := sandbox.DefaultConfig()
config.Init("/path/to/yao/data")
manager, err := sandbox.NewManager(config)
if err != nil {
log.Fatal(err)
}
defer manager.Close()
// Get or create container
container, err := manager.GetOrCreate(ctx, "user123", "chat456")
if err != nil {
log.Fatal(err)
}
// Execute command
result, err := manager.Exec(ctx, container.Name, []string{"echo", "hello"}, nil)
fmt.Println(result.Stdout) // "hello\n"
// Write file
err = manager.WriteFile(ctx, container.Name, "/workspace/test.txt", []byte("content"))
// Read file
data, err := manager.ReadFile(ctx, container.Name, "/workspace/test.txt")
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
YAO_SANDBOX_IMAGE |
yao/sandbox-claude:latest |
Docker image |
YAO_SANDBOX_WORKSPACE |
{YAO_DATA_ROOT}/sandbox/workspace |
Workspace directory |
YAO_SANDBOX_IPC |
{YAO_DATA_ROOT}/sandbox/ipc |
IPC socket directory |
YAO_SANDBOX_MAX |
100 |
Max concurrent containers |
YAO_SANDBOX_IDLE_TIMEOUT |
30m |
Idle timeout |
YAO_SANDBOX_MEMORY |
2g |
Memory limit |
YAO_SANDBOX_CPU |
1.0 |
CPU limit |
YAO_SANDBOX_VNC_PORT_MAPPING |
false |
Enable VNC port mapping (for Docker Desktop) |
Docker Desktop (macOS/Windows)
Docker Desktop runs containers in a LinuxKit VM, so container IPs are not directly accessible from the host. Enable VNC port mapping for local development:
export YAO_SANDBOX_VNC_PORT_MAPPING=true
export YAO_SANDBOX_IMAGE="yaoapp/sandbox-claude-browser:latest"
When enabled, VNC ports (6080, 5900) are automatically mapped to random available host ports on 127.0.0.1.
Docker Images
| Image | VNC | Description |
|---|---|---|
yaoapp/sandbox-base:latest |
❌ | Base image with git, curl, yao-bridge |
yaoapp/sandbox-claude:latest |
❌ | + Claude CLI, Node.js 20, Python 3.11 |
yaoapp/sandbox-claude:full |
❌ | + Go 1.23 |
yaoapp/sandbox-claude-browser:latest |
✅ | + Playwright, Fluxbox, VNC (~3.4GB) |
yaoapp/sandbox-claude-desktop:latest |
✅ | + XFCE Desktop, VNC (~3.1GB) |
yaoapp/sandbox-claude-chrome:latest |
✅ | + Real Chrome, CDP, PyAutoGUI, VNC (~3.4GB, amd64 only) |
IPC Communication
Sandbox containers communicate with Yao via Unix sockets using the MCP (Model Context Protocol) JSON-RPC format. The yao-bridge binary inside containers bridges stdio ↔ socket.
Supported methods:
initialize- Handshaketools/list- List available toolstools/call- Execute a tool
VNC Remote Desktop
VNC-enabled images (playwright, desktop) provide real-time visibility into Claude's operations.
API Endpoints
| Endpoint | Description |
|---|---|
GET /v1/sandbox/{id}/vnc |
VNC status (ready/starting/unavailable) |
GET /v1/sandbox/{id}/vnc/client |
noVNC HTML client page |
GET /v1/sandbox/{id}/vnc/ws |
WebSocket proxy to container VNC |
View Modes
- Interactive (default): User can use keyboard and mouse
- View-only (
?viewonly=true): User can only watch
For detailed design, see DESIGN-PLAYWRIGHT-VNC.md.
Directory Structure
sandbox/
├── bridge/ # yao-bridge source
├── docker/ # Dockerfiles and build script
│ ├── base/
│ ├── claude/
│ ├── browser/ # Browser (Playwright) + VNC image
│ ├── desktop/ # XFCE Desktop + VNC image
│ ├── chrome/ # Real Chrome + CDP + VNC image (amd64 only)
│ │ ├── config/ # Chrome preferences, stealth scripts
│ │ └── tests/ # LLM-driven browser automation demos
│ ├── vnc/ # Shared VNC scripts
│ └── build.sh
├── ipc/ # IPC system
│ ├── manager.go
│ ├── session.go
│ └── types.go
├── vncproxy/ # VNC proxy service
│ ├── proxy.go
│ ├── config.go
│ └── proxy_test.go
├── config.go # Configuration
├── errors.go # Error types
├── helpers.go # Helper functions
├── manager.go # Main manager
└── types.go # Type definitions
Testing
# Load environment variables first
source env.local.sh
# Unit tests (no Docker required)
go test -v ./sandbox/... -run "^Test.*Validation|^Test.*Generation|^Test.*Parsing"
# All tests (requires Docker)
go test -v ./sandbox/...
# VNC proxy tests only
go test -v ./sandbox/vncproxy/...
Security
- Containers run as non-root user
--cap-drop ALLremoves all capabilitiesno-new-privilegesprevents privilege escalation- Only workspace directory is mounted
- Per-session IPC sockets with authorized tools only