yao/sandbox/PLAN.md

486 lines
14 KiB
Markdown

# Sandbox Implementation Plan
## Overview
This document outlines the implementation plan for the Sandbox module, which provides persistent Docker containers for external CLI agents like Claude Code.
**Estimated Code**: ~1200 lines
---
## Phase 1: Core Interfaces & Types ✅ COMPLETED
### Goals
- Define all core types and interfaces
- Set up package structure
### Implemented Files
```
sandbox/
├── manager.go # Manager implementation
├── types.go # Container, ExecOptions, ExecResult, FileInfo
├── config.go # Configuration types
├── errors.go # Custom errors
├── helpers.go # Helper functions
└── ipc/
├── manager.go # IPC Manager
├── session.go # IPC Session
└── types.go # JSON-RPC types
```
### Completed
- [x] Create package structure
- [x] Define types in `types.go`
- `Config` struct
- `Container` struct
- `ExecOptions` struct
- `ExecResult` struct
- `FileInfo` struct
- [x] Define `Manager` in `manager.go`
- Container lifecycle: `GetOrCreate`, `Stop`, `Start`, `Remove`, `List`, `Cleanup`
- Command execution: `Stream`, `Exec`
- Filesystem: `WriteFile`, `ReadFile`, `ListDir`, `Stat`, `MkDir`, `RemoveFile`, `CopyToContainer`, `CopyFromContainer`
- [x] Define IPC types in `ipc/`
- `Session` struct
- `Manager` struct
- `AgentContext` struct
- `MCPTool` struct
- JSON-RPC request/response types
- [x] Unit tests for type helpers
---
## Phase 2: Docker Container Management ✅ COMPLETED
### Goals
- Implement container lifecycle management
- Handle container creation, start, stop, remove
### Completed
- [x] Initialize Docker client (`NewManager`)
- [x] Implement `createContainer()`
- Generate container name: `yao-sandbox-{userID}-{chatID}`
- Create workspace directory on host
- Configure mounts (workspace, IPC socket)
- Set resource limits (memory, CPU)
- Apply security options (`--cap-drop ALL`, `no-new-privileges`)
- [x] Implement `GetOrCreate()` with double-check locking
- [x] Implement `ensureImage()` - auto-pull missing Docker images
- [x] Implement `ensureRunning()`
- [x] Implement `Stop()` and `Start()`
- [x] Implement `Remove()`
- [x] Implement `List()`
- [x] Concurrency limit (`ErrTooManyContainers`)
---
## Phase 3: Command Execution & Filesystem ✅ COMPLETED
### Goals
- Execute commands inside containers
- Support both streaming and blocking execution
- Full filesystem operations
### Completed
#### Command Execution
- [x] Implement `Stream()` - returns io.ReadCloser
- [x] Implement `Exec()` - blocking execution with result
- [x] Handle timeout via context
- [x] Handle environment variables
#### Filesystem Operations
- [x] `WriteFile()` - tar archive + CopyToContainer
- [x] `ReadFile()` - CopyFromContainer + extract tar
- [x] `ListDir()` - execute `ls -la` and parse
- [x] `Stat()` - execute `stat` and parse
- [x] `MkDir()` - execute `mkdir -p`
- [x] `RemoveFile()` - execute `rm -rf`
- [x] `CopyToContainer()` - tar + Docker API
- [x] `CopyFromContainer()` - Docker API + extract
---
## Phase 4: IPC System ✅ COMPLETED
### Goals
- Implement Unix socket IPC
- Handle MCP JSON-RPC protocol
### Completed
- [x] Implement `ipc.Manager`
- `NewManager(sockDir string) *Manager`
- `Create(ctx, sessionID, agentCtx, mcpTools) (*Session, error)`
- `Close(sessionID) error`
- `Get(sessionID) (*Session, bool)`
- `CloseAll()`
- [x] Implement `ipc.Session`
- Create Unix socket listener
- Set socket permissions (0660)
- Handle connection lifecycle
- [x] Implement message loop
- Accept connection
- Read NDJSON lines
- Parse JSON-RPC requests
- Dispatch to handlers
- Write JSON-RPC responses
- [x] Implement MCP handlers
- `initialize` → handshake response
- `tools/list` → return authorized tools
- `tools/call` → execute Yao process
- `resources/list` → list resources
- `resources/read` → read resource
- [x] JSON-RPC error handling with proper error codes
---
## Phase 5: yao-bridge & Docker Image ✅ COMPLETED
### Goals
- Build yao-bridge binary
- Create Docker images
### Implemented Files
```
sandbox/
├── bridge/
│ └── main.go # yao-bridge source
├── docker/
│ ├── base/
│ │ └── Dockerfile.base # Common base image
│ ├── claude/
│ │ ├── Dockerfile # Default: Claude + Node + Python
│ │ └── Dockerfile.full # + Go
│ └── build.sh # Build script
```
### Completed
- [x] Implement yao-bridge (`sandbox/bridge/main.go`)
- stdin/stdout ↔ Unix socket bridge
- Signal handling for graceful shutdown
- [x] Create Dockerfiles
- `base/Dockerfile.base` - Ubuntu 22.04, git, curl, yao-bridge
- `claude/Dockerfile` - + Node.js 20, Python 3.11
- `claude/Dockerfile.full` - + Go 1.23
- [x] Create build script (`sandbox/docker/build.sh`)
- Builds yao-bridge as static binary
- Builds all image variants
---
## Phase 6: ClaudeExecutor Integration 🔲 PENDING
### Goals
- Integrate Sandbox with ClaudeExecutor
- End-to-end execution flow
### Tasks
- [ ] Add Sandbox Manager to ClaudeExecutor
```go
type ClaudeExecutor struct {
Assistant *Assistant
SandboxManager *sandbox.Manager
IPCManager *ipc.Manager
}
```
- [ ] Implement `Stream()` method
1. Get or create container
2. Create IPC session
3. Generate .mcp.json
4. Setup skills
5. Build Claude CLI args
6. Execute in container
7. Parse output
- [ ] Implement `writeMCPConfig()`
- Generate MCP config with yao-bridge
- Include external MCP servers
- Write to workspace
- [ ] Implement `setupSkills()`
- Symlink skills directory to .claude/skills/
- [ ] Implement output parsing
- Parse NDJSON stream
- Extract text content
- Extract file changes from tool_use
- Handle result message
- [ ] Handle session mapping
- Map Yao ChatID to Claude SessionID
- Support `--resume` for continuation
### Deliverables
- [ ] ClaudeExecutor with Sandbox
- [ ] MCP config generation
- [ ] Skills setup
- [ ] Output parsing
- [ ] Integration tests
---
## Phase 7: Cleanup & Testing ✅ COMPLETED
### Goals
- Implement cleanup strategies
- Comprehensive testing
- Documentation
### Completed
- [x] Implement cleanup loop (every 5 minutes)
- [x] Implement `Cleanup(ctx) error`
- [x] Unit tests (no Docker required)
- `config_test.go` - Config parsing, validation, env vars, edge cases
- `helpers_test.go` - parseMemory, mapToSlice, parseLS, parseStat, tar operations
- `ipc/jsonrpc_test.go` - JSON-RPC parsing, serialization
- [x] Integration tests (Docker required)
- `manager_test.go` - Container lifecycle, exec, filesystem operations
- `ipc/manager_test.go` - IPC session management
- `ipc/session_test.go` - Session message handling, MCP protocol
- [x] README.md with usage examples
### Test Files
| File | Tests | Description |
| --------------------- | ----- | ------------------------------------ |
| `config_test.go` | 10 | Config parsing, env vars, edge cases |
| `helpers_test.go` | 6 | Utility functions |
| `ipc/jsonrpc_test.go` | 8 | JSON-RPC types |
| `manager_test.go` | 18 | Container lifecycle (Docker) |
| `ipc/manager_test.go` | 10 | IPC sessions |
| `ipc/session_test.go` | 11 | Session handlers (Docker optional) |
### Unit Tests (No Docker)
```
✅ TestDefaultConfig
✅ TestConfigInit
✅ TestConfigInitWithEnv
✅ TestConfigInitWithWorkspaceEnv
✅ TestConfigInitWithPresetValues
✅ TestConfigInitInvalidEnvValues
✅ TestConfigInitNegativeValues
✅ TestConfigInitZeroMax
✅ TestContainerName
✅ TestConfigEnvPriority
✅ TestParseMemory
✅ TestMapToSlice
✅ TestParseLS
✅ TestParseStat
✅ TestParseLSMode
✅ TestCreateAndExtractTar
✅ TestJSONRPCRequestParsing
✅ TestJSONRPCResponseSerialization
✅ TestJSONRPCErrorResponse
✅ TestToolCallParams
✅ TestToolResult
✅ TestToolsListResult
✅ TestInitializeResult
```
### Integration Tests (Docker Required)
```
✅ TestNewManager
✅ TestNewManagerWithNilConfig
✅ TestGetOrCreate
✅ TestContainerStartStopRemove
✅ TestExec
✅ TestExecWithEnv
✅ TestExecWithTimeout
✅ TestFileOperations
✅ TestCopyOperations
✅ TestListContainers
✅ TestConcurrencyLimit
✅ TestConcurrentAccess
✅ TestContainerNotFound
✅ TestCleanup
✅ TestGetAccessors
✅ TestEnsureImageAutoPull
✅ TestManagerWithYaoApp (requires YAO_TEST_APPLICATION)
```
### IPC Tests
```
✅ TestNewManager
✅ TestCreateSession
✅ TestGetSession
✅ TestCloseSession
✅ TestCloseNonExistentSession
✅ TestCloseAllSessions
✅ TestSessionReplace
✅ TestConcurrentSessionAccess
✅ TestSessionConnection
✅ TestToolsList
✅ TestMethodNotFound
✅ TestParseError
✅ TestInitializedNotification
✅ TestSessionHandleInitialize
✅ TestSessionHandleResourcesList
✅ TestSessionHandleResourcesRead
✅ TestSessionHandleToolsCallInvalidParams
✅ TestSessionHandleToolsCallUnauthorized
✅ TestSessionToolsCallWithYaoApp (requires YAO_TEST_APPLICATION)
✅ TestSessionMultipleRequests
✅ TestSessionClose
✅ TestSessionEmptyLines
```
### Running Tests
```bash
# Unit tests only (no Docker needed)
go test -v ./sandbox/... -run "Test(Default|Config|Parse|Map|LS|Stat|Tar|JSONRPC|Tool)"
# Integration tests (Docker required)
source env.local.sh
go test -v ./sandbox/...
# With Yao application (full integration)
export YAO_TEST_APPLICATION=/path/to/yao-dev-app
source env.local.sh
go test -v ./sandbox/...
# Using Makefile (pulls test images automatically)
make unit-test-sandbox
```
---
## Phase Summary
| Phase | Description | Status |
| ----- | ------------------------------ | ------------ |
| 1 | Core Interfaces & Types | ✅ COMPLETED |
| 2 | Docker Container Management | ✅ COMPLETED |
| 3 | Command Execution & Filesystem | ✅ COMPLETED |
| 4 | IPC System | ✅ COMPLETED |
| 5 | yao-bridge & Docker Image | ✅ COMPLETED |
| 6 | ClaudeExecutor Integration | 🔲 PENDING |
| 7 | Cleanup & Testing | ✅ COMPLETED |
---
## Implementation Summary
### Files Created
| File | Lines | Description |
| --------------------------------------- | ----- | ------------------------------ |
| `sandbox/errors.go` | 22 | Error types |
| `sandbox/types.go` | 52 | Core type definitions |
| `sandbox/config.go` | 90 | Configuration with env vars |
| `sandbox/helpers.go` | 305 | Helper functions |
| `sandbox/manager.go` | 541 | Main manager implementation |
| `sandbox/ipc/types.go` | 139 | IPC type definitions |
| `sandbox/ipc/manager.go` | 101 | IPC session manager |
| `sandbox/ipc/session.go` | 252 | Session handling |
| `sandbox/bridge/main.go` | 59 | yao-bridge binary |
| `sandbox/docker/base/Dockerfile.base` | 34 | Base Docker image (multi-arch) |
| `sandbox/docker/claude/Dockerfile` | 43 | Claude image |
| `sandbox/docker/claude/Dockerfile.full` | 34 | Full Claude image (multi-arch) |
| `sandbox/docker/build.sh` | 145 | Build script (multi-arch) |
| `sandbox/config_test.go` | 175 | Config tests |
| `sandbox/helpers_test.go` | 190 | Helper tests |
| `sandbox/manager_test.go` | 520 | Manager integration tests |
| `sandbox/ipc/jsonrpc_test.go` | 236 | JSON-RPC tests |
| `sandbox/ipc/manager_test.go` | 330 | IPC manager tests |
| `sandbox/ipc/session_test.go` | 420 | IPC session tests |
| `sandbox/README.md` | 152 | Documentation |
**Total**: ~3800 lines
---
## Dependencies
### External
- Docker Engine (or Docker Desktop)
- Claude CLI (placeholder in Dockerfile)
### Go Packages
- `github.com/docker/docker/client` - Docker SDK
- `github.com/docker/docker/api/types/container` - Docker types
### Internal
- `github.com/yaoapp/gou/process` - Yao process execution
---
## Next Steps
1. **Phase 6: ClaudeExecutor Integration**
- Implement in `yao/agent/assistant/executor/claude/`
- Wire up sandbox with assistant execution flow
2. **CI/CD for Docker Images** ✅ COMPLETED
- Images already built and pushed to Docker Hub:
- `yaoapp/sandbox-base:latest` (amd64, arm64)
- `yaoapp/sandbox-claude:latest` (amd64, arm64)
- `yaoapp/sandbox-claude-full:latest` (amd64, arm64)
- Set up automated builds on version tags
3. **CI/CD for Tests** ✅ COMPLETED
- Sandbox tests run separately from core tests
- Makefile: `make unit-test-sandbox`
- GitHub Actions workflows updated:
- `unit-test.yml`: Added `sandbox-test` job
- `pr-test.yml`: Added `SandboxTest` job
- Test images pre-pulled before tests:
- `alpine:latest`
- `yaoapp/sandbox-base:latest`
- `yaoapp/sandbox-claude:latest`
---
## Success Criteria
### Functional ✅ (Sandbox Core)
- [x] Can create/start/stop/remove containers
- [x] Can execute commands in containers
- [x] IPC communication works bidirectionally
- [ ] Claude CLI can call Yao MCP tools (requires Phase 6)
- [x] Data persists across container restarts
### Performance (To be validated)
- [ ] Container creation < 5 seconds
- [ ] Command execution latency < 100ms overhead
- [ ] IPC round-trip < 10ms
### Reliability
- [x] Handles connection drops gracefully
- [x] Cleans up resources on errors
- [x] No resource leaks (cleanup loop)
### Security
- [x] User isolation enforced (one container per user+chat)
- [x] Resource limits enforced (memory, CPU)
- [x] No privilege escalation (`--cap-drop ALL`, `no-new-privileges`)