Claude Cookbooks 实战:为 Managed Agents 自建执行沙箱(Self-Hosted Sandboxes)的六种参考实现与全流程指南
导读
Claude Managed Agents 允许 Agent 通过工具(bash、read、write、edit、glob、grep)在沙箱中执行任务。当你对数据驻留、网络隔离或运维控制有更高要求时,可以把执行环境从云端换到自建(Self-Hosted)沙箱——把 Agent 的整个执行面托管在你自己控制的计算资源上。本指南以 claude-cookbooks 仓库中 managed_agents/self_hosted_sandboxes 目录下的参考实现为核心,完整讲解"环境钥匙(environment key)统一鉴权"的架构契约、ant CLI / SDK 的两种 worker 形态,以及 Docker、Cloudflare、Modal、Daytona、Vercel 六个平台的端到端接入与升级迁移。读完后,你能独立完成自建环境的创建、webhook 注册、worker 部署与按会话隔离的执行沙箱搭建。
一、总体架构:一次 webhook 触发的完整契约
目录下的所有变体都实现同一个工作契约,只是运行在不同计算平台上。顶层 README.md 把它归纳为三个步骤:
- 接收
session.status_run_startedwebhook,用client.beta.webhooks.unwrap()校验签名; - 排空(drain)环境工作队列,让单次投递即可恢复此前遗漏的所有工作项(work item);
- 对每个工作项启动一个按会话隔离的沙箱,在其中运行 SDK/CLI 工具执行器(提供
bash/read/write/edit/glob/grep六件套),对工作项租约(lease)做心跳,并把tool_result回传给会话。
其中最值得注意的安全设计是:组织级 API Key 永远不会到达执行器。沙箱只用环境钥匙(environment key)完成鉴权,这把钥匙是控制面(webhook 轮询、ack、stop)与按会话调用(事件流、心跳、技能下载)共用的唯一凭据。在 usage-guide.md 中也明确写道:这把钥匙"authenticates the whole worker flow — poll, ack, stop, heartbeat, the session event stream, and skill download — for that one environment. It is the only credential the worker needs"。
目录提供的六个参考实现如下表:
| 变体 | 计算平台 | 执行器形态 |
|---|---|---|
| docker/ | 你自己控制的普通 Docker | 按会话容器里跑 ant beta:worker run |
| cf/ | Cloudflare Containers | 按会话的 Cloudflare Container 里跑 ant beta:worker run |
| cf-worker/ | Cloudflare Workers(无容器) | Durable Object 内跑 TS SessionToolRunner,带隔离岛内伪文件系统 |
| modal/ | Modal | Modal Sandbox 里跑 Python sandbox_runner.py,挂载按会话 Volume |
| daytona/ | Daytona | 复用同一个 sandbox_runner.py 上传到 Daytona 沙箱 |
| vercel/ | Vercel Functions + Sandbox | Vercel Sandbox 里跑 Node runner.mjs |
Docker 变体的 README 明确指出一个共同设计原则:ant beta:worker poll 负责直接长轮询环境工作队列(不需要暴露公网 webhook),而 Modal/Daytona/Vercel/Cloudflare 变体则是 webhook 驱动(由云端函数作为唤醒信号触发)。两类模式走的是同一套 ANTHROPIC_* 环境变量契约和同一套 runner。
二、前置条件与鉴权模型
所有公开 API 调用都需要如下请求头(见 usage-guide.md):
anthropic-version: 2023-06-01
anthropic-beta: managed-agents-2026-04-01
SDK 与 CLI 依赖
# Python SDK
uv pip install anthropic
# TS SDK
npm i @anthropic-ai/sdk
托管文档中写明的发布构建版本为 Python SDK 0.103.0、TypeScript SDK 0.97.0、ant CLI v1.9.0;不过各变体内部的实际版本可能更新——例如 Docker 变体的 Dockerfile 与 README 已把 ANT_VERSION 钉到 1.10.0。因此实操时请以各变体当前文件内钉住的版本为准,并保持宿主机的 ant 与镜像内版本一致。
在目标机器上安装 ant CLI
ant 内置了两种 worker:ant beta:worker poll(长驻轮询)与 ant beta:worker run(单会话执行)。在 Linux / macOS 上按如下方式安装并核对版本:
VERSION=1.9.0
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')
curl -fsSL "https://github.com/anthropics/anthropic-cli/releases/download/v${VERSION}/ant_${VERSION}_${OS}_${ARCH}.tar.gz" \
| sudo tar -xz -C /usr/local/bin ant
ant --version
注:README 与 usage-guide 中的安装脚本指向的是 anthropic-cli 的 GitHub Release 归档;仓库是只读的,请在目标机器上自行执行安装与运行。
三、三步创建自建环境并启动 worker
第 1 步:创建 Self-hosted 环境
在 Console → Workspace → Environments → New → Self-hosted 中创建;也可以用代码创建。Python:
client = anthropic.Anthropic(api_key=API_KEY)
environment = client.beta.environments.create(
name="self-hosted",
config={"type": "self_hosted"},
)
TypeScript:
const client = new Anthropic({ apiKey: API_KEY });
const environment = await client.beta.environments.create({
name: "self-hosted",
config: { type: "self_hosted" },
});
第 2 步:生成并设置环境钥匙
打开该环境,点击 Generate environment key,把生成的 sk-ant-oat01-... 导出到运行 worker 的主机上:
export ANTHROPIC_ENVIRONMENT_KEY="sk-ant-oat01-..."
第 3 步:启动 worker
ant beta:worker poll 内置了循环逻辑:认领分配给该环境的会话 → 在 --workdir 中执行工具调用(bash/read/write/edit/glob/grep)→ 回传结果:
ant beta:worker poll \
--environment-id "env_01..." \
--workdir "/workspace"
每个 flag 都可以改用环境变量,因此对 systemd 或 compose 场景,零 flag 调用同样成立:
ANTHROPIC_ENVIRONMENT_ID=env_01... \
ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat01-... \
ant beta:worker poll --workdir /workspace
worker 在收到 SIGTERM / SIGINT 时会干净退出,先排空进行中的工具调用再停止。
四、两种 worker 形态与按工作项派生进程
形态 A:长驻 poller + --on-work 外部脚本
想让每个认领到的工作项都跑在你自定义的隔离进程里,就传 --on-work <script>。脚本会收到 ant beta:worker run 同款环境变量(ANTHROPIC_WORK_ID、ANTHROPIC_ENVIRONMENT_ID、ANTHROPIC_SESSION_ID、ANTHROPIC_ENVIRONMENT_KEY;ANTHROPIC_BASE_URL 被继承),原始 work JSON 从 stdin 传入。最简单的脚本是用 Docker 按会话拉起容器:
#!/bin/bash
exec docker run --rm \
-e ANTHROPIC_SESSION_ID -e ANTHROPIC_ENVIRONMENT_KEY \
-e ANTHROPIC_WORK_ID -e ANTHROPIC_ENVIRONMENT_ID -e ANTHROPIC_BASE_URL \
your-image ant beta:worker run --workdir /workspace
语义要点:poller 会等待脚本退出后才继续轮询;脚本返回非零只会记日志,不会终止 poller;对 poller 发 SIGTERM 会级联到脚本。
形态 B:容器 entrypoint(每会话一个执行进程)
如果你的控制面是"每个会话现拉一个新容器"而不是"一个常驻 poller",就把 ant beta:worker run 作为容器入口点。它不做轮询,直接附着到单个会话上执行。Dockerfile 示例:
FROM your-base-image
ARG ANT_VERSION=1.9.0
ARG TARGETOS=linux
ARG TARGETARCH=amd64
ADD https://github.com/anthropics/anthropic-cli/releases/download/v${ANT_VERSION}/ant_${ANT_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz /tmp/ant.tgz
RUN tar -xzf /tmp/ant.tgz -C /usr/local/bin ant && \
chmod +x /usr/local/bin/ant && rm /tmp/ant.tgz
WORKDIR /workspace
ENTRYPOINT ["ant", "beta:worker", "run"]
启动容器时传入 ANTHROPIC_SESSION_ID、ANTHROPIC_ENVIRONMENT_KEY、ANTHROPIC_WORK_ID、ANTHROPIC_ENVIRONMENT_ID 四个环境变量即可。
形态 C:直接在宿主上跑 run
如果会话信息已经就绪(例如你自己的编排器已认领 work,并把 worker 作为子进程拉起),可直接调用:
ant beta:worker run \
--session-id "sesn_..." \
--environment-key "$ANTHROPIC_ENVIRONMENT_KEY" \
--work-id "work_..." \
--environment-id "env_..." \
--workdir "/workspace"
纯环境变量写法:
export ANTHROPIC_SESSION_ID=sesn_...
export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat01-...
export ANTHROPIC_WORK_ID=work_...
export ANTHROPIC_ENVIRONMENT_ID=env_...
ant beta:worker run --workdir /workspace
ant beta:worker run 附着到会话的事件流、执行工具调用,并在会话终止或 --max-idle(默认 end_turn 空闲后 1 分钟)超时时退出 0;任何其他事件都会重置空闲计时器。
Flags 一览
usage-guide.md 给出的完整参数表:
| Flag | 环境变量 | 默认值 |
|---|---|---|
--environment-id |
ANTHROPIC_ENVIRONMENT_ID |
必填 |
--environment-key |
ANTHROPIC_ENVIRONMENT_KEY |
必填 |
--on-work |
内置进程内 runner | |
--worker-id |
ANTHROPIC_WORKER_ID |
hostname |
--workdir |
. |
|
--unrestricted-paths |
false |
|
--max-idle |
end_turn 空闲后 1m |
|
--log-format |
text(或 json) |
|
--base-url |
ANTHROPIC_BASE_URL |
api.anthropic.com |
需要强调的是:worker 直接在宿主上执行 shell 与文件操作,因此官方文档要求把它放进容器或其他你控制的隔离边界内运行——这正是"self-hosted"沙箱的核心语义。
五、库级用法:把 worker 内嵌进自己的进程
同一套 poll/run 逻辑在 Python 与 TypeScript SDK 中以库的形式暴露。client.beta.environments.work.worker(...) 把整个循环组合起来:poll → 准备 workdir + 下载会话 Agent 的 skills → 执行工具(同时对工作项租约做心跳)→ 退出时 force-stop → 循环。它接受与 client.beta.messages.tool_runner / .toolRunner 相同的工具类型,因此你在 Python 里用 @beta_async_tool、在 TS 里用 betaZodTool / BetaRunnableTool 自定义的工具,都可以通过 tools= 与默认工具一并传入。
Python 库用法
import asyncio, os
from anthropic import AsyncAnthropic
environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
async def main() -> None:
async with AsyncAnthropic(auth_token=environment_key) as client:
await client.beta.environments.work.worker(
environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"],
environment_key=environment_key,
workdir="/workspace",
).run()
asyncio.run(main())
.handle_item() 是逐项形态——适合 --on-work 脚本或每个会话单独派生的沙箱:它读取 ANTHROPIC_* 环境变量,只服务那一个已被认领的工作项。这一点在 Modal 的 sandbox_runner.py 中有完整印证:脚本内部只做
await client.beta.environments.work.worker(
environment_key=environment_key,
workdir=WORKDIR,
unrestricted_paths=True,
).handle_item()
并显式配置了标准库 logging([runner] 前缀输出到 stdout),因为 EnvironmentWorker 用 logging.INFO 上报 start / idle-out / heartbeat shutdown / 流重连 / 工具派发等生命周期事件,若不配 handler 会全部被丢弃。
TypeScript 库用法
import Anthropic from '@anthropic-ai/sdk';
const environmentKey = process.env.ANTHROPIC_ENVIRONMENT_KEY!;
const client = new Anthropic({ authToken: environmentKey });
const ctrl = new AbortController();
process.once('SIGTERM', () => ctrl.abort());
await client.beta.environments.work
.worker({
environmentId: process.env.ANTHROPIC_ENVIRONMENT_ID!,
environmentKey,
workdir: '/workspace',
signal: ctrl.signal,
})
.run();
.handleItem() 是逐项形态(同样读取 ANTHROPIC_* 环境变量)。Go SDK 的 worker 支持在文档中标记为 TODO: pending a released Go SDK build。
六、裁剪与扩展默认工具集
beta_agent_toolset_20260401(env)(Python)/ betaAgentToolset20260401(ctx)(TS)以列表形式返回 agent_toolset_20260401 的实现:bash、read、write、edit、glob、grep。你可以过滤或扩展它,再作为 tools 传给 worker(...)——该工厂会针对每个被认领的会话、以该会话的工具上下文调用一次:
from anthropic.lib.tools import beta_async_tool
from anthropic.lib.tools.agent_toolset import (
AgentToolContext, beta_agent_toolset_20260401, beta_bash_tool, beta_read_tool,
)
# 去掉 grep,加一个自定义工具
@beta_async_tool
async def fetch_url(url: str) -> str: ...
def tools(env: AgentToolContext):
return [t for t in beta_agent_toolset_20260401(env) if t.name != "grep"] + [fetch_url]
# 或者只用单个工厂拼装
def tools(env: AgentToolContext):
return [beta_bash_tool(env), beta_read_tool(env), my_custom_tool]
client.beta.environments.work.worker(..., tools=tools)
TypeScript 侧对应写法:
import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod';
import { betaAgentToolset20260401, betaBashTool } from '@anthropic-ai/sdk/tools/agent-toolset/node';
client.beta.environments.work.worker({
...,
tools: (ctx) => [...betaAgentToolset20260401(ctx).filter(t => t.name !== 'grep'), myZodTool],
});
自定义工具是自建沙箱里注入数据库凭据等敏感信息的推荐路径(见下文 MongoDB 示例中"最小权限"的讨论)。
七、变体深潜:六种计算平台的端到端接入
7.1 Docker:无需暴露公网的纯自托管
Docker 变体是 cf 变体的"无云版本",完整诠释了 poller + 按会话容器两级架构。其 README 给出的运行方式是:
export ANTHROPIC_ENVIRONMENT_ID=env_...
export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat...
./start.sh
三个核心文件各司其职:
- Dockerfile:按会话镜像。钉住
antCLI(ARG ANT_VERSION=1.10.0),WORKDIR /workspace,ENTRYPOINT ["ant","beta:worker","run","--workdir","/workspace","--unrestricted-paths","--max-idle","60s","--log-format","json"]。CLI 自己负责心跳、积压对账(backlog reconcile)、SSE、六件套工具与退出时的 force-stop。 - on-work.sh:poller 每认领一个工作项就调用它。脚本校验四个必填环境变量、排空 stdin 的 work JSON,然后用前台
exec(不是docker run -d)拉起--rm的按会话容器,容器名cma-${ANTHROPIC_SESSION_ID}、卷cma-ws-${ANTHROPIC_SESSION_ID}。为什么必须是前台阻塞?文件注释解释得很清楚:ant beta:worker poll在--on-work脚本返回的瞬间就会对该工作项发 stop(无 CLI 开关可关),如果docker run -d提前返回,poller 会抢在容器认领工作前把它停掉("heartbeat reports shutdown, state stopped"),bash 工具调用永远不会执行。脚本还做了幂等保护:若同名容器已在运行,重复的工作项直接 no-op 退出。 - start.sh:构建镜像并以
--on-work on-work.sh --workdir /tmp --log-format json启动宿主 poller。
一个值得注意的实现细节(README 与 Dockerfile 都强调):容器除 ANTHROPIC_ENVIRONMENT_KEY 外,还需要把同一把钥匙作为 ANTHROPIC_AUTH_TOKEN 传入,因为 CLI 的技能下载客户端只解析 ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN,并不识别 ANTHROPIC_ENVIRONMENT_KEY——缺少它技能会静默下载失败。
由于该变体没有 webhook,ant beta:worker poll 直接长轮询环境队列,因此不需要向公网暴露任何东西。成功后按顺序能看到:
[start] polling env=env_... base=https://api.anthropic.com
[on-work] session=sesn_... work=work_... container=... (started)
容器在空闲超时后自删,而 cma-ws-<session_id> 卷会保留给该会话的下一轮消息(docker volume rm 可丢弃)。
7.2 Cloudflare 双变体:容器 vs 纯 Worker
cf(Container 变体) 的 README 描述:Worker(src/index.ts)用 client.beta.webhooks.unwrap() 校验 session.status_run_started 后排空环境工作队列(poll → ack 直到空),对每个工作项启动一个 entrypoint 为 ant beta:worker run 的按会话 Cloudflare Container(src/container.ts)。CLI 拥有空闲策略;Durable Object 拥有 Cloudflare 侧容器生命周期:它会持续流式读取会话状态来续期 sleepAfter,防止 CF 在 ant beta:worker run 还活着时回收 VM。部署命令:
npm i
wrangler secret put ANTHROPIC_WEBHOOK_SECRET
wrangler secret put ANTHROPIC_ENVIRONMENT_KEY
# 编辑 wrangler.toml 里的 ANTHROPIC_ENVIRONMENT_ID,然后:
wrangler deploy
cf-worker(纯 Worker 变体) 与容器版同一结构(webhook → 排空队列 → 按会话 runner),但 runner 是 Durable Object 里跑的 TS client.beta.sessions.events.toolRunner()(src/runner.ts),配一个隔离岛内伪文件系统(src/tools.ts):read/write/edit/glob/grep 作用于 DO 内存中的一个 Map<string,string>,bash 返回不可用桩。它同时是 SessionToolRunner 低层 API 的 TS 库用法范本(自定义工具、由 DO 负责心跳与 force-stop)。但若要真实 shell、或要用组合完整工作项生命周期的 EnvironmentWorker(它依赖 Node-only 的 agent-toolset/node 模块,Workers isolate 无法运行),应改用 Container 或 Vercel/Modal 变体。
7.3 Modal:Python webhook + 可复用 runner
Modal README 提到两个文件的分工:
modal_sandbox_webhook.py:Modal 应用,接收 webhook →unwrap()校验 →client.beta.environments.work.poller(drain=True, auto_stop=False)排空队列 → 每项拉起一个按会话 Modal Sandbox,并把按会话的modal.Volume挂到/workspace,让 Agent 工作树与已下载技能跨沙箱重启保留;sandbox_runner.py:如前所述,在沙箱内以handle_item()完成"读环境变量 → 构建按会话AgentToolContext→ 技能下载到/workspace/skills/<name>/→ 跑SessionToolRunner(心跳 + 对账 + 事件流 + 六件套派发 + 结果回传)→ 退出时 force-stop"的完整闭环。
操作流程:
pip install modal
modal setup # 认证你的 Modal workspace
# 配置(首次用 placeholder)
modal secret create cma-self-hosted-sandboxes-secrets \
ANTHROPIC_WEBHOOK_SECRET=placeholder \
ANTHROPIC_ENVIRONMENT_ID='env_...' \
ANTHROPIC_ENVIRONMENT_KEY='sk-ant-oat...'
# 部署,会打印一个 *.modal.run 的 URL
modal deploy modal_sandbox_webhook.py
把该 URL 注册为 session.status_run_started 的 webhook,拿到下发的 secret 后用 --force 更新密钥(无需重新部署——密钥在容器启动时读取):
modal secret create cma-self-hosted-sandboxes-secrets \
ANTHROPIC_WEBHOOK_SECRET='whsec_...' \
ANTHROPIC_ENVIRONMENT_ID='env_...' \
ANTHROPIC_ENVIRONMENT_KEY='sk-ant-oat...' \
--force
测试(Python 侧):
session = client.beta.sessions.create(agent=agent_id, environment_id=ENVIRONMENT_ID)
client.beta.sessions.events.send(session.id, events=[{"type": "user.message", "content": "ls -la"}])
modal app logs cma-self-hosted-sandboxes 中依次可见 [webhook] event=session.status_run_started ... 与 [webhook] acked work=... session=... sandbox=sb-... (created);[runner] 行显示在 Modal 控制台 Apps → Sandboxes 下。修改 Python 文件需重新 deploy;只改密钥不用;迭代时可先 modal app stop 强制清场。
7.4 Daytona:同一份 runner 的 FastAPI 封装
Daytona README 本质上是把同一个 provider 无关的 sandbox_runner.py 上传进 Daytona 全功能 Linux 容器,因此 beta_agent_toolset_20260401 六件套原样可用。daytona_webhook.py 是 FastAPI 应用,负责 unwrap() 校验 → poller(drain=True, auto_stop=False) 排空 → 逐项创建 Daytona 沙箱并启动 runner。运行:
# standardwebhooks 支撑 client.beta.webhooks.unwrap()——只有编排宿主需要它,
# 内层 Daytona 沙箱永远不会看到原始 webhook 投递。
pip install fastapi uvicorn daytona-sdk standardwebhooks anthropic
export DAYTONA_API_KEY=... DAYTONA_API_URL=...
export ANTHROPIC_WEBHOOK_SECRET=... \
ANTHROPIC_ENVIRONMENT_ID=env_... ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat...
uvicorn daytona_webhook:app --host 0.0.0.0 --port 8080
把 FastAPI 应用部署到任何能提供 HTTP 且可达 Daytona API 的地方(Fly、Render、隧道后的 VM 等),再注册 webhook。README 特别提醒 冷启动开销:_spawn() 会在每个新建沙箱内 pip install anthropic,增加约 10–15 秒;生产环境应把 SDK 预烘焙进自定义 Daytona 镜像并去掉该行。
7.5 Vercel:TS runner + 可选的 KV 沙箱复用
Vercel README 与 Modal/Daytona/CF 共享"webhook → 排空 → 按会话 runner"骨架,但 runner 是 Vercel Sandbox 中运行的 TS EnvironmentWorker.handleItem() + betaAgentToolset20260401(),直接作用于沙箱真实文件系统。其 runner/runner.mjs 负责构建按会话 AgentToolContext、下载技能、跑 SessionToolRunner 并做心跳、退出时 force-stop。
关键工程决策:
- webhook 只是唤醒信号(api/webhook.ts):校验签名 → 排空
work.poll()(上限 25 个)→ 逐项 ack、创建 Sandbox、上传runner.mjs并分离式启动(detached)。坏工作项只记日志并被跳过——它保持未 ack 状态,由下一次 webhook 重新认领,不会卡死队列。 - 冷启动预算:
Sandbox.create()+writeFiles()+npm install约 15–25 秒;vercel.json 设了maxDuration: 60,函数还会把 runner 前约 30 秒输出镜像进函数日志便于vercel logs排障。想再省约 10 秒可用 esbuild 预打包 runner 上传。 - 沙箱复用靠 KV:Vercel Sandbox 没有原生 tag/get-by-name API。若挂载 Vercel KV(设置了
KV_REST_API_URL/KV_REST_API_TOKEN),webhook 会记录session_id → sandbox_id并在下一次run_started复用运行中的沙箱、调用extendTimeout()让活跃会话跨轮次保住 VM;无 KV 时每次投递都新建沙箱——SessionToolRunner用seen/answered去重,重复 runner 只是浪费而非错误。 - 工作目录是临时目录:webhook 启动时创建
/mnt/session与/workspace,以/workspace作为派发 workdir(技能下载到/workspace/skills/<name>/);它们是纯临时目录,Vercel Sandbox 无卷 API,沙箱停止后工作树即消失——这恰与 Modal/CF 的持久化 Volume 形成对照。
配置与部署:
npm install
vercel link
vercel env add ANTHROPIC_WEBHOOK_SECRET # 先用 placeholder
vercel env add ANTHROPIC_ENVIRONMENT_ID
vercel env add ANTHROPIC_ENVIRONMENT_KEY
vercel deploy --prod
将打印出的 https://<project>.vercel.app/api/webhook 注册为 session.status_run_started webhook,拿到真实 secret 后替换并重新部署:
vercel env rm ANTHROPIC_WEBHOOK_SECRET production
vercel env add ANTHROPIC_WEBHOOK_SECRET production
vercel deploy --prod
测试方式与 Modal 相同(创建指向该 environment 的会话并发消息),vercel logs 中应依次出现 [webhook] event=... 与 [webhook] acked work=... sandbox=sbx_... (created)。
八、在沙箱中访问数据库:以 MongoDB 为例的自建优势
自建沙箱带来的独特能力是可以给沙箱注入真实凭据。Docker 变体镜像捆绑了 python3 + pymongo(外加 python3-dnspython——Dockerfile 注释强调它是 mongodb+srv:// URI 的硬性依赖,缺少会直接报 "The dnspython module must be installed…"),on-work.sh 会把可选的 MONGO_URI 转发进每个按会话容器,于是 Agent 可以直接从 bash 工具查询 MongoDB:
python3 -c 'import os; from pymongo import MongoClient; \
c = MongoClient(os.environ["MONGO_URI"]); \
print(list(c["mydb"]["mycoll"].find({}, {"_id": 0}).limit(5)))'
宿主侧在运行 ./start.sh 前设置即可(它会顺着 poller → on-work.sh → 容器一路传递):
export MONGO_URI="mongodb+srv://<user>:<password>@<cluster>/"
Docker 变体 README 特别点明:MONGO_URI 是你的密钥,不是 Anthropic 的。因为是自建容器,它只是一个普通环境变量,永远不进入控制面与会话事件历史——这正是 self-hosted 相比云沙箱的核心优势(云沙箱没有环境变量或 vault 通道给数据库密钥,只能把凭据留在宿主侧、藏在自定义工具后面)。相关对照材料见 mongodb_on_cma/README.md 与 CMA_with_mongodb_atlas.ipynb。
最小权限提示:Agent 的 bash 运行在该容器内、能读到 MONGO_URI——当你信任任务内容时没问题;若需要最小权限,应把内置工具集替换为只暴露窄接口 mongo_query(...) 的自定义 worker 工具,而不是把原始 URI 交给模型。偏好 MongoDB Shell 的话,可往镜像里加 mongosh(MongoDB 官方 apt 源)再调用 mongosh "$MONGO_URI" --eval '…'。
九、从旧版 SDK 迁移:一次聚焦三类破坏性变更的升级指南
upgrade-guide.md 是一份面向"前一版文档用户"的迁移手册,把变化精确划分为三个相互独立的变更,可以按任意顺序逐项处理。升级对象为已发布 SDK 构建:Python anthropic 0.103.0(PyPI)、TypeScript @anthropic-ai/sdk 0.97.0(npm)、ant CLI v1.9.0(GitHub Release),取代此前文档中引用的 anthropic.lib.runner、tool_dispatcher/toolDispatcher、default_tools/defaultTools、按工作项下发的 secret/sessions_token、ant worker poll/ant worker dispatch 等预发布构件。
变更一:唯一凭据——environment key
旧的鉴权故事是"双钥匙":一个 service key 只授权 work.poll,每个被认领的工作项再携带一个 secret,解码成按工作项有效的 sessions_token,用于一切会话级调用(事件流、心跳、force-stop、技能下载)。新模型下只有一把 environment key 认证全部这些调用,worker 流程中不再有按工作项的密钥,decodeWorkSecret 不再导出,sessions_token 消失。对应改名:
| 旧 | 新 |
|---|---|
ENVIRONMENT_SERVICE_KEY / ANTHROPIC_ENV_KEY |
ANTHROPIC_ENVIRONMENT_KEY |
service_key= / serviceKey:(SDK) |
environment_key= / environmentKey: |
secret / sessions_token / decodeWorkSecret |
(从 worker 流程移除) |
| Console:Generate service key | Generate environment key |
变更二:ant CLI 命令改名
旧文档在 CLI 层几乎全部过时:worker 前缀与 dispatch 子命令都被改了名,原因在于发布版 CLI 中 beta: 前缀是 API 资源命令(beta:environments:work、beta:sessions…),自托管 worker 归到同一前缀下成为 beta:worker,其容器入口点子命令由 dispatch 改名为 run。run 的环境变量契约是 ANTHROPIC_{SESSION_ID,ENVIRONMENT_KEY,WORK_ID,ENVIRONMENT_ID,BASE_URL}。
| 旧 | 新 |
|---|---|
ant worker poll |
ant beta:worker poll |
ant worker dispatch |
ant beta:worker run |
--service-key / ANTHROPIC_ENV_KEY |
--environment-key / ANTHROPIC_ENVIRONMENT_KEY |
--session-token flag |
(移除——run 使用 --environment-key) |
--allow-absolute-paths |
--unrestricted-paths |
ANTHROPIC_SESSION_TOKEN env var |
ANTHROPIC_ENVIRONMENT_KEY |
diff 形式的典型修改如下:
- ant worker dispatch \
+ ant beta:worker run \
--session-id "sesn_..." \
- --session-token "$SESSION_TOKEN" \
+ --environment-key "$ANTHROPIC_ENVIRONMENT_KEY" \
--work-id "work_..." \
--environment-id "env_..." \
- --workdir "/workspace"
+ --workdir "/workspace" --unrestricted-paths
变更三:库形态重构——模块搬家、符号改名、dispatch 拆分
anthropic.lib.runner(Python)与 @anthropic-ai/sdk/helpers/beta/runner(TS)不再存在。三类变化:模块路径迁移、符号改名、以及 dispatcher 一分为二。
3a. 模块路径:
| 用途 | Python | TypeScript |
|---|---|---|
| worker 组合 / poller / runner | anthropic.lib.environments |
@anthropic-ai/sdk/helpers/beta/environments |
| 工具实现 + 技能下载 | anthropic.lib.tools.agent_toolset |
@anthropic-ai/sdk/tools/agent-toolset/node |
3b. 符号改名:
| 旧 | 新(Python) | 新(TypeScript) |
|---|---|---|
ToolEnv |
AgentToolContext |
AgentToolContext |
default_tools / defaultTools |
beta_agent_toolset_20260401 |
betaAgentToolset20260401 |
bash_tool、read_tool… |
beta_bash_tool、beta_read_tool… |
betaBashTool、betaReadTool… |
tool_dispatcher / toolDispatcher |
tool_runner |
toolRunner |
decodeWorkSecret |
(移除) | (移除) |
service_key= / serviceKey: |
environment_key= |
environmentKey: |
TS 还多一处:toolRunner / SessionToolRunner 的 sessionId 改为位置参数——toolRunner({ sessionId, tools }) → toolRunner(sessionId, { tools });工具每次调用 run(args, context) 的上下文对象 BetaToolRunContext 把 toolUseBlock 字段改名为 toolUse(旧名保留为 deprecated 别名)。
3c. dispatcher 拆分——tool_dispatcher → SessionToolRunner + EnvironmentWorker:
旧的 tool_dispatcher 接收 work_id + environment_id + session_token,独占整个工作项生命周期(心跳、对账、事件流、结果回传、退出 force-stop)。拆分后:
SessionToolRunner(client.beta.sessions.events.tool_runner(...)/.toolRunner(...))只负责派发:事件流 + 工具执行 + 结果回传,接收session_id+tools(Python 还可选接收environment_key;TS 的toolRunner从 client 读取鉴权)——没有work_id、没有environment_id,也不做心跳与 force-stop。EnvironmentWorker才是完整组合:poll → 准备 workdir + 下载技能 → 跑一个SessionToolRunner同时对租约心跳 → 退出 force-stop → 循环。用工厂client.beta.environments.work.worker(...)(Python 与 TS SDK 都有)或直接EnvironmentWorker(...)/new EnvironmentWorker({...})构造。
迁移指南给出的普适升级路径一句话可概括:把手工的
poller+tool_dispatcher循环替换为client.beta.environments.work.worker(...)。
3d. 三个 API 的分工备忘:
EnvironmentWorker(client.beta.environments.work.worker(...)):完整组合;.run()是长驻轮询循环,.handle_item()/.handleItem()是逐项流程(webhook handler、ant beta:worker poll --on-work场景)——无参数时读取ANTHROPIC_*环境变量。client.beta.environments.work.poller(...):仅控制面,长轮询并逐个 ack,产出BetaSelfHostedWork;不再返回(work, secret)元组;参数service_key→environment_key/environmentKey。client.beta.sessions.events.tool_runner(...)/.toolRunner(...):仅派发,工作项生命周期(心跳、force-stop)由调用方负责;TS 用toolRunner(sessionId, opts)。client.beta.webhooks.unwrap(...):不变。
3e–3g. Python 与 TS 的 before / after 示例(完整代码见 upgrade-guide.md,此处给出最关键的 Python 对比):
# BEFORE:手工 poller + tool_dispatcher + 按工作项 secret
import anyio, os
from anthropic import AsyncAnthropic
from anthropic.lib.runner import ToolEnv, default_tools
client = AsyncAnthropic()
async def main() -> None:
async for work, secret in client.beta.environments.work.poller(
environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"],
service_key=os.environ["ANTHROPIC_ENV_KEY"],
):
if work.data.type != "session":
continue
async with ToolEnv(workdir="/workspace") as env:
async with client.beta.sessions.events.tool_dispatcher(
session_id=work.data.id, work_id=work.id,
environment_id=work.environment_id,
session_token=secret.sessions_token,
tools=default_tools(env),
) as calls:
async for call in calls:
print(call.name)
anyio.run(main)
# AFTER:工厂即完整闭环——poll → skills → dispatch → heartbeat → force-stop → loop,
# environment key 是唯一凭据
import asyncio, os
from anthropic import AsyncAnthropic
async def main() -> None:
environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
async with AsyncAnthropic(auth_token=environment_key) as client:
await client.beta.environments.work.worker(
environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"],
environment_key=environment_key,
workdir="/workspace",
).run()
asyncio.run(main())
webhook 驱动的低层写法同样简化:poller 只产出 work,spawn_sandbox(...) 里原来的 sessions_token=secret.sessions_token 变成 environment_key=environment_key;TS 侧手工 poll → ack 循环丢掉了 decodeWorkSecret、!work.secret 守卫、逐调用 Authorization 头以及伪造的 x-environment-runner-version 轮询参数——因为 client 已用 environment key 鉴权,ack 不再需要逐调用头。数据字段也简化了:work.data 现在只是 Session | HealthCheck。
3h. 技能与自定义工具:ToolEnv → AgentToolContext;default_tools → beta_agent_toolset_20260401(Python)/ betaAgentToolset20260401(TS);逐工具工厂加 beta_/beta 前缀;import 迁到上文新模块路径。EnvironmentWorker 会自动下载会话技能,因此手工 AgentToolContext(client=…, session_id=…) 只在手工组合 runner 时才需要;技能端点(GET /v1/skills/{id}/versions/{version}/content)不变。
明确"不要动"的部分:anthropic-version/anthropic-beta: managed-agents-2026-04-01 前置头(SDK helpers 会自动加);client.beta.environments.create(...) 签名;--max-idle 空闲策略(end_turn 空闲后 60s);poller 的 reclaim_older_than_ms、block_ms、drain、auto_stop 语义;client.beta.webhooks.unwrap(...)。
十、把文档落到实现:六个变体如何映射同一契约
将五个变体的 README 与实现源码交叉对照,可以归纳出设计上的关键差异点:
- 派发入口:Docker 走 CLI
ant beta:worker run(容器 entrypoint);cf-container 同样走 CLI;cf-worker 走 TS 库SessionToolRunner(Durable Object 内);Modal/Daytona 走 PythonEnvironmentWorker.handle_item();Vercel 走 TSEnvironmentWorker.handleItem()。这些是 CLI 与 SDK 库两条等价路径的落地证据。 - 持久化策略:需要跨会话保留 Agent 工作树与技能时用按会话卷——Docker 的
cma-ws-<session_id>命名卷(on-work.sh)、Modal 的modal.Volume;无卷 API 的平台(Vercel)或伪文件系统(cf-worker)则退化为会话内临时状态。 - 空闲策略:全部遵循 SDK 默认——会话
session.status_idle且stop_reason: end_turn后 60s 退出(Dockerfile 用--max-idle 60s显式声明),任何其他事件(包括 Agent 被沙箱阻塞的requires_action空闲)都会重置计时器。 - 去重与容错:on-work.sh 用容器名探测实现幂等;Vercel webhook 用 KV 复用 +
seen/answered去重 + 坏项不 ack 等待回收;cf 用 DO 的sleepAfter续期防止容器被平台回收。 - 安全边界:所有变体在控制面与 runner 两侧都只用 environment key,组织级 API key 不进入执行环境;Docker 变体额外用
ANTHROPIC_AUTH_TOKEN兼容技能下载客户端,避免技能静默失败。
小结
自托管沙箱把 Managed Agents 的执行平面交还给你自己:要么用 ant beta:worker poll(长驻、按工作项通过 --on-work 派生隔离进程,无需暴露公网),要么用 webhook + 排空队列 + EnvironmentWorker/SessionToolRunner(事件驱动、按会话拉起云端沙箱)。无论选哪条路线,都遵循同一套环境变量契约、同一个 60 秒空闲策略,以及同一个以 environment key 为唯一凭据的鉴权模型。在动手部署前,建议依次通读 usage-guide.md(完整流程)、你选定变体的 README(平台部署细节),并对照 upgrade-guide.md 确认所用 SDK 版本的 API 形态。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00