yao/sandbox/README.md

151 lines
6.2 KiB
Markdown

# 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
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Yao Server │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Sandbox Manager │ │
│ │ │ │
│ │ - GetOrCreate(userID, chatID) → container │ │
│ │ - Exec/Stream commands in container │ │
│ │ - Filesystem operations (read, write, copy) │ │
│ │ │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Container │ │ Container │ │ Container │ │
│ │ (user1) │ │ (user2) │ │ (user3) │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
│ ──────┴───────────────┴───────────────┴──── │
│ Unix Socket IPC │
│ (one socket per container) │
└───────────────────────────────────────────────────────────────┘
```
## Quick Start
### Build Docker Images
```bash
cd sandbox/docker
./build.sh claude
```
### Usage
```go
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 |
## Docker Images
| Image | Description |
| --------------------------- | ------------------------------------- |
| `yao/sandbox-base:latest` | Base image with git, curl, yao-bridge |
| `yao/sandbox-claude:latest` | + Claude CLI, Node.js 20, Python 3.11 |
| `yao/sandbox-claude:full` | + Go 1.23 |
## 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` - Handshake
- `tools/list` - List available tools
- `tools/call` - Execute a tool
## Directory Structure
```
sandbox/
├── bridge/ # yao-bridge source
├── docker/ # Dockerfiles and build script
│ ├── base/
│ ├── claude/
│ └── build.sh
├── ipc/ # IPC system
│ ├── manager.go
│ ├── session.go
│ └── types.go
├── config.go # Configuration
├── errors.go # Error types
├── helpers.go # Helper functions
├── manager.go # Main manager
└── types.go # Type definitions
```
## Testing
```bash
# Unit tests (no Docker required)
go test -v ./sandbox/... -run "^Test.*Validation|^Test.*Generation|^Test.*Parsing"
# All tests (requires Docker)
go test -v ./sandbox/...
```
## Security
- Containers run as non-root user
- `--cap-drop ALL` removes all capabilities
- `no-new-privileges` prevents privilege escalation
- Only workspace directory is mounted
- Per-session IPC sockets with authorized tools only