跳到主要内容

运行时与入口

本章讲「这台机器怎么开机、怎么进门」:一条 docker run 启动什么、对外那个端口如何分流、启动过程的三段 hook、以及鉴权怎么做。

1. 一条命令启动整台机器

最小启动就一行(README.md:38-41):

docker run --security-opt seccomp=unconfined --rm -it \
-e SANDBOX_API_KEY=your-secret-key \
-p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:latest

几个要点:

  • seccomp=unconfined:容器内要跑浏览器 / 多种运行时,需要放宽默认 seccomp 限制。

  • -p 127.0.0.1:8080:8080:容器内服务监听 0.0.0.0:8080,但宿主侧故意只绑到 127.0.0.1——默认只让本机访问。原文点明这是有意为之:

    These examples intentionally bind the host side to 127.0.0.1 because the sandbox listens on 0.0.0.0 inside the container.(README.md:61)

  • SANDBOX_API_KEY:设了就开启 API Key 鉴权(保护 API/JupyterLab/VNC);不设则服务开放(向后兼容)。

启动后,各入口都挂在这一个端口上(README.md:63-67):

入口URL给谁
API 文档/v1/docs开发者
VNC 浏览器/vnc/index.html?autoconnect=true人(看桌面)
VSCode/code-server/人(写代码)
MCP 服务/mcpagent

2. 一个端口,按路径前缀分流

整套设计的入口形态是:对外只有一个 :8080,内部按 URL 路径前缀把请求路由到不同子系统。这一点从 SDK 列出的全部子系统命名空间能直接看到——Sandbox 类把 20 个子客户端挂成属性(sdk/python/agent_sandbox/client.py:106-264):

sandbox shell bash file jupyter nodejs code
mcp browser browser_page browser_tabs browser_cookies
browser_state browser_network browser_captcha
util skills proxy display auth

每个子系统对应一组 /v1/<名字>/... 端点。例如 shell 子系统的真实路径(sdk/python/agent_sandbox/shell/raw_client.py,符号 RawShellClient):v1/shell/execv1/shell/sessions/createv1/shell/terminal-url 等。SDK 是服务端 API 契约的精确镜像——它由 Fern 从 API 定义自动生成(sdk/python/agent_sandbox/client.py:1 的 auto-generated 注释 + sdk/fern/),所以读 SDK 的 raw_client.py 就等于读 API 的方法/路径/参数表。

3. 沙箱上下文与 llms.txt(给 agent 的「机器自述」)

agent 进门第一件事通常是问「这是台什么机器」。client.sandbox.get_context() 返回一段自然语言的环境描述(website/docs/en/guide/basic/sandbox.mdx:110-160),内容包括系统/用户/home 目录/已占用端口、已装的运行时(Python、Node、Git、uv…)和可用工具(vim、jq、yt-dlp…)。其中关键字段:

  • home_dir:沙箱里的 home 目录(如 /home/gem),后续读写文件都基于它。
  • 已占用端口列表:让 agent 知道哪些端口被内部服务占了。

另外沙箱在 /llms.txt 暴露一份给 agent 的使用手册(website/docs/en/guide/basic/sandbox.mdx:99-103):

llms.txt is an usage manual for Agents. Through this manual, the Agent can better use the sandbox.

这是一种「让环境自我描述给模型」的约定:agent 读一遍 llms.txt 就知道该怎么用这台机器,而不必把用法硬编码进 prompt。

4. 启动生命周期:三段 hook

容器启动不是一锤子,而是分阶段,并在阶段之间留出hook 注入点(website/docs/en/guide/advanced/lifecycle.md):

容器启动
→ RUN_HOOK_INIT 最早的初始化(服务准备之前必须跑的)
→ 准备运行用户与 workspace
→ RUN_HOOK_PRE_SERVICES 托管服务启动前:装工具 / 写配置
→ 托管服务启动
→ 就绪检查通过
→ RUN_HOOK_POST_READY 预热:需要 API/浏览器/code-server 已可用时

三个 hook 各管一段(website/docs/en/guide/advanced/lifecycle.md「Hooks」表):

Hook用于
RUN_HOOK_INIT必须在服务准备前跑的最早初始化
RUN_HOOK_PRE_SERVICES在受管服务启动前装工具 / 写配置
RUN_HOOK_POST_READY需要 API/浏览器/code-server 已就绪的预热任务

