- Added functionality to build and manage MCP configuration for sandbox environments, allowing for dynamic tool execution. - Enhanced the Assistant's Stream method to skip MCP tool calls in sandbox mode, with internal handling by Claude CLI. - Introduced unit tests for MCP configuration building and skills directory resolution, ensuring robust integration. - Updated sandbox manager to create IPC sessions and manage tool exposure dynamically, improving interaction with external agents. - Enhanced documentation to reflect new features and integration points for MCP and skills within the sandbox.
12 KiB
Agent Sandbox Implementation Plan
Overview
This plan covers the implementation of the agent sandbox integration layer (agent/sandbox/), which enables coding agents (Claude CLI, Cursor CLI) to run in isolated Docker containers with Yao's LLM pipeline.
Test Environment
Environment Configuration
Tests should run with the local development environment:
# Source environment variables
source /Users/max/Yao/yao/env.local.sh
# Key variables used:
# YAO_TEST_APPLICATION=/Users/max/Yao/yao-dev-app
# YAO_ROOT=$YAO_TEST_APPLICATION
# DEEPSEEK_API_KEY, DEEPSEEK_API_PROXY, DEEPSEEK_MODELS_V3
Test Application
Test assistants at yao-dev-app/assistants/tests/sandbox/:
yao-dev-app/assistants/tests/
└── sandbox/
├── basic/ # Basic sandbox execution test
│ ├── package.yao # uses.search: disabled
│ └── prompts.yml
├── hooks/ # Hook integration test
│ ├── package.yao # uses.search: disabled
│ ├── prompts.yml
│ └── src/index.ts
└── full/ # Full test with MCPs, Skills, Hooks
├── package.yao # uses.search: disabled, mcp: {servers: [...]}
├── prompts.yml
├── src/index.ts
└── skills/echo-test/ # Agent Skills standard
├── SKILL.md
└── scripts/echo.sh
Connector Configuration
Use deepseek.v3 as the default connector (via Volcengine API).
Implementation Status
Phase 1: Core Types and Interfaces ✅ COMPLETED
- Define
Executorinterface with all methods - Define
Optionsstruct with JSON tags - Define
FileInfoalias to infrastructure sandbox - Add
DefaultImage()andIsValidCommand()helpers
Phase 2: Claude Executor Implementation ✅ COMPLETED
- Implement
Executorstruct - Implement
NewExecutor()constructor with container reuse - Implement
Stream()method with CCR config writing - Implement
Execute()method (wrapper) - Implement
Close()method (removes container) - Implement filesystem methods:
ReadFile,WriteFile,ListDir - Implement
Exec()method - Implement
GetWorkDir()method
Phase 3: CCR Configuration ✅ COMPLETED
- Implement
BuildCCRConfig()with correct CCR format - Auto-detect provider type (volcengine, deepseek, openai, claude)
- Add transformer for DeepSeek/Volcengine (maxtoken)
- Generate Router configuration
- Write config to container before execution
Phase 4: Assistant Integration ✅ COMPLETED
- Implement
GetSandboxManager()singleton - Implement
HasSandbox()method - Implement
initSandbox()with cleanup function - Implement
executeSandboxStream()method - Build executor options from assistant config
- Resolve connector settings (host, key, model)
- Add trace logging for sandbox creation
- Send loading message during sandbox init
- Expose executor to hooks via
ctx.SetSandboxExecutor() - Handle sandbox lifecycle (create → hooks → execute → cleanup)
Phase 5: JSAPI Integration ✅ COMPLETED
- Define
SandboxExecutorinterface - Implement JS bindings for
ReadFile,WriteFile,ListDir,Exec - Expose
workdirproperty - Register in context's
NewObjectmethod
Phase 6: Concurrency & Resource Management ✅ COMPLETED
- Container creation uses Double-Check Locking (in
manager.GetOrCreate) - Same chatID reuses container (by design)
- Container cleanup on request completion (
defer sandboxCleanup()) - Unique chatID in tests to avoid conflicts
Phase 7: MCP & Skills Integration ✅ COMPLETED
- Build MCP config from assistant's
mcp.serversconfiguration - Write MCP config to container workspace (
.mcp.json) - Resolve skills directory from
assistants/{name}/skills/ - Copy skills to container (
/workspace/.claude/skills/) - Skip MCP tool execution in
agent.gofor sandbox mode (Claude CLI handles internally) - Add unit tests for MCP config building (
TestBuildMCPConfigForSandbox) - Add unit tests for skills directory resolution (
TestSandboxMCPAndSkillsOptions)
Phase 8: MCP IPC Bridge ✅ COMPLETED
- Modify
BuildMCPConfigForSandboxto useyao-bridgecommand for IPC - Create IPC session in
sandbox/manager.createContainer()(socket created before container) - Bind mount IPC socket to container at
/tmp/yao.sock - Add
SetMCPTools()method toipc.Sessionfor runtime tool configuration - Set MCP tools dynamically in
claude.Executor.Stream()before execution - IPC session lifecycle managed by
sandbox.Manager(create on container create, close on remove) - Load MCP tool definitions from gou/mcp and pass to IPC session
- Add
TestClaudeExecutorIPCSocketMountto verify socket bind mount - Verify E2E test shows "Loaded X MCP tools for IPC"
Phase 9: Workspace Management ⏳ PENDING
- Implement workspace cleanup configuration
- Implement stale workspace detection
- Implement cleanup scheduler
Phase 9: Cursor Placeholder ⏳ PENDING
- Create
cursor/README.mdplaceholder
Testing Status
Unit Tests
| Package | Test File | Status |
|---|---|---|
agent/sandbox |
types_test.go |
✅ PASS |
agent/sandbox |
executor_test.go |
✅ PASS |
agent/sandbox/claude |
command_test.go |
✅ PASS |
agent/sandbox/claude |
executor_test.go |
✅ PASS |
Integration Tests
| Package | Test File | Status |
|---|---|---|
agent/sandbox |
integration_test.go |
✅ PASS |
JSAPI Tests
| Package | Test File | Status |
|---|---|---|
agent/context |
jsapi_sandbox_test.go |
✅ PASS |
Assistant Loading Tests
| Package | Test File | Status |
|---|---|---|
agent/assistant |
sandbox_test.go |
✅ PASS |
agent/assistant |
sandbox_integration_test.go |
✅ PASS |
E2E Tests
| Package | Test Case | Status |
|---|---|---|
agent/assistant |
TestSandboxBasicE2E |
✅ PASS |
agent/assistant |
TestSandboxHooksE2E |
✅ PASS |
agent/assistant |
TestSandboxFullE2E |
✅ PASS |
agent/assistant |
TestSandboxContextAccess |
✅ PASS |
agent/assistant |
TestSandboxLoadConfiguration |
✅ PASS |
agent/assistant |
TestSandboxMCPToolCall |
✅ PASS |
agent/assistant |
TestSandboxMCPEchoTool |
✅ PASS |
Running Tests
# Source environment
source /Users/max/Yao/yao/env.local.sh
# Run all sandbox tests
go test -v ./agent/sandbox/...
# Run assistant sandbox tests
go test -v ./agent/assistant -run "Sandbox"
# Run E2E tests (requires Docker)
go test -v ./agent/assistant -run "TestSandbox.*E2E" -timeout 300s
File Structure
yao/agent/sandbox/ # Executor layer
├── DESIGN.md # ✅ Design document
├── PLAN.md # ✅ This file
├── types.go # ✅ Common types and interfaces
├── types_test.go # ✅ Types tests
├── executor.go # ✅ Factory function
├── executor_test.go # ✅ Factory tests
├── integration_test.go # ✅ Integration tests
├── claude/
│ ├── types.go # ✅ Claude-specific types
│ ├── executor.go # ✅ Executor implementation
│ ├── executor_test.go # ✅ Executor tests
│ ├── command.go # ✅ Command builder + CCR config
│ └── command_test.go # ✅ Command tests
└── cursor/
└── README.md # ⏳ Placeholder (pending)
yao/agent/assistant/ # Integration layer
├── sandbox.go # ✅ Sandbox handler
├── sandbox_test.go # ✅ Loading tests
├── sandbox_integration_test.go # ✅ Integration tests
├── sandbox_e2e_test.go # ✅ E2E tests
├── sandbox_debug_test.go # ✅ Debug tests
└── agent.go # ✅ Modified: sandbox detection in Stream()
yao/agent/context/ # Context layer
├── jsapi_sandbox.go # ✅ Sandbox JSAPI bindings
└── jsapi_sandbox_test.go # ✅ Sandbox JSAPI tests
yao-dev-app/assistants/tests/sandbox/ # Test assistants
├── basic/ # ✅ Basic sandbox test
├── hooks/ # ✅ Hooks test
└── full/ # ✅ Full test with MCPs and Skills
Key Design Decisions
1. Container Reuse
Same userID + chatID reuses the same container:
- Workspace directory persists across requests
- CCR config is written on each request (same content, safe to overwrite)
- Container is removed when request completes
2. Concurrency
- Container creation: Protected by mutex + double-check locking
- Container execution: Multiple requests can run concurrently in same container
- Claude CLI: Supports concurrent execution
3. CCR Configuration
CCR requires specific JSON format:
{
"Providers": [{"name": "volcengine", "api_base_url": "...", ...}],
"Router": {"default": "volcengine,model", ...}
}
Auto-detection of provider type based on host URL.
4. Resource Cleanup
executor.Close()removes the container and closes IPC sessiondefer sandboxCleanup()inagent.goensures cleanup- Tests use unique chatID (timestamp) to avoid conflicts
5. MCP IPC Architecture
Host (Yao) Container (Claude CLI)
┌────────────────────────┐ ┌────────────────────────┐
│ IPC Manager │ │ yao-bridge │
│ └─ Session │◄─────────────│ (stdio ↔ socket) │
│ └─ MCPTools │ Unix Socket │ │
│ └─ Process │ (/tmp/ │ Claude CLI reads │
│ executor │ yao.sock) │ .mcp.json and calls │
└────────────────────────┘ │ yao-bridge for tools │
└────────────────────────┘
.mcp.jsonpoints to single "yao" server usingyao-bridge /tmp/yao.sock- IPC session created with authorized MCP tools from assistant config
- Tools executed via
process.New()in IPC session handler
Known Issues
macOS Docker Desktop Socket Permissions
On macOS with Docker Desktop (gRPC-FUSE), Unix socket permissions are not properly preserved when bind mounting from the host. The IPC socket created on the host with 0666 permissions appears as 0660 inside the container.
Solution: After container start, we execute chmod 666 /tmp/yao.sock as root inside the container to fix permissions. This is handled automatically by sandbox.Manager.fixIPCSocketPermissions().
Notes
- All tests validate return values (use
require/assert) - Docker must be available for integration and E2E tests
- Tests automatically clean up containers after completion
- Use
uses.search: disabledin test assistants to avoid auto-search LLM calls