使用说明
沙盒代理
基于Docker的沙盒代码执行的LangGraph代理。每个会话都在一个隔离的、强化的Docker容器中运行,该容器具有一个持久内核——Python的IPython, vm.createContext Node.js和专用的R环境。支持3个运行时、与提供者无关的LLM配置和视觉(多模式模型的自动检测)。可用作交互式CLI、MCP服务器(Cursor、Claude Desktop)、REST API(艾格拉),以及React前端。
特性
- Docker隔离 --每个会话都在自己的容器中运行,没有暴露的端口,也没有主机卷
- 硬化容器 --非根用户(UID 65532)、PID限制、内存+交换限制、仅tmpfs可写目录、,
no-new-privileges - 碰撞检测 --检测到OOM杀伤、分叉炸弹、分段故障,并明确报告给特工
- 持久状态 --变量在代码执行之间存活(如Jupyter单元格)
- PostgreSQL检查点 --对话历史记录在重新启动后仍然存在(与Aegra共享)
- 异步支持 --Promise(Node.js)和协程(Python)会自动等待
- 多运行时 --Python、Node.js和R
- 丰富的显示输出 --捕获matplotlib/ggplot图形、Plotly图表、IPython音频、HTML小部件等;自动将图像发送到多模式LLM
- 提供者不可知 --通过以下方式与OpenAI、Anthropic、Google Gemini、Ollama或任何兼容提供商合作
langchain init_chat_model - 运行时软件包安装 —
pip install/npm install/install.packages()在会话创建时或通过终端 - 6工具 —
create_session,execute_code,execute_terminal,import_files,export_files,stop_session - MCP服务器 --通过模型上下文协议(stdio传输)公开相同的工具
- REST API -完整的LangGraph平台API,通过 艾格拉 使用OpenAPI文档、流媒体、线程管理
- 输入验证 --Pydantic模式在执行前验证所有工具输入,失败时返回结构化错误
- React前端 --SPA聊天、工具可视化、文件上传/下载、设置对话框(React 19+Vite+Tailwind CSS)
- 文件上传 -将文件上载到API以导入沙箱会话(
POST /threads/{id}/files/upload) - 文件导出 --注册文件以供下载(无主机副本);通过API下载或在交叉会话导入中使用
- 文件导入 --从主机路径、内联内容或其他会话(在同一对话中导出的文件)导入
- 跨会话传输 --从会话A导出,导入到会话B
{session_id, path} - 会话垃圾回收 --空闲超时、最大生存期、线程驱逐、孤立容器清理
- 自动清理 --当代理退出时,所有容器都会停止并被移除
先决条件
- Python 3.11+
- Docker引擎
- LLM提供商的API密钥(
CHAT_MODEL_API_KEY) - PostgreSQL(适用于API/CLI模式-checkpointer+Aegra)
- Node.js 18+和npm(用于React前端)
设置
# Docker — installs (if needed), configures permissions, and builds all 3 images
sudo ./setup-docker.sh
# Install Python dependencies (open a new terminal so the docker group is active)
uv sync
# Install frontend dependencies
cd frontend && npm install && cd ..
# Configure environment
cp .env.example .env
# Edit .env with your CHAT_MODEL_API_KEY, POSTGRES_PASSWORD, and other settings
# Docker images are also built automatically on first use if not already present
PostgreSQL(CLI、API和UI都需要)
PostgreSQL在使用时通过Docker Compose自动启动 localhostCLI检测PostgreSQL是否可访问并自动启动:
# Manual start (if needed)
docker compose up postgres -d
或者通过以下方式指向现有的PostgreSQL实例 POSTGRES_* env变量 .env.
用法
所有命令都使用统一 sandbox-agent 入口点:
uv run sandbox-agent cli # Interactive CLI (default)
uv run sandbox-agent mcp # MCP server (Cursor, Claude Desktop)
uv run sandbox-agent api # REST API (Aegra, no reload)
uv run sandbox-agent api dev # REST API with hot reload
uv run sandbox-agent ui # React UI (auto-starts API if needed)
命令行界面
uv run sandbox-agent cli
# or simply
uv run sandbox-agent
CLI作为Aegra REST API之上的瘦客户端运行。要求API正在运行(uv run sandbox-agent api).特征:
- 富有的 带有语法高亮显示的工具I/O的面板(每个运行时词法分析器)
- Markdown渲染流媒体代理输出
- 跨重启的持久线程(
~/.local/state/sandbox-agent/cli-thread.json) /new开始新对话的命令- 通过将模型/提供商/密钥设置传递给API
configurable
MCP 服务器
运行MCP服务器(stdio传输)以与Cursor、Claude Desktop或任何兼容MCP的客户端集成:
uv run sandbox-agent mcp
光标或克劳德桌面
添加以下MCP配置:
{
"mcpServers": {
"sandbox-agent": {
"command": "uv",
"args": ["--directory", "/path/to/sandbox-agent", "run", "sandbox-agent", "mcp"]
}
}
}
MCP服务器公开了与CLI代理相同的6个工具,具有相同的行为。它在中维护一个持久的thread_id ~/.local/state/sandbox-agent/mcp-thread.json 为了确保导出URL的一致性。
这 import_files 该工具直接接受文件内容(如文本或base64) file_content/encoding 密钥)、主机路径(通过 source/destination),或跨会话引用(session_id+path).这 export_files 该工具通过注册文件进行下载 GET /threads/{thread_id}/files/download?session_id=...&path=....
REST API(Aegra)
将代理作为REST API运行,通过 艾格拉 (自托管LangGraph平台替代方案):
uv run sandbox-agent api # Production mode (no reload, auto-starts PostgreSQL)
uv run sandbox-agent api dev # Development mode (hot reload via aegra dev)
如果无法在本地主机上访问PostgreSQL,则生产命令会通过Docker Compose自动启动PostgreSQL。服务器运行在 http://localhost:8000 OpenAPI文档位于 /docs。使用LangGraph SDK或curl创建助手、线程和流运行。与Agent Chat UI、LangGraph Studio和CopilotKit兼容。
自定义端点:
GET /threads/{thread_id}/files/download?session_id=...&path=...--流式传输从容器导出的文件POST /threads/{thread_id}/files/upload--上传文件以供导入沙盒会话DELETE /threads/{thread_id}--还清理该线程的Docker会话和存储(通过中间件)GET /settings--返回在后端合并的持久化前端设置.env默认值PUT /settings--将前端设置持久化到PostgreSQL(加密)
反应前端
通过Aegra API与代理商聊天的web UI(React 19+Vite+Tailwind CSS):
# Install frontend dependencies (if not done during setup)
cd frontend && npm install && cd ..
# Start the UI (auto-starts API + PostgreSQL if needed)
uv run sandbox-agent ui
前端运行在 http://localhost:5173 (带有API代理的Vite dev服务器 :8000).特征:
- 通过侧边栏进行话题管理(创建、恢复、删除对话)
- 使用可扩展的工具块流式传输响应(每次运行时突出显示语法)
- 文件上传和下载支持
- 思维块可视化
- 设置对话框(模型、提供商、API密钥、基本URL、视觉切换)
- 通过服务器端API进行持久设置(
GET/PUT /settings),带后端.env默认为回退
程序化
from sandbox_agent.sandbox import SandboxManager
manager = SandboxManager()
info = manager.create_session(
runtime="python",
dependencies={"pandas": "2.2.3", "matplotlib": ""},
)
sid = info.session_id
r1 = manager.execute_code(sid, """
import pandas as pd
df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})
print(df.describe())
""")
print(r1.stdout)
# Variables persist between calls
r2 = manager.execute_code(sid, "df.shape")
print(r2.result)
# Export files from the sandbox (registers for download, no host copy)
manager.execute_code(sid, "df.to_csv('/workspace/output.csv', index=False)")
export = manager.export_files(sid, [{"source": "output.csv"}])
print(export.files[0].session_id, export.files[0].path)
manager.stop_session(sid)
导出文件
export_files 注册文件以供下载和跨会话导入(无主机副本)。文件可通过API获得(GET /threads/{thread_id}/files/download?session_id=...&path=...)以及 import_files 在其他会议中:
# Export a single file
result = manager.export_files(sid, [{"source": "report.pdf"}])
# Export an entire directory
result = manager.export_files(sid, [{"source": "results/"}])
# Export multiple files at once
result = manager.export_files(sid, [
{"source": "data.csv"},
{"source": "chart.png"},
{"source": "/workspace/logs/"},
])
for f in result.files:
print(f"{f.session_id}:{f.path} ({'OK' if f.success else f.error})")
跨会话文件传输
使用 export_files + import_files 在会话之间移动文件(甚至在不同的运行时之间):
# Session A (Python): produce data
sid_a = manager.create_session(runtime="python", dependencies={"pandas": ""}).session_id
manager.execute_code(sid_a, """
import pandas as pd
df = pd.DataFrame({'x': [1,2,3], 'y': [4,5,6]})
df.to_csv('/workspace/data.csv', index=False)
""")
export = manager.export_files(sid_a, [{"source": "data.csv"}])
path = export.files[0].path # /workspace/data.csv
# Session B (R): consume the same data
sid_b = manager.create_session(runtime="r", dependencies={"readr": ""}).session_id
manager.import_files(sid_b, [{"session_id": sid_a, "path": path, "destination": "data.csv"}])
manager.execute_code(sid_b, 'df <- readr::read_csv("/workspace/data.csv"); summary(df)')
导入文件
import_files 将文件从主机或其他会话复制到沙盒中:
# Import from host
result = manager.import_files(sid, [
{"source": "/home/user/data.csv", "destination": "data.csv"},
{"source": "/home/user/project/", "destination": "project/"},
])
# Import from another session (file must have been exported first)
result = manager.import_files(sid, [
{"session_id": "abc123", "path": "/workspace/out.csv", "destination": "out.csv"},
])
其他运行时也以同样的方式工作——pass runtime="node" 或 runtime="r" 到 create_session.
异步代码
Node.js --如果最后一个表达式返回Promise,内核将在收集输出之前等待它。顶层 await 还支持(需要时回退到异步IIFE包装器)。
const axios = require('axios');
async function fetchData() {
const resp = await axios.get('https://api.example.com/data');
console.log(resp.data);
}
fetchData(); // Promise is awaited automatically
python --IPython autoawait 处理顶级 await如果一个单元返回一个未等待的协程,内核会检测到它并用 asyncio.run().
import aiohttp
async def fetch_data():
async with aiohttp.ClientSession() as session:
resp = await session.get('https://api.example.com/data')
print(await resp.text())
fetch_data() # coroutine is detected and executed automatically
容器安全
每个容器都创建了以下保护:
| 保护 | 设置 | 效果 |
|---|---|---|
| 内存限制 | 2048m (无交换) |
溢出时OOM终止,主机不受影响 |
| PID限制 | 512 |
叉式炸弹被控制并杀死 |
| CPU配额 | 2 核心 |
防止主机CPU不足 |
| 可写目录 | tmpfs(/workspace, /tmp, /home/sandbox) |
tmpfs-dirs从不接触主机磁盘 |
| tmpfs大小 | 200m 每次装载 |
容器磁盘使用限制 |
| 用户 | sandbox (UID 65532) |
容器内没有根 |
| 特权 | no-new-privileges |
无法通过setuid/setgid升级 |
| 网络 | 可配置(默认启用) | 每个会话都可以禁用 |
当容器崩溃时,代理会收到清除 CONTAINER_DIED 错误原因(OOM终止、SIGKILL、segfault等)和重新创建会话的提示。
会话生命周期
会话通过垃圾回收自动管理:
| 行为 | 默认 | 设置 |
|---|---|---|
| 空闲超时 | 30分钟 | SESSION_IDLE_TTL_SECONDS |
| 最大寿命 | 2小时 | SESSION_MAX_LIFETIME_SECONDS |
| GC间隔 | 60秒 | SESSION_GC_INTERVAL_SECONDS |
| 最大活动线程数 | 10 | SESSION_MAX_ACTIVE_THREADS |
| 最大会话数(全局) | 5 | CONTAINER_MAX_SESSIONS |
| 每个线程的最大会话数 | 3 | CONTAINER_MAX_SESSIONS_PER_THREAD |
| 孤儿清理年龄 | 5分钟 | CONTAINER_ORPHAN_MIN_AGE_SECONDS |
在启动时,管理器会删除超过最低年龄的孤立容器。出口时,所有容器都会被停止并通过以下方式移除 atexit 和信号处理器(SIGTERM/SIGINT).
配置
所有设置都可以通过环境变量或 .env默认值显示如下 settings.py:
# ── LLM (provider-agnostic) ──
CHAT_MODEL=gpt-4o # Model name
CHAT_MODEL_PROVIDER=openai # Provider: openai, anthropic, google_genai, ollama
CHAT_MODEL_API_KEY=sk-... # API key (required)
CHAT_MODEL_BASE_URL= # Custom API base URL (optional)
CHAT_MODEL_SUPPORTS_VISION= # Override vision detection (true/false, empty = auto)
# ── Container Limits ──
CONTAINER_MEMORY_LIMIT=2048m # Docker memory limit (no swap)
CONTAINER_CPU_QUOTA=200000 # CPU quota (100000 = 1 core)
CONTAINER_PIDS_LIMIT=512 # Max PIDs per container
CONTAINER_TMPFS_SIZE=200m # tmpfs size for writable dirs
CONTAINER_EXECUTION_TIMEOUT_SECONDS=30 # Default code execution timeout
CONTAINER_MAX_SESSIONS=5 # Max concurrent sessions (global)
CONTAINER_MAX_SESSIONS_PER_THREAD=3 # Max sessions per conversation
CONTAINER_EXECUTE_AS_ROOT=False # Run terminal commands as root
CONTAINER_NETWORK_ENABLED=True # Enable container networking (disable per session)
CONTAINER_ORPHAN_MIN_AGE_SECONDS=300 # Min age before orphan cleanup (5 min)
# ── Session Lifecycle / GC ──
SESSION_IDLE_TTL_SECONDS=1800 # Idle timeout (30 min)
SESSION_MAX_LIFETIME_SECONDS=7200 # Hard lifetime cap (2 hours)
SESSION_GC_INTERVAL_SECONDS=60 # GC check interval
SESSION_MAX_ACTIVE_THREADS=10 # Max active threads before eviction
# ── Output Truncation (characters) ──
MAX_STDOUT_CHARS=50000
MAX_STDERR_CHARS=120000
MAX_RESULT_CHARS=30000
MAX_TRACEBACK_CHARS=8000
# ── Encryption ──
ENCRYPTION_KEY= # Fernet key for settings encryption (optional)
# ── Storage ──
STORAGE_DIR=./storage # Base dir for uploads
IMPORT_ALLOWED_DIRS= # Comma-separated host dirs allowed for import (empty = all)
# ── API ──
API_BASE_URL=http://127.0.0.1:8000 # API URL (for export download URLs)
# ── Agent ──
MAX_ITERATIONS=25 # Max LangGraph iterations (recursion limit)
# ── PostgreSQL (checkpointer + Aegra) — all required, no defaults ──
POSTGRES_USER=sandbox_agent
POSTGRES_PASSWORD=sandbox_agent_secret
POSTGRES_DB=sandbox_agent
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
运行时
| 运行时 | 基础映像 | 内核 | IPC | 预安装 |
|---|---|---|---|---|
python python:3.12-slim |
IPython shell | UNIX套接字 | IPython+系统库 | |
| Node.js | node:22-slim |
vm.createContext |
UNIX套接字 | 裸运行时 |
R rocker/r-ver:4 |
专用R环境 | TCP :8765 |
jsonline、base64enc、tidyverse、data.table、readxl、haven、httr2、DBI、RSQLite、rmarkdown、kniter、devtools、glmnet、randomForest |
R容器使用编译的C客户端二进制文件进行IPC,而Python和Node.js使用本机客户端。
建筑
flowchart TB
CLI["CLI · Rich REPL"]
MCP["MCP Server · FastMCP (stdio)"]
UI["React · Frontend"]
CLI --> API["Aegra REST API
(LangGraph Platform)"]
UI --> API
API --> Agent["LangGraph ReAct Agent"]
Agent --> Tools["LangChain Tools"]
MCP --> Core["Core Tool Functions"]
Tools --> Core
Core --> SM["SandboxManager
Docker SDK"]
SM -->|"docker exec -i + JSON pipe"| Docker
subgraph Docker ["Docker Containers
isolated, hardened"]
direction LR
PY["Python
IPython · UNIX socket"]
JS["Node.js
vm.createContext · UNIX socket"]
R["R
R env · TCP :8765"]
end
subgraph Storage ["Persistence"]
PG["PostgreSQL
checkpoints, exports"]
end
API --> PG
SM --> PG
在每个容器内,都有一个持久的 内核 (PID 1)保持执行状态 客户端 通过UNIX套接字(Python/Node.js)或TCP(R)连接到它 docker exec 呼叫:
flowchart TB
SM["SandboxManager"] -->|"docker exec -i"| Client["Client (ephemeral)"]
subgraph container ["Container"]
Client -->|"UNIX socket / TCP"| Kernel["Kernel (PID 1, persistent)"]
Kernel --- State["State
variables, imports, data"]
end
测试
# Unit tests (no Docker required)
uv run pytest tests/test_cli.py tests/test_http_app.py -v
# Integration tests (requires Docker)
uv run pytest tests/test_manager.py tests/test_tools.py tests/test_export_files.py tests/test_mcp.py -v
# LangGraph debug trace (requires Docker + LLM API key)
uv run pytest tests/test_langgraph_debug.py -v -s
# API integration tests (requires Docker + running API: uv run sandbox-agent api dev)
uv run pytest tests/test_api.py -v -s
# Full suite
uv run pytest tests/ -v
生产部署
A制作 Dockerfile 和 docker-compose.yml 包括:
# Start PostgreSQL + API
docker compose up -d
# Or build and run manually
docker build -t sandbox-agent-api .
docker run -p 8000:8000 --env-file .env sandbox-agent-api
生产图像使用 aegra serve 具有非根 app 用户。
许可证
麻省理工学院 --爱德华多·拉蒙·雷瑟