用法就是启动时塞环境变量,例如先装个 Python 包:

docker run --security-opt seccomp=unconfined --rm -it -p 127.0.0.1:8080:8080 \
-e RUN_HOOK_PRE_SERVICES="python -m pip install requests" \
ghcr.io/agent-infra/sandbox:latest

运行时还能注册关闭 hook。 沙箱跑起来后可以通过 API 注册 shutdown 事件 hook,用来在退出前刷状态 / 写标记(website/docs/en/guide/advanced/lifecycle.md「Runtime Shutdown Hooks」;对应端点 POST/GET/DELETE v1/sandbox/hooks,见 sdk/python/agent_sandbox/sandbox/raw_client.py):

curl -X POST "http://localhost:8080/v1/sandbox/hooks" \
-H "Content-Type: application/json" \
-d '{"name":"write-shutdown-marker","event":"shutdown",
"command":"printf stopped > /home/gem/workspace/shutdown.txt",
"timeout":10,"priority":100}'

注意: hook 的具体编排逻辑(谁先跑、超时怎么处理)在容器镜像内部,本 repo 看不到;上面是文档与 API 契约层面的事实,内部实现 (inferred)

5. 鉴权:两条路

沙箱默认面向可信的本地开发;一旦要暴露给网络或别的服务,就得加鉴权(website/docs/en/guide/advanced/security.md)。有两种机制:

(a) API Key(简单)。SANDBOX_API_KEY 后,三种方式都能带 key(README.md:35-40):

  • X-AIO-API-Key 请求头
  • Authorization: Bearer <key> 请求头
  • ?api_key= 查询参数(给那些没法带 header 的场景,如浏览器直开 URL)

(b) JWT 公钥验签(适合多服务架构)。JWT_PUBLIC_KEY(base64 编码的公钥),你的业务后端用私钥签发 JWT,沙箱用公钥验签(website/docs/en/guide/basic/authentication.mdwebsite/docs/en/guide/advanced/security.md):

# 1) 生成密钥对
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
# 2) 启动时把公钥交给沙箱
docker run ... -e JWT_PUBLIC_KEY="$(base64 -w 0 public_key.pem)" ghcr.io/agent-infra/sandbox:latest
# 3) 调 API 带 Bearer token
curl "http://localhost:8080/v1/sandbox" -H "Authorization: Bearer $SANDBOX_TOKEN"

短时票据(short-lived ticket)。 对于浏览器访问、临时交接这类「没法塞 header」的流程,文档建议用快速过期、最小权限的短时票据,通过 ?ticket= 传(website/docs/en/guide/basic/authentication.md「Short-lived Ticket」、website/docs/en/guide/advanced/security.md)。VNC 这种纯 URL 访问就靠它。

鉴权发生在入口层。 三种入口(REST API / JupyterLab / VNC)由同一把 key 保护(README.md:36),所以鉴权逻辑统一在 :8080 入口,而不是每个子系统各做一遍 (inferred)

6. 网络与密钥的硬规矩

安全文档给的是一串「别做什么」(website/docs/en/guide/advanced/security.md):

  • 非必要不暴露:默认绑 localhost;共享环境前面套带 TLS 的反向代理;按 IP/网络/服务身份限制入站。
  • 绝不把未鉴权的沙箱 API 暴露到公网。
  • 密钥走运行时环境变量或 secret 管理器,不要烤进镜像层;不要把长期密钥写进 Skills / hooks / notebook / 生成文件;优先短期凭证。

7. 代码地图

主题文件路径符号名
全部子系统命名空间sdk/python/agent_sandbox/client.py:106-264Sandbox@property
沙箱上下文 / hooks / observe 端点sdk/python/agent_sandbox/sandbox/raw_client.pyv1/sandbox, v1/sandbox/hooks, v1/sandbox/observe/*
沙箱上下文与 llms.txtwebsite/docs/en/guide/basic/sandbox.mdx:97-160get_context, home_dir, /llms.txt
生命周期 hookwebsite/docs/en/guide/advanced/lifecycle.mdRUN_HOOK_INIT/PRE_SERVICES/POST_READY
JWT 鉴权流程website/docs/en/guide/basic/authentication.mdJWT_PUBLIC_KEY
安全边界 / 密钥规矩website/docs/en/guide/advanced/security.md
API Key 三种传法README.md:35-40SANDBOX_API_KEY, X-AIO-API-Key