Agent 与沙箱:架构与关系解析
采集环境:主机
2663239c2aaa· Ubuntu 24.04.3 LTS · 内核 6.6.117-45.11.6.tl4.x86_64
采集时间:2026-10-15 10:18 CST
证据来源:/proc/<pid>/environ、/proc/<pid>/ns、supervisord 配置、workspace-init启动脚本、ss -tnpsocket 连接、cgroup 文件、mount 表
0. 一句话结论
Agent 不是沙箱之外的东西,而是沙箱内部一个由
sandbox-proxy派生出来的普通进程。
控制面不直接连接 Agent:它先把配置注入envd,再由沙箱内的sandbox-proxy拉起 Agent 进程;Agent 通过沙箱共享的文件系统、网络和命令执行能力完成工作,出网统一经由宿主网关172.24.0.1:80。
换句话说:沙箱是"机器",Agent 是跑在这台机器里的"操作员"——两者不是并列关系,而是宿主与租户、容器与进程的关系。
1. 环境身份:这是一个什么沙箱
从 Agent 进程(PID 1416)和sandbox-proxy(PID 125)的环境变量中可以看到完整身份:
| 类别 | 关键变量 | 值 |
|---|---|---|
| 平台 | X_IDE_IS_CLOUDSTUDIO/X_IDE_SPACE_HOST | TRUE/sandbox.cloudstudio.club |
| 编排 | WORKSPACE_NAMESPACE | cs-spacelet-v2-headless.coding-workspace.svc.cluster.local. |
| 区域 | X_IDE_SPACE_REGION | sh4(上海) |
| 环境 | IDE_ENV | prod |
| 运行时镜像 | IDE_USER_RUNTIME_IMAGE | cloudstudio-sh.tencentcloudcr.com/saas.container/full-1.0.0:202505140048 |
| 宿主名 | HOSTNAME | VM-32-6-tencentos |
| IDE | IDE_EDITOR_TYPE | VSCODE |
| 工作区 | IDE_WORK_DIR | /workspace |
| 基础目录 | IDE_EDITOR_SERVER_DIR | /.PlnPyKFp4CRfFtgC1 |
结论:这是腾讯云 CloudStudio 的 spacelet 沙箱,运行在一个名为cs-spacelet-v2-headless的 K8s 工作负载中(命名空间coding-workspace),采用 headless(无界面)形态,区域 sh4。
资源配额(envd 下发与 cgroup 实测一致)
| 项目 | envd 声明 | cgroup 实测 | 一致性 |
|---|---|---|---|
| 内存 | X_IDE_MEMORY_LIMIT=8G | memory.max = 8589934592(8 GiB) | ✅ 一致 |
| CPU | X_IDE_CPU_LIMIT=4 | cpu.max = 400000/100000(4 核) | ✅ 一致 |
| 磁盘 | X_IDE_DISK_QUOTA=250G | 根 overlay 256 G | ✅ 基本一致 |
| PID 上限 | — | pids.max = 151533 | — |
注意:在容器内直接执行
free会看到宿主的 123 GiB 内存(未挂载 lxcfs),判断配额必须读 cgroup。这一点在排障时极易误判。
2. 三层结构总览
┌─ 外部(控制面与网关)────────────────────────────────────┐ │ CloudStudio 控制面(SaaS) 宿主网关 172.24.0.1 │ │ 沙箱调度 · 配置下发 · 会话路由 出网 :80 · 日志 :18100 │ └───────────────────────┬──────────────────────────────────┘ │ 配置下发(HTTP) ┌─ 沙箱边界(K8s Pod / 容器)───────────────────────────────┐ │ │ │ 【沙箱基础服务层】由 supervisord (PID 61) 守护 │ │ envd:49983 sync_server editor_sdk node api │ │ terminal:65310 log-collector dockerd/containerd │ │ │ │ 【Agent 子系统(cbc.d)】 │ │ sandbox-proxy:9090/65225 ← Agent 启动器 + 出网代理 │ │ └─ codebuddy ×3 (prewarm 池) │ │ └─ mcp-stdio / MCP 服务 / zsh 命令 shell │ │ │ │ 【共享资源】/workspace · cgroup 8GiB/4vCPU · cwd 共享 │ └──────────────────────────────────────────────────────────┘关键点:服务层与 Agent 层位于同一个容器内,共享 PID / 网络 / 挂载命名空间,之间没有硬隔离。
3. 沙箱侧:基础服务与守护体系
3.1 守护骨架
PID 1 docker-init(容器入口,执行 exec supervisord) └ PID 61 supervisord -c supervisord-conf/supervisord.conf ├ [program:proxy] → sandbox-proxy ← 经由 start-cbc.d 拉起 ├ [program:e2b-envd] → envd ├ [program:api] → node api ├ [program:terminal] → terminal/app, local-proxy ├ [program:docker] → dockerd → containerd └ 其他一次性/常驻:init-workspace、sandbox-log-collector、editor_sdk、sync_serversupervisord 配置中autostart=true/autorestart=true,意味着这些服务被杀死后会自动重启——这是沙箱自愈能力的来源。
3.2 服务清单与端口
| 服务 | PID | 监听 | 职责 |
|---|---|---|---|
envd | 1284 | *:49983 | 配置权威源 + 控制面入口(/envs接口) |
sync_server | 42496 | 127.0.0.1:8089、*:18601 | 工作区文件双向同步(本地 ↔ 沙箱) |
editor_sdk | 42465 | 127.0.0.1:39099 | 编辑器 SDK(仅回环,--cors_origin=*) |
node api | 107 | *:65213 | 沙箱 API 服务 |
terminal / app | 95 | *:65310 | 终端服务 |
local-proxy | 93 | *:65216 | 本地代理 |
sandbox-proxy | 125 | *:9090、*:65225 | 出网代理 + Agent 启动器 |
sandbox-log-collector | 118 | 出向:18100 | 日志汇聚转发 |
dockerd/containerd | 29424 / 29488 | unix socket | 容器引擎(供沙箱内嵌套构建) |
3.3envd—— 最容易被忽略但最关键的一环
envd不只是"环境变量服务",它是沙箱与控制面之间的唯一配置通道:
- 对外做配置分发:
GET http://localhost:49983/envs返回所有环境变量,启动脚本靠它决定功能开关。 - 对内接收控制面连接:
ss显示envd的 49983 端口上有来自172.24.0.1(宿主侧)的已建立连接(fd 6、fd 9),说明控制面是主动连进沙箱的。
# start-cbc.d 和 agent-init 的标准模式ENVD_URL="http://localhost:49983/envs"ENVD_RESPONSE=$(curl-s"${ENVD_URL}")ENABLE_CBC_D_VALUE=$(echo"$ENVD_RESPONSE"|grep-o'"ENABLE_CBC_D":"[^"]*"'|cut-d'"'-f4)if["$ENABLE_CBC_D_VALUE"!="true"];thenexit0;fi设计含义:功能开关(
ENABLE_CBC_D、ENABLE_AGENT_INIT)不在镜像里写死,而是由控制面通过 envd 动态下发。这使得平台可以在不改镜像的前提下灰度开关 Agent 能力。
4. Agent 侧:形态、位置与派生关系
4.1 Agent 是谁
node /root/.nvm/versions/node/v22.13.1/lib/node_modules/@tencent-ai/codebuddy-code/bin/codebuddy --prewarm --prewarm-id sbx-125-N- 安装包:
@tencent-ai/codebuddy-code - 运行时:Node.jsv22.13.1(nvm 管理)
- 当前沙箱内共3 个实例,构成预热池:
| PID | 启动时间 | RSS | 参数 | 角色 |
|---|---|---|---|---|
| 1416 | 10:04:43 | ~335 MB | --prewarm-id sbx-125-1 | 当前活跃会话 |
| 1417 | 10:04:43 | 162 MB | --prewarm-id sbx-125-2 | 预热待命 |
| 42241 | 10:18:03 | 162 MB | --prewarm-id sbx-125-3 | 预热待命 |
三者的父进程都是sandbox-proxy(PID 125)——这是判定"Agent 属于沙箱"最直接的证据。
4.2 Agent 的两种形态
| 形态 | 启动方式 | 监听 | 用途 |
|---|---|---|---|
| prewarm 模式 | sandbox-proxy派生 | 无对外端口 | 预热待命,收到会话请求即接管(当前形态) |
| serve 模式 | start-codebuddy-serve脚本 | 0.0.0.0:65222 | 作为常驻 HTTP 服务运行 |
start-codebuddy-serve脚本内容:
cd"/workspace"# 使用 setsid 完全脱离控制终端,避免 codebuddy 因 tty 问题被挂起execsetsid codebuddy--serve--host0.0.0.0--port65222</dev/null4.3 Agent 调用外部能力的方式
在/proc/1416/fd与进程树中可以看到 Agent 的两类子进程:
| 子进程 | PID | 作用 |
|---|---|---|
sandbox-proxy mcp-stdio | 42268 | MCP 协议的 stdio 传输通道 |
node .../workbuddy-sheetagent/mcp/start.mjs | 42512 | 表格处理 MCP 服务 |
zsh | 53271+ | 由 Agent 派发的命令执行 shell(临时生命周期) |
MCP 插件清单(/root/.codebuddy/.mcp.json)包含:agent-browser、docx、pdf、pptx、xlsx、playwright-cli、workbuddy-docx、workbuddy-pptx、workbuddy-sheetagent、hot-skills。
重要机制:Agent 的"工具执行"在本机表现为fork 子 shell / 起 MCP 子进程。每一次工具调用都是沙箱内的一次真实进程创建,而不是远程 RPC。
5. 两者的接口契约:四条通道
Agent 与沙箱/控制面之间只有四条交互通道,边界非常清晰:
| # | 通道 | 方向 | 载体 | 证据 |
|---|---|---|---|---|
| ① | 配置下发 | 控制面 → 沙箱 | envd:49983的/envs接口 | 启动脚本curl localhost:49983/envs;envd 上有来自172.24.0.1的入站连接 |
| ② | 进程派生 | 沙箱 → Agent | supervisord → start-cbc.d → sandbox-proxy → codebuddy | ps --ppid 125显示 3 个 codebuddy |
| ③ | 出网访问 | Agent/沙箱 → 外网 | 宿主网关172.24.0.1:80 | 1416的 fd 20/22、125的多个 fd 均指向172.24.0.1:80 |
| ④ | 日志上报 | 沙箱 → 宿主 | log-forward-tee → nc → 172.24.0.1:18100 | SANDBOX_LOG_FORWARD=1;多个nc 172.24.0.1 18100进程 |
注意 ③ 的细节:Agent 进程自身也直连172.24.0.1:80,并非所有流量都绕经sandbox-proxy。说明sandbox-proxy的"代理"职责与"启动器"职责是两件独立的事。
6. 完整启动链路(从容器到 Agent)
1. 容器启动 PID 1 docker-init -- bash -c "...exec supervisord" 2. 进程管理 PID 61 supervisord(按 supervisord.conf 拉起各 program) 3. 读取平台配置 start-cbc.d → curl http://localhost:49983/envs(envd) → 解析 ENABLE_CBC_D,为 true 才继续 4. 获取 Agent 运行时(box) 优先:/root/proxy/box-current(sandbox-componentd 版本管理软链) 兜底:curl https://cs-res-1324618448.cos.ap-shanghai.myqcloud.com/sandbox/tools/box.tar.gz → 解压到 /root/proxy/box 5. 识别产物形态并运行 Go 形态: /root/proxy/box/sandbox-proxy → exec 前台运行 Node 形态: ecosystem.config.js → pm2 start → pm2 logs 保持前台 6. Agent 派生 sandbox-proxy(125) → codebuddy --prewarm --prewarm-id sbx-125-{1,2,3} 7. 会话接入 当前会话落在 codebuddy 1416;工具调用 → fork zsh / 启 MCP 子进程
sandbox-proxy二进制实测为46 MB 的 Go 静态链接 ELF(statically linked、带 debug_info),说明它是外部分发、独立升级的组件——这正好解释了start-cbc.d中"从 COS 下载 + 支持box-current软链切换版本"的设计。
启动脚本的一个工程细节
agent-init脚本中有一段明确注释,记录了一个已修复的线上问题,很能反映这套架构的可靠性设计:
历史上这里有 download-if-absent 兜底(文件不存在就从固定 COS URL 拉取)。该兜底有两个问题已移除:
- COS 那份长期未更新(停在 2026-02 旧版),一旦触发就把系统盘自带的新版覆盖成旧版(生产实测 sh3:base 盘 1.0.22 被覆盖成 1.0.10);
- 触发条件是竞态——warm 空壳的系统盘是 base 盘的 COW fork,脚本在 NBD 盘就绪前的窗口内
[ -f ]判为不存在 → 误触下载。
含义:沙箱使用warm 池 + NBD 远程克隆盘做秒级启动,因此启动早期存在"文件尚未可见"的时间窗,任何"文件缺失即下载"的逻辑都会误判。
7. 边界与共享:Agent 到底能看到什么
7.1 命名空间:无隔离
/proc/1416/ns显示 Agent 与沙箱服务处于同一组命名空间:
net:[4026534002] pid:[4026534001] mnt:[4026533985] ipc:[4026534000]因此:
- Agent 能直接访问
127.0.0.1:49983(envd)、127.0.0.1:8089(sync_server)、127.0.0.1:39099(editor_sdk) - Agent 与沙箱服务共享同一张网络栈、同一个 PID 空间、同一套挂载
7.2 文件系统:完全共享
| 观察项 | 值 |
|---|---|
/proc/1416/cwd | /workspace |
/proc/1416/root | / |
| 根挂载 | overlay,upperdir=/mnt/user-data/46e3d0d99e364ee9b2f628933b04a755/diff |
| warm 池来源 | /var/run/cloudstudio/warm-b492449d-8153-4eb5-8096-dcd22815b96d... |
| 嵌套容器盘 | /dev/nbd503(btrfs)→/var/lib/docker、/var/lib/buildkit |
Agent 对沙箱文件系统的读写就是直接的磁盘读写,/workspace同时被sync_server同步到用户本地。
7.3 存储分层
NBD 远程盘(/dev/vdb、/dev/nbd503,btrfs) └─ overlay upperdir(/mnt/user-data/<uuid>/diff) └─ 容器根 /(rw) → /workspace(用户工作区)启动方式为NBD 克隆盘 + overlay,配合 warm 池实现"空壳预热、按需接管"。
8. 通信关系总表
| 源 | 目标 | 协议/端口 | 用途 |
|---|---|---|---|
| 控制面 | envd | 172.24.0.1 → 172.24.0.11:49983 | 配置/指令下发 |
codebuddy 1416 | 宿主网关 | 172.24.0.11 → 172.24.0.1:80 | Agent 出网(云端模型调用等) |
sandbox-proxy 125 | 宿主网关 | → 172.24.0.1:80(多条) | 代理出网 |
sandbox-proxy 125 | 外网 | → 43.141.132.124:443 | HTTPS 出网 |
sandbox-proxy 125 | envd | 127.0.0.1:58324 → :49983 | 本地配置读取 |
sandbox-proxy 125 | sync_server | 127.0.0.1:54252 → :8089 | 同步服务调用 |
codebuddy 1416 | 自身 | 127.0.0.1:35729 | 内部 RPC / 扩展通道 |
nc/log-collector | 宿主 | → 172.24.0.1:18100 | 日志转发 |
sync_server | 宿主 | → 172.24.0.1:80 | 同步数据回传 |
9. 关键洞察与排障提示
洞察 1:职责分离做得比想象中干净
envd管配置,sandbox-proxy管进程与出网,sync_server管文件,log-collector管日志- 控制面从不直连 Agent 进程,只通过 envd 注入配置,Agent 由沙箱内部自行派生
洞察 2:Agent 是"可替换的租户",沙箱是"稳定的宿主"
- 三个
codebuddy实例由sandbox-proxy统一派生,会话切换只是换一个 prewarm 实例 - Agent 崩溃不影响沙箱存活:
sandbox-proxy在,随时能再拉一个
洞察 3:prewarm 池 = 首字延迟优化
CODEBUDDY_STREAM_TIMEOUT_MS=300000、CODEBUDDY_FIRST_TOKEN_TIMEOUT_MS=300000(均 5 分钟)- 两份待命实例让新会话无需冷启动
洞察 4:热升级路径清晰
sandbox-proxy从 COS 分发,支持box-current软链切版本- Agent 包在
codebuddy-codenpm 目录(独立 overlay 挂载),可单独更新
排障提示
| 现象 | 正确排查方式 |
|---|---|
| 内存/CPU 显示与预期不符 | 读cat /sys/fs/cgroup/memory.max、cpu.max,不要用free |
| 判断某能力是否开启 | curl -s localhost:49983/envs | grep ENABLE_ |
| 确认 Agent 归属 | ps --ppid <sandbox-proxy-pid>看是否为其子进程 |
| 出网异常 | 检查到172.24.0.1:80的连接,而非公网直连 |
| 日志丢失 | 检查nc 172.24.0.1 18100链路与SANDBOX_LOG_FORWARD=1 |
附录:一句话对照表
| 概念 | 具体实体 | 定位 |
|---|---|---|
| 沙箱 | CloudStudio spacelet(K8s Pod / 容器) | 宿主,提供机器、文件、网络、命令 |
| 控制面 | CloudStudio SaaS(codingcorp.cloudstudio.net/api) | 调度与配置来源 |
| 配置通道 | envd:49983 | 控制面进入沙箱的唯一入口 |
| Agent | codebuddy(@tencent-ai/codebuddy-code) | 沙箱内的租户进程,执行任务 |
| Agent 启动器 | sandbox-proxy:9090/65225 | 派生 Agent + 出网代理 |
| 边界出口 | 宿主网关172.24.0.1(:80出网 /:18100日志) | 沙箱与外界的唯一出入口 |
CodeBuddy 沙箱系统架构 — 文字版
一、整体概览
这是一个运行在Docker 容器内的 IDE 沙箱环境,操作系统Ubuntu 24.04,配备123GB 内存 / 256GB 磁盘。容器入口是docker-init(PID 1),由它拉起supervisord作为总控进程,再由 supervisord 逐级启动所有子服务。
二、层级结构(从外到内)
┌─────────────────────────────────────────────────────┐ │ 🐳 Docker 容器 (PID 1: docker-init) │ │ │ │ ┌──────────┐ │ │ │ PM2 │ 独立进程管理器,管着 sandbox-proxy-mock │ │ │ PID 1378 │ │ │ └──────────┘ │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ supervisord ★ PID 62(总管家) │ │ │ │ │ │ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ IDE 基础服务 │ │ │ │ │ │ · api :65213 (PID 109)│ │ │ │ │ │ · terminal :65310 (PID 103)│ │ │ │ │ │ · local-proxy :65216 (PID 114)│ │ │ │ │ ├─────────────────────────────────┤ │ │ │ │ │ 容器 & 运维 │ │ │ │ │ · envd :49983 (PID 1337) │ │ │ │ · log-collector (PID 95) │ │ │ │ · dockerd (PID 22848) │ │ │ │ · editor_sdk :39099 (PID 42299) │ │ │ │ · sync_server :8089 (PID 42329) │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ▼ 启动/控制 │ │ ┌──────────────────────────────────────────┐ │ │ │ 🔀 sandbox-proxy PID 127(网关) │ │ │ │ 对外端口:9090 / 内部:65225 │ │ │ └────────────────┬─────────────────────────┘ │ │ │ │ │ ┌────────┼────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │主实例 │ │预热 #2 │ │预热 #3 │ │ │ │PID 1412 │ │PID 1413 │ │PID 41711 │ │ │ │sbx-127-1 │ │sbx-127-2 │ │sbx-127-3 │ │ │ │~355MB │ │~222MB │ │~221MB │ │ │ │ ★活跃 │ │ ⏳待命 │ │ ⏳待命 │ │ │ └────┬─────┘ └──────────┘ └──────────┘ │ │ │ │ │ ▼ 派生 │ │ ┌──────────┐ │ │ │当前会话 │ ← 你现在就在这里 │ │ │·zsh 46046│ │ │ │·SA 41933 │ │ │ │·mcp 41861│ │ │ └──────────┘ │ └─────────────────────────────────────────────────────┘三、各组件职责
| 层级 | 组件 | PID | 端口 | 职责 |
|---|---|---|---|---|
| 总控 | supervisord | 62 | — | 进程管理器,启动和监控所有子服务 |
| 进程管理 | PM2 | 1378 | — | 管理 sandbox-proxy-mock |
| IDE 入口 | api | 109 | 65213 | 对外提供 IDE API 接口 |
| 终端 | terminal | 103 | 65310 | Web Terminal 服务 |
| 本地代理 | local-proxy | 114 | 65216 | 本地请求转发代理 |
| 环境守护 | envd | 1337 | 49983 | e2b 沙箱环境管理 |
| 日志 | log-collector | 95 | — | 日志采集与转发 |
| ★ 网关 | sandbox-proxy | 127 | 9090 / 65225 | 请求路由核心,分发到 AI Agent |
| ★ AI Agent | codebuddy 主实例 | 1412 | 40593(RPC) | 当前与你对话的实例,派生 zsh Shell |
| codebuddy 预热#2 | 1413 | — | 备用实例,毫秒级接管新会话 | |
| codebuddy 预热#3 | 41711 | — | 备用实例 | |
| 会话 | zsh Shell | 46046 | — | 你的命令执行环境(由 1412 派生) |
| SheetAgent MCP | 41933 | — | 表格处理 MCP 子服务 | |
| mcp-stdio proxy | 41861 | — | MCP 标准输入输出代理 | |
| 编辑 | editor_sdk | 42299 | 39099 | Office/WPS 文件实时编辑 |
| 同步 | sync_server | 42329 | 8089, 18601 | 数据同步服务 (HTTP + gRPC) |
| 容器 | dockerd | 22848 | — | Docker 引擎(嵌套容器支持) |
| containerd | 22871 | — | 容器运行时 |
四、数据流向
你的浏览器/客户端 │ ▼ HTTP/WebSocket sandbox-proxy (:9090) ← 唯一对外入口,做路由分发 │ ▼ 内部转发 (:65225) codebuddy PID 1412 ← AI Agent 主实例,处理对话逻辑 │ ├──► zsh Shell (PID 46046) → 执行你输入的 bash 命令 ├──► SheetAgent MCP (41933) → 处理 Excel/表格操作 └──► mcp-stdio proxy (41861) → MCP 协议通信关键点:你看到的终端、执行的每条命令,全部运行在PID 1412 (codebuddy sbx-127-1)这个进程的上下文中。PID 1413 和 41711 是两个已经预热的备用实例,随时可以接手新会话。
五、设计特点
- 三级就绪— 1 个活跃 + 2 个预热,新用户进来无需冷启动
- 网关隔离— sandbox-proxy 是唯一入口,内部服务不直接暴露
- 父子派生— 你的 Shell 会话是 AI Agent 的子进程,资源共享且便于管理
- MCP 扩展— SheetAgent 等 MCP 子服务以子进程形式挂载在 Agent 下
- 嵌套 Docker— 容器内还跑了 dockerd,支持在沙箱里再起容器