結論 — sandbox を Anthropic に預けない選択肢が初日に揃った
結論を先に書きます。v0.103.0 は changelog が 1 行 (Add support for self-hosted sandboxes in CMA with sandbox helpers) ですが、commit e5625b0 単体で追加された Python コードは 7,000 行を超え、Managed Agents (CMA、Anthropic がホストする agent 実行基盤) の sandbox を Anthropic に任せる前提が崩れた回です。release は 2026-05-19 07
src/anthropic/lib/environments/ と src/anthropic/resources/beta/environments/work.py がまとめて生えてきました。
中身は「agent の tool 実行を、自分のマシンか自分のコンテナの中で回す」ための helpers です。私は通勤前に release ページを 5 分眺めて、6 日前に書いた TypeScript SDK v0.96.0 の記録(cache diagnostics と Managed Agents Search Block の話)の隣に並ぶ更新ではないと気付きました。TS は API surface の観測性、Python はランタイム配置の選択肢、ベクトルが違います。
自分の手元でやりたかった理由 — managed sandbox の 3 つの引っ掛かり
要点を先に書きます。Anthropic の管理 sandbox は便利ですが、私の業務 3 件で踏み止まっていた理由は「ネットワーク境界」「データ持ち出し」「観測可能性」の 3 点でした。
1 つ目はネットワーク境界です。managed sandbox から社内 API を叩きたい時、IP allowlist の対象が Anthropic 側になり、社内 SRE と詰める列が長くなります。2 つ目はデータ持ち出しです。tool 実行で参照する顧客データを、3rd-party 環境に置きたくないという法務観点。3 つ目は観測可能性で、bash の stdout と終了 code を、自社の Datadog や OpenTelemetry に直に送りたい時、間に層が挟まると面倒でした。
これらは 5 月の社内設計レビューで「全部 self-host できれば解決するよね」と言われ、その通りでしたが、SDK 側がまだ揃っていなかったので止まっていました。今回の v0.103.0 は、その止まりを SDK レベルで外しに来た更新です。
v0.103.0 が出した 3 つの helpers
要点を先に書きます。helpers は 3 段の抽象で組まれていて、抽象度の高い順に EnvironmentWorker / SessionToolRunner / Poller です。helpers.md の追記 134 行から型と責務を引きます。
client.beta.environments.work.worker(...)—EnvironmentWorkerを返す factory。poll → 1 セッションごとに workdir を組み、agent の skills を download、tool 呼び出しに応答、lease を heartbeat、終了時に force-stop、までを 1 ループで回しますclient.beta.sessions.events.tool_runner(...)—SessionToolRunner。session の event stream に attach して、tool 1 件ごとにDispatchedToolCallを yield。中でEnvironmentWorkerが使っているのと同じ runner で、観測が要る時はこちらを直に使いますclient.beta.environments.work.poller(...)— control plane だけ。work item を claim して ack、yield する。EnvironmentWorkerを組まずに自分で workdir 配置を書きたい時用です
3 つとも async only で、anyio 上に乗っているので asyncio でも trio でも動きます。同期版を期待していた私の前提は、ここで上書きされました。FastAPI や background worker から呼ぶ前提なら問題ないですが、既存の同期 Django app に組み込むなら asgiref.sync.async_to_sync で挟むか、別プロセスで worker を回す形が現実的です。
run() 常駐 と handle_item() 単発 の 2 形態
要点を先に書きます。worker には 2 つの shape があります。1 つは run() で常駐 poll、もう 1 つは handle_item() で「外側が claim した 1 item を処理して exit」です。
# 常駐型: 1 プロセスで poll + dispatch
from anthropic import AsyncAnthropic
from anthropic.lib.tools.agent_toolset import beta_agent_toolset_20260401
client = AsyncAnthropic()
worker = client.beta.environments.work.worker(
environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"],
environment_key=os.environ["ANTHROPIC_ENVIRONMENT_KEY"],
workdir="/workspace",
tools=lambda env: [*beta_agent_toolset_20260401(env), my_tool],
)
await worker.run() # ループ。asyncio.wait_for で bound 可
# 単発型: ant worker poll --on-work などが env で渡す
# ANTHROPIC_WORK_ID / ANTHROPIC_SESSION_ID を読む
await client.beta.environments.work.worker(
workdir="/workspace",
tools=lambda env: [*beta_agent_toolset_20260401(env), my_tool],
).handle_item()
私は単発型 (handle_item()) の方が魅力的に見えました。理由は、1 item ごとに新しい container(or gVisor sandbox)を立てる方が isolation が単純化するからです。Kubernetes Job や Nomad batch を 1 item = 1 Pod に当てれば、終了後に Pod 丸ごと捨てられます。常駐型は同じプロセスで複数 session の tool を回すので、bash の /bin/bash 子プロセスを横断する状態を意識する必要があります(→ 次節)。handle_item() は ANTHROPIC_WORK_ID / ANTHROPIC_ENVIRONMENT_ID / ANTHROPIC_SESSION_ID / ANTHROPIC_ENVIRONMENT_KEY を env から読むので、orchestrator 側が env injection するだけで済みます。
bash tool の運用注意 — 「session 側 tool_runner で使え」が文書化された
要点を先に書きます。agent_toolset_20260401 の bash は persistent な /bin/bash 子プロセスを保持していて、その close hook を呼ぶのは SessionToolRunner か EnvironmentWorker だけです。
helpers.md の警告ブロックがこれを明示してきました。client.beta.messages.tool_runner(...)(Messages API 側の runner)に agent_toolset_20260401 を渡すと、bash の close hook が呼ばれず、orphan shell が 1 つ漏れます。同じ toolset を session 側と messages 側に使い回したい時、ここを混ぜると週次でゾンビプロセスが積み上がる、という、5 月の私が将来踏みそうなパターンです(救いは、helpers.md に「だから session 側で使え」と書いてあることで、警告を読み飛ばさない限り踏まないように工夫されています)。
回避は 2 つで、SessionToolRunner か EnvironmentWorker に閉じる、または messages 側に渡す時は [t for t in beta_agent_toolset_20260401(env) if t.name != "bash"] で bash を外す、です。後者は file tool(read / write / edit / glob / grep)だけ取り出す形で、これらは workdir を symlink-aware に limit してくれているので、bash と違って sandbox 無しでも安全だと diff には書いてあります。
TS SDK v0.96.0 (6 日前) との温度差
要点を先に書きます。5/13 の TS SDK 更新が「観測性」だったのに対し、5/19 の Python SDK 更新は「配置の自由度」です。
6 日前に書いた記事で扱った cache diagnostics と Search Result Block は、すでに走っている agent の挙動を見るための機能でした。今回は、agent が動く場所を決め直すための機能です。観測 → 配置、と続けてリリースが出てくると、Anthropic 側が「Managed Agents の self-host 化」を本気で進めていることが分かります。
私は当日に prototype 環境で examples/managed-agents-private-sandbox-worker.py を docker container 内に置いて、bash 経由で pwd と ls だけ叩く最小構成を試しました。/workspace を bind mount、ANTHROPIC_API_KEY と ANTHROPIC_ENVIRONMENT_ID と ANTHROPIC_ENVIRONMENT_KEY を env に注入。worker.run() を 60 秒で asyncio.wait_for で bound して return まで、3 回試して 3 回とも transcript が返りました。本番投入前に詰めるのは、container image の build 時間(cold start を許せるか)、workdir に download される skills のキャッシュ、Datadog への bash stdout ルーティング、の 3 点です。
書きながら、6 日前の TS の記事の最後で「次は配置側が来るかも」と書かなかったことを、少し悔いています。SDK 全体としては、観測 → 配置 → おそらく次は権限分離(環境ごとの API キー scope や、tool ごとの allowlist)という順で広がる、というのが今日の私の見立てです。次に release を確認するのは、helpers.md がもう一度大きく書き換わったタイミングだと思います。
Tags
よくある質問
- v0.103.0 で agent の sandbox はどこで動くようになりましたか?
- Anthropic がホストする managed sandbox を引き続き使うか、anthropic.lib.environments.EnvironmentWorker を自分のプロセスで回して self-host するか、を選べるようになりました。後者なら docker / gVisor / Kubernetes Job など、自分が isolation 境界を設計できる場所に配置できます。`client.beta.environments.work.worker(...)` がエントリポイントです。
- run() と handle_item() のどちらを使うべきですか?
- 1 プロセスで複数 session を回したいなら run() の常駐型、1 work item を独立 isolation で扱いたいなら handle_item() の単発型が向きます。Kubernetes Job などで 1 item = 1 Pod を立てる構成は handle_item() 一択で、ANTHROPIC_WORK_ID / ANTHROPIC_SESSION_ID 等が env から渡ってくるので worker(...) には workdir と tools だけ渡せば動きます。
- agent_toolset_20260401 を Messages tool_runner で使うとどうなりますか?
- bash の close hook が呼ばれず、persistent な /bin/bash 子プロセスが orphan として残ります。helpers.md がこれを明示的に警告しており、回避は (a) SessionToolRunner / EnvironmentWorker で使う、(b) bash を除いた toolset を渡す、の 2 通りです。file tools (read/write/edit/glob/grep) は workdir を symlink-aware に limit するので bash 抜きでも safe です。