首页
/ Claude Cookbooks 实战:为 Managed Agents 自建执行沙箱(Self-Hosted Sandboxes)的六种参考实现与全流程指南

Claude Cookbooks 实战:为 Managed Agents 自建执行沙箱(Self-Hosted Sandboxes)的六种参考实现与全流程指南

2026-09-07 12:48:05作者:邬祺芯Juliet

导读

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 把它归纳为三个步骤:

  1. 接收 session.status_run_started webhook,用 client.beta.webhooks.unwrap() 校验签名;
  2. 排空(drain)环境工作队列,让单次投递即可恢复此前遗漏的所有工作项(work item);
  3. 每个工作项启动一个按会话隔离的沙箱,在其中运行 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.0ant CLI v1.9.0;不过各变体内部的实际版本可能更新——例如 Docker 变体的 DockerfileREADME 已把 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_IDANTHROPIC_ENVIRONMENT_IDANTHROPIC_SESSION_IDANTHROPIC_ENVIRONMENT_KEYANTHROPIC_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_IDANTHROPIC_ENVIRONMENT_KEYANTHROPIC_WORK_IDANTHROPIC_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),因为 EnvironmentWorkerlogging.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 的实现:bashreadwriteeditglobgrep。你可以过滤或扩展它,再作为 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:按会话镜像。钉住 ant CLI(ARG ANT_VERSION=1.10.0),WORKDIR /workspaceENTRYPOINT ["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 时每次投递都新建沙箱——SessionToolRunnerseen/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.mdCMA_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.runnertool_dispatcher/toolDispatcherdefault_tools/defaultTools、按工作项下发的 secret/sessions_tokenant 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:workbeta:sessions…),自托管 worker 归到同一前缀下成为 beta:worker,其容器入口点子命令由 dispatch 改名为 runrun 的环境变量契约是 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_toolread_tool beta_bash_toolbeta_read_tool betaBashToolbetaReadTool
tool_dispatcher / toolDispatcher tool_runner toolRunner
decodeWorkSecret (移除) (移除)
service_key= / serviceKey: environment_key= environmentKey:

TS 还多一处:toolRunner / SessionToolRunnersessionId 改为位置参数——toolRunner({ sessionId, tools })toolRunner(sessionId, { tools });工具每次调用 run(args, context) 的上下文对象 BetaToolRunContexttoolUseBlock 字段改名为 toolUse(旧名保留为 deprecated 别名)。

3c. dispatcher 拆分——tool_dispatcherSessionToolRunner + EnvironmentWorker

旧的 tool_dispatcher 接收 work_id + environment_id + session_token独占整个工作项生命周期(心跳、对账、事件流、结果回传、退出 force-stop)。拆分后:

  • SessionToolRunnerclient.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 的分工备忘:

  • EnvironmentWorkerclient.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_keyenvironment_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 只产出 workspawn_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. 技能与自定义工具ToolEnvAgentToolContextdefault_toolsbeta_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_msblock_msdrainauto_stop 语义;client.beta.webhooks.unwrap(...)

十、把文档落到实现:六个变体如何映射同一契约

将五个变体的 README 与实现源码交叉对照,可以归纳出设计上的关键差异点:

  1. 派发入口:Docker 走 CLI ant beta:worker run(容器 entrypoint);cf-container 同样走 CLI;cf-worker 走 TS 库 SessionToolRunner(Durable Object 内);Modal/Daytona 走 Python EnvironmentWorker.handle_item();Vercel 走 TS EnvironmentWorker.handleItem()。这些是 CLI 与 SDK 库两条等价路径的落地证据。
  2. 持久化策略:需要跨会话保留 Agent 工作树与技能时用按会话卷——Docker 的 cma-ws-<session_id> 命名卷(on-work.sh)、Modal 的 modal.Volume;无卷 API 的平台(Vercel)或伪文件系统(cf-worker)则退化为会话内临时状态。
  3. 空闲策略:全部遵循 SDK 默认——会话 session.status_idlestop_reason: end_turn 后 60s 退出(Dockerfile 用 --max-idle 60s 显式声明),任何其他事件(包括 Agent 被沙箱阻塞的 requires_action 空闲)都会重置计时器。
  4. 去重与容错:on-work.sh 用容器名探测实现幂等;Vercel webhook 用 KV 复用 + seen/answered 去重 + 坏项不 ack 等待回收;cf 用 DO 的 sleepAfter 续期防止容器被平台回收。
  5. 安全边界:所有变体在控制面与 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 形态。

登录后查看全文
热门项目推荐
相关项目推荐