# 自托管沙箱

在您自己的自托管沙箱环境中运行智能体会话。

---

默认情况下，托管智能体在 [Anthropic 托管的云容器](/docs/en/managed-agents/cloud-containers)内执行工具和代码。自托管沙箱将编排保留在 Anthropic 侧，但将工具执行转移到您控制的基础设施中，因此智能体的代码、文件系统和网络出口永远不会离开您的环境。

自托管沙箱尚未在 [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) 上可用。

## 与云环境的区别

| | 云环境 | 自托管沙箱 |
|---|---|---|
| 工具运行位置 | Anthropic 托管的容器 | 您的基础设施 |
| 网络访问 | Anthropic 的出口控制 | 您的网络策略 |
| 文件和 GitHub 仓库挂载 | Anthropic 管理 | 您管理 |
| 生命周期 | Anthropic 管理 | 您管理 |

当智能体需要操作不能离开您网络边界的数据、访问不可公开路由的内部服务，或在您组织自己的合规性和审计控制下运行时，自托管是一个很好的选择。

有关零数据保留和 HIPAA BAA 资格，请参阅 [API 和数据保留](/docs/en/manage-claude/api-and-data-retention#feature-eligibility)。

## 何时与 MCP 隧道结合使用

自托管控制*智能体代码在哪里执行*。[MCP 隧道](/docs/en/agents-and-tools/mcp-tunnels/overview)控制*Anthropic 如何访问您网络中的 MCP 服务器*。它们是独立的：在 Anthropic 云容器中运行的会话仍然可以通过隧道访问私有 MCP 服务器，自托管会话可以使用隧道或公共 MCP 服务器。当您希望执行和工具访问都保持在您的边界内时，请同时使用两者。

## 环境工作者

<Tip>
以下指南描述了如何使用任何通用沙箱平台构建工作者。还有针对 [Cloudflare](https://developers.cloudflare.com/sandbox/claude-managed-agents/)、[Daytona](https://www.daytona.io/docs/en/guides/claude/claude-managed-agents)、[Modal](https://github.com/modal-labs/claude-managed-agents-modal-sandbox) 和 [Vercel](https://vercel.com/kb/guide/run-claude-managed-agent-tools-with-vercel-sandbox) 的平台特定指南。
</Tip>

环境工作者是您在自己基础设施上运行的进程，它接收来自 Anthropic 的工具执行请求并在本地运行。`self_hosted` 环境是一个工作队列，将 Anthropic 的编排连接到您的工作者：当[会话](/docs/en/managed-agents/sessions)被分配到环境时，Anthropic 将其作为工作项入队。您的工作者从该队列中获取项，为每个会话生成执行上下文，下载[智能体技能](/docs/en/managed-agents/skills)，在本地运行工具调用，并将结果发回。

工作通过轮询环境的队列来获取：由持续轮询的**常驻工作者**，或在 `session.status_run_started` 时唤醒并开始轮询的 **webhook 触发处理器**。

CLI 和 SDK 都包含预构建的工作者来编排您的会话。`ant` CLI 仅支持常驻模式；SDK 支持常驻和 webhook 触发架构。

CLI 和 SDK 都是可配置的（参见[参考](#reference)），但如果您需要更多控制，可以直接使用[环境工作端点](/docs/en/api/beta/environments/work)并实现自己的工作者。

<Note>
SDK 辅助程序需要 `/bin/bash` 在该确切路径。TypeScript SDK 还需要 `unzip`、`tar` 和 Node.js 22 或更高版本。这些依赖项在固定路径解析，不尊重 `PATH` 覆盖。
</Note>

### 沙箱文件系统

- **`/workspace`**：工具执行和技能下载的默认工作目录。技能下载到 `/workspace/skills/<name>/`。如果您将 `--workdir` 从默认值更改，请更新智能体的系统提示，让 Claude 知道在哪里找到它们。
- **`/mnt/session/outputs`**：智能体将最终输出文件写入此路径。在容器中运行时，在此处挂载主机目录以检索它们。

<Tabs>
  <Tab title="常驻（ant CLI）">
    <Steps>
      <Step title="创建自托管环境">
        在[控制台](https://platform.claude.com/workspaces/default/environments)：**Workspace > Environments > New > Self-hosted**

        或通过 API：

        <CodeGroup>
          ```bash cURL
          curl -sS --fail-with-body https://api.anthropic.com/v1/environments \
            -H "x-api-key: $ANTHROPIC_API_KEY" \
            -H "anthropic-version: 2023-06-01" \
            -H "anthropic-beta: managed-agents-2026-04-01" \
            -H "content-type: application/json" \
            -d '{
              "name": "self-hosted",
              "config": {"type": "self_hosted"}
            }'
          ```

          ```bash CLI
          ant beta:environments create \
            --name self-hosted \
            --config '{"type": "self_hosted"}'
          ```

          ```python Python
          client = anthropic.Anthropic()

          environment = client.beta.environments.create(
              name="self-hosted", config={"type": "self_hosted"}
          )
          print(environment.id)
          ```

          ```typescript TypeScript
          const client = new Anthropic();

          const environment = await client.beta.environments.create({
            name: "self-hosted",
            config: { type: "self_hosted" }
          });
          console.log(environment.id);
          ```

          ```csharp C#
          using Anthropic.Models.Beta.Environments;

          var client = new AnthropicClient();

          var environment = await client.Beta.Environments.Create(
              new EnvironmentCreateParams
              {
                  Name = "self-hosted",
                  Config = new BetaSelfHostedConfigParams(),
              }
          );
          Console.WriteLine(environment.ID);
          ```

          ```go Go hidelines={1..10,-1}
          package main

          import (
          	"context"
          	"fmt"

          	"github.com/anthropics/anthropic-sdk-go"
          )

          func main() {
          	client := anthropic.NewClient()

          	environment, err := client.Beta.Environments.New(context.Background(), anthropic.BetaEnvironmentNewParams{
          		Name: "self-hosted",
          		Config: anthropic.BetaEnvironmentNewParamsConfigUnion{
          			OfSelfHosted: &anthropic.BetaSelfHostedConfigParams{},
          		},
          	})
          	if err != nil {
          		panic(err)
          	}
          	fmt.Println(environment.ID)
          }
          ```

          ```java Java
          import com.anthropic.models.beta.environments.BetaSelfHostedConfigParams;
          import com.anthropic.models.beta.environments.EnvironmentCreateParams;

          var client = AnthropicOkHttpClient.fromEnv();

          var environment = client.beta().environments().create(
              EnvironmentCreateParams.builder()
                  .name("self-hosted")
                  .config(BetaSelfHostedConfigParams.builder().build())
                  .build()
          );
          IO.println(environment.id());
          ```

          ```php PHP
          $client = new Anthropic\Client();

          $environment = $client->beta->environments->create(
              name: 'self-hosted',
              config: ['type' => 'self_hosted'],
          );
          echo $environment->id, PHP_EOL;
          ```

          ```ruby Ruby
          client = Anthropic::Client.new

          environment = client.beta.environments.create(
            name: "self-hosted",
            config: {type: :self_hosted}
          )
          puts environment.id
          ```
        </CodeGroup>

      </Step>

      <Step title="设置环境密钥">
        在控制台中，打开环境并单击**生成环境密钥**。然后在工作者主机上导出环境 ID 和密钥：

        ```bash
        export ANTHROPIC_ENVIRONMENT_KEY="sk-ant-oat01-..."
        export ANTHROPIC_ENVIRONMENT_ID="env_..."
        ```
      </Step>

      <Step title="安装 ant CLI">
        在工作者将运行的计算机上运行此命令。

        ```bash nocheck
        VERSION=1.9.1
        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
        ```
      </Step>

      <Step title="运行工作者">
        **进程内**

        `ant beta:worker poll` 获取分配给环境的会话，下载技能，在工作目录中执行工具调用，并将结果发回。

        <Note>
        技能可以包含智能体可能直接运行的可执行文件。CLI 会自动将下载的技能文件在沙箱中标记为可执行。如果您手动实现技能下载，您有责任设置可执行权限。
        </Note>

        ```bash
        ant beta:worker poll \
          --workdir "/workspace"
        ```

        工作者在 SIGTERM 或 SIGINT 时干净退出，在停止前排空进行中的工具调用。

        **每个会话一个容器**

        为了更强的隔离：每个会话的新文件系统、资源限制或网络控制。在各自的容器中运行每个会话。首先构建一个安装了 `ant` 并以 `ant beta:worker run` 作为入口点的镜像。当容器启动时，它从环境变量读取会话详细信息，处理该会话，然后退出：

        ```text
        FROM your-base-image
        ARG ANT_VERSION=1.9.1
        ARG TARGETARCH
        RUN ARCH=$([ "$TARGETARCH" = "arm64" ] && echo arm64 || echo amd64) && \
            curl -fsSL "https://github.com/anthropics/anthropic-cli/releases/download/v${ANT_VERSION}/ant_${VERSION}_linux_${ARCH}.tar.gz" \
              | tar -xz -C /usr/local/bin ant
        WORKDIR /workspace
        VOLUME /mnt/session/outputs
        ENTRYPOINT ["ant", "beta:worker", "run"]
        ```

        然后编写一个生成脚本，将会话详细信息转发到新容器中，并启动指向它的轮询器：

        ```bash
        #!/bin/bash
        # spawn.sh: 每个会话调用一次
        mkdir -p "/host/outputs/$ANTHROPIC_SESSION_ID"
        exec docker run --rm \
          -e ANTHROPIC_SESSION_ID -e ANTHROPIC_ENVIRONMENT_KEY \
          -e ANTHROPIC_WORK_ID -e ANTHROPIC_ENVIRONMENT_ID -e ANTHROPIC_BASE_URL \
          -v "/host/outputs/$ANTHROPIC_SESSION_ID":/mnt/session/outputs \
          your-image
        ```

        ```bash
        ant beta:worker poll \
          --on-work ./spawn.sh
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="常驻（SDK）">
    <Steps>
      <Step title="创建自托管环境">
        在[控制台](https://platform.claude.com/workspaces/default/environments)：**Workspace > Environments > New > Self-hosted**

        或通过 API：

        <CodeGroup>
          ```bash cURL
          curl -sS --fail-with-body https://api.anthropic.com/v1/environments \
            -H "x-api-key: $ANTHROPIC_API_KEY" \
            -H "anthropic-version: 2023-06-01" \
            -H "anthropic-beta: managed-agents-2026-04-01" \
            -H "content-type: application/json" \
            -d '{
              "name": "self-hosted",
              "config": {"type": "self_hosted"}
            }'
          ```

          ```bash CLI
          ant beta:environments create \
            --name self-hosted \
            --config '{"type": "self_hosted"}'
          ```

          ```python Python
          client = anthropic.Anthropic()

          environment = client.beta.environments.create(
              name="self-hosted", config={"type": "self_hosted"}
          )
          print(environment.id)
          ```

          ```typescript TypeScript
          const client = new Anthropic();

          const environment = await client.beta.environments.create({
            name: "self-hosted",
            config: { type: "self_hosted" }
          });
          console.log(environment.id);
          ```

          ```csharp C#
          using Anthropic.Models.Beta.Environments;

          var client = new AnthropicClient();

          var environment = await client.Beta.Environments.Create(
              new EnvironmentCreateParams
              {
                  Name = "self-hosted",
                  Config = new BetaSelfHostedConfigParams(),
              }
          );
          Console.WriteLine(environment.ID);
          ```

          ```go Go hidelines={1..10,-1}
          package main

          import (
          	"context"
          	"fmt"

          	"github.com/anthropics/anthropic-sdk-go"
          )

          func main() {
          	client := anthropic.NewClient()

          	environment, err := client.Beta.Environments.New(context.Background(), anthropic.BetaEnvironmentNewParams{
          		Name: "self-hosted",
          		Config: anthropic.BetaEnvironmentNewParamsConfigUnion{
          			OfSelfHosted: &anthropic.BetaSelfHostedConfigParams{},
          		},
          	})
          	if err != nil {
          		panic(err)
          	}
          	fmt.Println(environment.ID)
          }
          ```

          ```java Java
          import com.anthropic.models.beta.environments.BetaSelfHostedConfigParams;
          import com.anthropic.models.beta.environments.EnvironmentCreateParams;

          var client = AnthropicOkHttpClient.fromEnv();

          var environment = client.beta().environments().create(
              EnvironmentCreateParams.builder()
                  .name("self-hosted")
                  .config(BetaSelfHostedConfigParams.builder().build())
                  .build()
          );
          IO.println(environment.id());
          ```

          ```php PHP
          $client = new Anthropic\Client();

          $environment = $client->beta->environments->create(
              name: 'self-hosted',
              config: ['type' => 'self_hosted'],
          );
          echo $environment->id, PHP_EOL;
          ```

          ```ruby Ruby
          client = Anthropic::Client.new

          environment = client.beta.environments.create(
            name: "self-hosted",
            config: {type: :self_hosted}
          )
          puts environment.id
          ```
        </CodeGroup>

      </Step>

      <Step title="设置环境密钥">
        在控制台中，打开环境并单击**生成环境密钥**。然后在工作者主机上导出环境 ID 和密钥：

        ```bash
        export ANTHROPIC_ENVIRONMENT_KEY="sk-ant-oat01-..."
        export ANTHROPIC_ENVIRONMENT_ID="env_..."
        ```
      </Step>

      <Step title="运行工作者">
        `EnvironmentWorker` 获取分配给环境的会话，下载技能，在工作目录中执行工具调用，并将结果发回。使用您在步骤 2 中生成的环境密钥进行身份验证。

        <Note>
        技能可以包含智能体可能直接运行的可执行文件。SDK 会自动将下载的技能文件在沙箱中标记为可执行。如果您手动实现技能下载，您有责任设置可执行权限。
        </Note>

        <CodeGroup>
          ````python
          import asyncio
          import os
          from anthropic import AsyncAnthropic
          from anthropic.lib.environments import EnvironmentWorker


          async def main() -> None:
              environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
              environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
              async with AsyncAnthropic(auth_token=environment_key) as client:
                  await EnvironmentWorker(
                      client,
                      environment_id=environment_id,
                      environment_key=environment_key,
                      workdir="/workspace",
                  ).run()


          asyncio.run(main())
          ````

          ````typescript
          import Anthropic from "@anthropic-ai/sdk";
          import { EnvironmentWorker } from "@anthropic-ai/sdk/helpers/beta/environments";

          const environmentKey = process.env.ANTHROPIC_ENVIRONMENT_KEY!;
          const environmentId = process.env.ANTHROPIC_ENVIRONMENT_ID!;
          const client = new Anthropic({ authToken: environmentKey });
          const ctrl = new AbortController();
          process.once("SIGTERM", () => ctrl.abort());

          await new EnvironmentWorker({
            client,
            environmentId,
            environmentKey,
            workdir: "/workspace",
            signal: ctrl.signal
          }).run();
          ````

          ```csharp C#
          // EnvironmentWorker 目前在 C# SDK 中不可用。请参阅上面的常驻（ant CLI）选项卡。
          ```

          ````go
          package main

          import (
          	"context"
          	"log"
          	"os"
          	"os/signal"
          	"syscall"

          	"github.com/anthropics/anthropic-sdk-go"
          	"github.com/anthropics/anthropic-sdk-go/lib/environments"
          	"github.com/anthropics/anthropic-sdk-go/option"
          )

          func main() {
          	environmentKey := os.Getenv("ANTHROPIC_ENVIRONMENT_KEY")
          	environmentID := os.Getenv("ANTHROPIC_ENVIRONMENT_ID")

          	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
          	defer stop()

          	client := anthropic.NewClient(option.WithAuthToken(environmentKey))

          	worker := environments.NewEnvironmentWorker(client, environments.EnvironmentWorkerOptions{
          		EnvironmentID:  environmentID,
          		EnvironmentKey: environmentKey,
          		Workdir:        "/workspace",
          	})
          	if err := worker.Run(ctx); err != nil {
          		log.Fatalf("worker: %v", err)
          	}
          }

          ````

          ```java Java
          // EnvironmentWorker 目前在 Java SDK 中不可用。请参阅上面的常驻（ant CLI）选项卡。
          ```

          ```php PHP
          // EnvironmentWorker 目前在 PHP SDK 中不可用。请参阅上面的常驻（ant CLI）选项卡。
          ```

          ```ruby Ruby
          # EnvironmentWorker 目前在 Ruby SDK 中不可用。请参阅上面的常驻（ant CLI）选项卡。
          ```
        </CodeGroup>

      </Step>
    </Steps>
  </Tab>

  <Tab title="Webhook 触发（SDK）">
    <Steps>
      <Step title="订阅会话 webhook">
        在[控制台](https://platform.claude.com/settings/workspaces/default/webhooks)中，定义一个侦听 `session.status_run_started` 事件的 webhook 端点。有关更多详细信息，请参阅 [webhook 文档](/docs/en/managed-agents/webhooks)。
      </Step>

      <Step title="创建自托管环境">
        在[控制台](https://platform.claude.com/workspaces/default/environments)：**Workspace > Environments > New > Self-hosted**

        或通过 API：

        <CodeGroup>
          ```bash cURL
          curl -sS --fail-with-body https://api.anthropic.com/v1/environments \
            -H "x-api-key: $ANTHROPIC_API_KEY" \
            -H "anthropic-version: 2023-06-01" \
            -H "anthropic-beta: managed-agents-2026-04-01" \
            -H "content-type: application/json" \
            -d '{
              "name": "self-hosted",
              "config": {"type": "self_hosted"}
            }'
          ```

          ```bash CLI
          ant beta:environments create \
            --name self-hosted \
            --config '{"type": "self_hosted"}'
          ```

          ```python Python
          client = anthropic.Anthropic()

          environment = client.beta.environments.create(
              name="self-hosted", config={"type": "self_hosted"}
          )
          print(environment.id)
          ```

          ```typescript TypeScript
          const client = new Anthropic();

          const environment = await client.beta.environments.create({
            name: "self-hosted",
            config: { type: "self_hosted" }
          });
          console.log(environment.id);
          ```

          ```csharp C#
          using Anthropic.Models.Beta.Environments;

          var client = new AnthropicClient();

          var environment = await client.Beta.Environments.Create(
              new EnvironmentCreateParams
              {
                  Name = "self-hosted",
                  Config = new BetaSelfHostedConfigParams(),
              }
          );
          Console.WriteLine(environment.ID);
          ```

          ```go Go hidelines={1..10,-1}
          package main

          import (
          	"context"
          	"fmt"

          	"github.com/anthropics/anthropic-sdk-go"
          )

          func main() {
          	client := anthropic.NewClient()

          	environment, err := client.Beta.Environments.New(context.Background(), anthropic.BetaEnvironmentNewParams{
          		Name: "self-hosted",
          		Config: anthropic.BetaEnvironmentNewParamsConfigUnion{
          			OfSelfHosted: &anthropic.BetaSelfHostedConfigParams{},
          		},
          	})
          	if err != nil {
          		panic(err)
          	}
          	fmt.Println(environment.ID)
          }
          ```

          ```java Java
          import com.anthropic.models.beta.environments.BetaSelfHostedConfigParams;
          import com.anthropic.models.beta.environments.EnvironmentCreateParams;

          var client = AnthropicOkHttpClient.fromEnv();

          var environment = client.beta().environments().create(
              EnvironmentCreateParams.builder()
                  .name("self-hosted")
                  .config(BetaSelfHostedConfigParams.builder().build())
                  .build()
          );
          IO.println(environment.id());
          ```

          ```php PHP
          $client = new Anthropic\Client();

          $environment = $client->beta->environments->create(
              name: 'self-hosted',
              config: ['type' => 'self_hosted'],
          );
          echo $environment->id, PHP_EOL;
          ```

          ```ruby Ruby
          client = Anthropic::Client.new

          environment = client.beta.environments.create(
            name: "self-hosted",
            config: {type: :self_hosted}
          )
          puts environment.id
          ```
        </CodeGroup>

      </Step>

      <Step title="设置凭据">
        在控制台中，打开环境并单击**生成环境密钥**。在您的处理器主机上导出所有三个：

        ```bash
        export ANTHROPIC_ENVIRONMENT_KEY="sk-ant-oat01-..."
        export ANTHROPIC_ENVIRONMENT_ID="env_..."
        export ANTHROPIC_WEBHOOK_SIGNING_KEY="whsec_..."
        ```
      </Step>

      <Step title="实现 webhook 处理器">
        `EnvironmentWorker` 获取会话，下载技能，在工作目录中执行工具调用，发回结果，然后退出。在 `session.status_run_started` 触发时调用它。

        <Note>
        技能可以包含智能体可能直接运行的可执行文件。SDK 会自动将下载的技能文件在沙箱中标记为可执行。如果您手动实现技能下载，您有责任设置可执行权限。
        </Note>

        <CodeGroup>
          ````python
          import os
          import anthropic

          environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
          environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
          client = anthropic.AsyncAnthropic(
              auth_token=environment_key,
          )


          async def handle(raw: bytes, headers: dict[str, str]) -> dict:
              event = client.beta.webhooks.unwrap(raw.decode(), headers=headers)
              if event.data.type != "session.status_run_started":
                  return {"status": "ignored"}
              async for work in client.beta.environments.work.poller(
                  environment_id=environment_id,
                  environment_key=environment_key,
                  block_ms=None,
                  reclaim_older_than_ms=2000,
                  drain=True,
                  auto_stop=False,
              ):
                  await client.beta.environments.work.worker(workdir="/workspace").handle_item(
                      work_id=work.id,
                      environment_id=environment_id,
                      session_id=work.data.id,
                      environment_key=environment_key,
                  )
              return {"status": "ok"}
          ````

          ````typescript
          import Anthropic from "@anthropic-ai/sdk";

          const environmentKey = process.env.ANTHROPIC_ENVIRONMENT_KEY!;
          const environmentId = process.env.ANTHROPIC_ENVIRONMENT_ID!;
          const client = new Anthropic({
            authToken: environmentKey
          });

          export async function handle(req: Request): Promise<Response> {
            const body = await req.text();
            let event;
            try {
              event = client.beta.webhooks.unwrap(body, { headers: Object.fromEntries(req.headers) });
            } catch {
              return new Response("signature verification failed", { status: 401 });
            }
            if (event.data.type !== "session.status_run_started") {
              return Response.json({ status: "ignored" });
            }

            for await (const work of client.beta.environments.work.poller({
              environmentId,
              environmentKey,
              blockMs: null,
              reclaimOlderThanMs: 2000,
              drain: true,
              autoStop: false
            })) {
              await client.beta.environments.work.worker({ workdir: "/workspace" }).handleItem({
                workId: work.id,
                environmentId,
                sessionId: work.data.id,
                environmentKey
              });
            }
            return Response.json({ status: "ok" });
          }
          ````

          ```csharp C#
          // EnvironmentWorker 目前在 C# SDK 中不可用。
          // 要直接处理工作项，请参阅介绍中链接的环境工作端点。
          ```

          ````go
          package main

          import (
          	"context"
          	"encoding/json"
          	"io"
          	"log/slog"
          	"net/http"
          	"os"

          	"github.com/anthropics/anthropic-sdk-go"
          	"github.com/anthropics/anthropic-sdk-go/lib/environments"
          	"github.com/anthropics/anthropic-sdk-go/option"
          )

          var (
          	environmentKey = os.Getenv("ANTHROPIC_ENVIRONMENT_KEY")
          	environmentID  = os.Getenv("ANTHROPIC_ENVIRONMENT_ID")
          	client         = anthropic.NewClient(
          		option.WithAuthToken(environmentKey),
          		option.WithWebhookKey(os.Getenv("ANTHROPIC_WEBHOOK_SIGNING_KEY")),
          	)
          	worker = environments.NewEnvironmentWorker(client, environments.EnvironmentWorkerOptions{
          		Workdir: "/workspace",
          	})
          )

          func handle(w http.ResponseWriter, r *http.Request) {
          	body, err := io.ReadAll(r.Body)
          	if err != nil {
          		http.Error(w, "bad request", http.StatusBadRequest)
          		return
          	}
          	event, err := client.Beta.Webhooks.Unwrap(body, r.Header)
          	if err != nil {
          		http.Error(w, "signature verification failed", http.StatusUnauthorized)
          		return
          	}
          	if event.Data.Type != "session.status_run_started" {
          		json.NewEncoder(w).Encode(map[string]string{"status": "ignored"})
          		return
          	}

          	// Go SDK 不提供 RunOne 便利方法：使用 WorkPoller 获取项，
          	// 然后使用 HandleItem 运行它。
          	// 从 r.Context() 分离：会话可能比 webhook 交付超时更长。
          	ctx := context.Background()
          	poller := environments.NewWorkPoller(ctx, client, environments.WorkPollerOptions{
          		EnvironmentID:  environmentID,
          		EnvironmentKey: environmentKey,
          		Drain:          true,
          	})
          	defer poller.Close()
          	if poller.Next() {
          		item := poller.Current()
          		if err := worker.HandleItem(ctx, environments.HandleItemOptions{
          			WorkID:         item.ID,
          			EnvironmentID:  item.EnvironmentID,
          			SessionID:      item.Data.ID,
          			EnvironmentKey: environmentKey,
          		}); err != nil {
          			slog.Error("handle work item", "work_id", item.ID, "err", err)
          			http.Error(w, "internal error", http.StatusInternalServerError)
          			return
          		}
          	}
          	if err := poller.Err(); err != nil {
          		slog.Error("poll work queue", "err", err)
          		http.Error(w, "internal error", http.StatusInternalServerError)
          		return
          	}
          	json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
          }

          func main() {
          	http.HandleFunc("POST /webhook", handle)
          	if err := http.ListenAndServe(":8080", nil); err != nil {
          		slog.Error("http server", "err", err)
          		os.Exit(1)
          	}
          }

          ````

          ```java Java
          // EnvironmentWorker 目前在 Java SDK 中不可用。
          // 要直接处理工作项，请参阅介绍中链接的环境工作端点。
          ```

          ```php PHP
          // EnvironmentWorker 目前在 PHP SDK 中不可用。
          // 要直接处理工作项，请参阅介绍中链接的环境工作端点。
          ```

          ```ruby Ruby
          # EnvironmentWorker 目前在 Ruby SDK 中不可用。
          # 要直接处理工作项，请参阅介绍中链接的环境工作端点。
          ```
        </CodeGroup>
      </Step>
    </Steps>
  </Tab>
</Tabs>

## 启动会话

工作者运行后，创建目标为环境的会话。Anthropic 将其入队，您的工作者获取并执行它。

文件和 GitHub 资源挂载在您的容器镜像中处理，而不是由 Anthropic 处理。要加载具有会话特定文件的沙箱，您可以在创建会话时传递会话元数据。您的编排层可以读取该元数据，并在工作者开始执行之前挂载相关文件。

<CodeGroup>
  ```bash cURL nocheck
  curl -sS --fail-with-body https://api.anthropic.com/v1/sessions \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    -d @- <<EOF
  {
    "agent": "$AGENT_ID",
    "environment_id": "$ENVIRONMENT_ID",
    "metadata": {"input_file": "s3://my-bucket/data.csv"}
  }
  EOF
  ```

  ```bash CLI nocheck
  ant beta:sessions create \
    --agent "$AGENT_ID" \
    --environment-id "$ENVIRONMENT_ID" \
    --metadata '{"input_file": "s3://my-bucket/data.csv"}'
  ```

  ````python
  session = client.beta.sessions.create(
      agent=agent.id,
      environment_id=environment.id,
      metadata={"input_file": "s3://my-bucket/data.csv"},
  )
  ````

  ````typescript
  const session = await client.beta.sessions.create({
    agent: agent.id,
    environment_id: environment.id,
    metadata: { input_file: "s3://my-bucket/data.csv" }
  });
  ````

  ````csharp
  var session = await client.Beta.Sessions.Create(new()
  {
      Agent = agent.ID,
      EnvironmentID = environment.ID,
      Metadata = new Dictionary<string, string> { ["input_file"] = "s3://my-bucket/data.csv" },
  });
  ````

  ````go
  session, err := client.Beta.Sessions.New(ctx, anthropic.BetaSessionNewParams{
  	Agent:         anthropic.BetaSessionNewParamsAgentUnion{OfString: anthropic.String(agent.ID)},
  	EnvironmentID: environment.ID,
  	Metadata: map[string]string{
  		"input_file": "s3://my-bucket/data.csv",
  	},
  })
  if err != nil {
  	panic(err)
  }
  ````

  ````java
  var session = client.beta().sessions().create(SessionCreateParams.builder()
      .agent(agent.id())
      .environmentId(environment.id())
      .metadata(SessionCreateParams.Metadata.builder()
          .putAdditionalProperty("input_file", JsonValue.from("s3://my-bucket/data.csv"))
          .build())
      .build());
  ````

  ````php
  $session = $client->beta->sessions->create(
      agent: $agent->id,
      environmentID: $environment->id,
      metadata: ['input_file' => 's3://my-bucket/data.csv'],
  );
  ````

  ````ruby
  session = client.beta.sessions.create(
    agent: agent.id,
    environment_id: environment.id,
    metadata: {input_file: "s3://my-bucket/data.csv"}
  )
  ````

</CodeGroup>

<Note>
[记忆](/docs/en/managed-agents/memory)尚不支持自托管沙箱。
</Note>

## 参考

<Tabs>
  <Tab title="ant CLI">
    | 标志 | 描述 |
    |------|------|
    | `--environment-id` | 轮询工作的环境。也从 `ANTHROPIC_ENVIRONMENT_ID` 读取。 |
    | `--environment-key` | 使用此环境对工作者进行身份验证。也从 `ANTHROPIC_ENVIRONMENT_KEY` 读取。 |
    | `--workdir` | 下载技能和工具读写文件的目录。默认为 `/workspace`。 |
    | `--on-work` | 为每个获取的工作项调用的脚本，而不是在进程内运行工具。接收会话详细信息作为环境变量。 |
    | `--unrestricted-paths` | 允许工具调用访问 `--workdir` 之外的路径。 |
    | `--max-idle` | 在 `end_turn` 空闲后等待多长时间再关闭。默认为 `60s`。 |
    | `--log-format` | 日志输出格式。使用 `json` 进行结构化日志摄取。默认为 `text`。 |
  </Tab>

  <Tab title="SDK">
    SDK 在不同级别提供三个辅助程序：

    - **`EnvironmentWorker`**：开箱即用的工作者。端到端处理轮询、设置和执行。
      - `.run()`：无限期运行，随会话到达而获取。在 SIGTERM 时干净退出。
      - `.handle_item()`：获取一个待处理会话，处理它，然后退出。
    - **`work.poller()`**：代表您轮询工作队列并向您提供每个获取的会话。当您想决定每个会话发生什么时使用此选项，例如启动容器而不是在进程内运行工具。
      - `drain`：队列为空时是否停止轮询，而不是等待新工作。
      - `block_ms`：在返回之前等待工作到达的时间（毫秒）。必须在 1 到 999 之间。设置为 `None` 进行非阻塞检查。
      - `reclaim_older_than_ms`：重新获取已租给停止响应的工作者的工作项。
      - `auto_stop`：迭代器退出后是否在工作项上发布停止信号。
    - **`tool_runner()`**：为单个会话运行工具调用。当您已获取工作并且只需要执行层时使用。

    `EnvironmentWorker` 覆盖大多数用例。当您想启动自己的每会话进程时，直接使用工作轮询器，例如为每个获取的会话启动容器：

    <CodeGroup>
      ```bash cURL nocheck
      # 轮询端点需要环境密钥（Bearer 身份验证），而不是您的
      # API 密钥。使用下面的 SDK 选项之一。
      ```

      ```bash CLI nocheck
      # 轮询端点需要环境密钥（Bearer 身份验证），而不是您的
      # API 密钥。使用下面的 SDK 选项之一。
      ```

      ```python Python nocheck
      import asyncio
      import os

      from anthropic import AsyncAnthropic
      from anthropic.types.beta.environments import BetaSelfHostedWork


      async def launch_container(work: BetaSelfHostedWork) -> None:
          # 替换为您自己的每会话沙箱启动器。将
          # ANTHROPIC_ENVIRONMENT_KEY 传入启动的容器，永远不要
          # 传入您的 API 密钥。
          print(f"claimed session {work.data.id}")


      async def main() -> None:
          environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
          environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
          async with AsyncAnthropic(auth_token=environment_key) as client:
              async for work in client.beta.environments.work.poller(
                  environment_id=environment_id,
                  environment_key=environment_key,
                  auto_stop=False,  # 启动的容器拥有停止调用
              ):
                  await launch_container(work)


      asyncio.run(main())
      ```

      ```typescript TypeScript nocheck
      import Anthropic from "@anthropic-ai/sdk";
      import { WorkPoller } from "@anthropic-ai/sdk/helpers/beta/environments";
      import type { BetaSelfHostedWork } from "@anthropic-ai/sdk/resources/beta/environments";

      const environmentKey = process.env.ANTHROPIC_ENVIRONMENT_KEY!;
      const environmentId = process.env.ANTHROPIC_ENVIRONMENT_ID!;
      const client = new Anthropic({ authToken: environmentKey });

      async function launchContainer(work: BetaSelfHostedWork): Promise<void> {
        // 替换为您自己的每会话沙箱启动器。将
        // ANTHROPIC_ENVIRONMENT_KEY 传入启动的容器，永远不要
        // 传入您的 API 密钥。
        console.log(`claimed session ${work.data.id}`);
      }

      const poller = new WorkPoller({
        client,
        environmentId,
        environmentKey,
        autoStop: false // 启动的容器拥有停止调用
      });

      for await (const work of poller) {
        await launchContainer(work);
      }
      ```

      ```csharp C# nocheck
      // 工作轮询目前在 C# SDK 中不可用。
      ```

      ```go Go nocheck
      package main

      import (
      	"context"
      	"fmt"
      	"log"
      	"os"

      	"github.com/anthropics/anthropic-sdk-go"
      	"github.com/anthropics/anthropic-sdk-go/lib/environments"
      	"github.com/anthropics/anthropic-sdk-go/option"
      )

      func launchContainer(work *anthropic.BetaSelfHostedWork) {
      	// 替换为您自己的每会话沙箱启动器。Go 轮询器
      	// 在此函数返回时调用 work.Stop（它没有自动停止
      	// 选择退出），因此在此处阻塞直到会话完成，而不是
      	// 像 Python 和 TypeScript 选项卡那样分离。
      	fmt.Printf("claimed session %s\n", work.Data.ID)
      }

      func main() {
      	environmentID := os.Getenv("ANTHROPIC_ENVIRONMENT_ID")
      	environmentKey := os.Getenv("ANTHROPIC_ENVIRONMENT_KEY")

      	client := anthropic.NewClient(option.WithAuthToken(environmentKey))

      	ctx := context.Background()

      	poller := environments.NewWorkPoller(ctx, client, environments.WorkPollerOptions{
      		EnvironmentID:  environmentID,
      		EnvironmentKey: environmentKey,
      	})
      	defer poller.Close()

      	for work, err := range poller.All() {
      		if err != nil {
      			log.Fatal(err)
      		}
      		launchContainer(work)
      	}
      }
      ```

      ```java Java nocheck
      // 工作轮询目前在 Java SDK 中不可用。
      ```

      ```php PHP nocheck
      // 工作轮询目前在 PHP SDK 中不可用。
      ```

      ```ruby Ruby nocheck
      # 工作轮询目前在 Ruby SDK 中不可用。
      ```
    </CodeGroup>

    **`AgentToolContext`** 是工具调用的执行上下文，定义工作目录、路径策略，并在作为上下文管理器进入时可选地下载会话的技能。**`beta_agent_toolset_20260401(env)`** 接收 `AgentToolContext` 并返回标准工具实现（`bash`、`read`、`write`、`edit`、`glob`、`grep`）。

    **使用 `EnvironmentWorker`：** 两者都自动管理。传递 `tools` 工厂来自定义工具列表：

    ```python Python
    EnvironmentWorker(client, ..., tools=lambda env: [beta_bash_tool(env), my_custom_tool])
    ```

    **使用 `work.poller()` / `tool_runner()`：** 在运行工具之前自己设置 `AgentToolContext` 和 `beta_agent_toolset_20260401(env)`：

    <CodeGroup>
      ````python
      from anthropic.lib.tools.agent_toolset import (
          AgentToolContext,
          beta_agent_toolset_20260401,
      )

      async with AgentToolContext(
          workdir="/workspace", client=client, session_id=work.data.id
      ) as env:
          # 技能下载到 /workspace/skills/<name>/
          tools = beta_agent_toolset_20260401(env)
      ````

      ````typescript
      import {
        setupSkills,
        betaAgentToolset20260401
      } from "@anthropic-ai/sdk/tools/agent-toolset/node";

      const ctx = { workdir: "/workspace", client, sessionId: work.data.id };
      await setupSkills(ctx);
      const tools = betaAgentToolset20260401(ctx);
      ````

      ```csharp C#
      // AgentToolContext 目前在 C# SDK 中不可用。
      ```

      ````go
      env := &agenttoolset.AgentToolContext{Workdir: "/workspace"}
      if err := env.SetupSkills(ctx, client, work.Data.ID); err != nil {
      	panic(err)
      }
      // 技能下载到 /workspace/skills/<name>/
      tools := agenttoolset.BetaAgentToolset20260401(env)
      ````

      ```java Java
      // AgentToolContext 目前在 Java SDK 中不可用。
      ```

      ```php PHP
      // AgentToolContext 目前在 PHP SDK 中不可用。
      ```

      ```ruby Ruby
      # AgentToolContext 目前在 Ruby SDK 中不可用。
      ```
    </CodeGroup>

  </Tab>
</Tabs>

## 监控和运维

这些调用从您的监控或运维工具运行，使用您的 Claude API 密钥进行身份验证，以观察和管理工作者集群。获取和保活循环在工作者辅助程序内部处理，因此您不需要直接调用这些端点。

<Warning>
  这些端点使用您的组织 API 密钥进行身份验证，而不是环境密钥。从工作者主机外部调用它们。在工作者主机上设置 `ANTHROPIC_API_KEY` 会将组织范围的凭据暴露给智能体工具调用。
</Warning>

### 读取队列深度

`work.stats` 返回环境的队列状态：

- `depth` 是等待获取的项数。根据此值扩展您的工作者集群或对积压工作进行警报。
- `pending` 是工作者已获取并当前正在处理的项数。
- `oldest_queued_at` 是队列中最旧项的时间戳，如果队列为空则为 `null`。
- `workers_polling` 是在过去 30 秒内轮询的工作者数量。将此用于存活性警报。

<CodeGroup>
  ```bash cURL nocheck
  curl -sS "https://api.anthropic.com/v1/environments/$ANTHROPIC_ENVIRONMENT_ID/work/stats" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-beta: managed-agents-2026-04-01" \
    -H "anthropic-version: 2023-06-01"
  ```

  ```bash CLI nocheck
  ant beta:environments:work stats --environment-id "$ANTHROPIC_ENVIRONMENT_ID"
  ```

  ```python Python nocheck
  import os

  import anthropic

  client = anthropic.Anthropic()

  stats = client.beta.environments.work.stats(os.environ["ANTHROPIC_ENVIRONMENT_ID"])
  print(f"depth={stats.depth} pending={stats.pending}")
  ```

  ```typescript TypeScript nocheck
  import Anthropic from "@anthropic-ai/sdk";

  const client = new Anthropic();

  const stats = await client.beta.environments.work.stats(process.env.ANTHROPIC_ENVIRONMENT_ID!);

  console.log(`depth=${stats.depth} pending=${stats.pending}`);
  ```

  ```csharp C# nocheck
  using Anthropic;

  var client = new AnthropicClient();

  var environmentId = Environment.GetEnvironmentVariable("ANTHROPIC_ENVIRONMENT_ID")!;

  var stats = await client.Beta.Environments.Work.Stats(environmentId);

  Console.WriteLine($"depth={stats.Depth} pending={stats.Pending}");
  ```

  ```go Go nocheck
  package main

  import (
  	"context"
  	"fmt"
  	"os"

  	"github.com/anthropics/anthropic-sdk-go"
  )

  func main() {
  	client := anthropic.NewClient()
  	environmentID := os.Getenv("ANTHROPIC_ENVIRONMENT_ID")

  	stats, err := client.Beta.Environments.Work.Stats(
  		context.Background(),
  		environmentID,
  		anthropic.BetaEnvironmentWorkStatsParams{},
  	)
  	if err != nil {
  		panic(err)
  	}

  	fmt.Printf("depth=%d pending=%d\n", stats.Depth, stats.Pending)
  }
  ```

  ```java Java nocheck
  import com.anthropic.client.AnthropicClient;
  import com.anthropic.client.okhttp.AnthropicOkHttpClient;
  import com.anthropic.models.beta.environments.work.BetaSelfHostedWorkQueueStats;

  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      BetaSelfHostedWorkQueueStats stats = client.beta()
          .environments()
          .work()
          .stats(System.getenv("ANTHROPIC_ENVIRONMENT_ID"));

      IO.println("depth=" + stats.depth() + " pending=" + stats.pending());
  }
  ```

  ```php PHP nocheck
  <?php

  use Anthropic\Client;

  $client = new Client();

  $stats = $client->beta->environments->work->stats(getenv('ANTHROPIC_ENVIRONMENT_ID'));

  printf("depth=%d pending=%d\n", $stats->depth, $stats->pending);
  ```

  ```ruby Ruby nocheck
  require "anthropic"

  client = Anthropic::Client.new

  stats = client.beta.environments.work.stats(ENV.fetch("ANTHROPIC_ENVIRONMENT_ID"))

  puts "depth=#{stats.depth} pending=#{stats.pending}"
  ```
</CodeGroup>

```text
{
  "type": "work_queue_stats",
  "depth": 0,
  "pending": 0,
  "oldest_queued_at": null,
  "workers_polling": 0
}
```

### 优雅停止会话

使用 `work.stop` 请求处理特定会话的工作者干净地关闭它。工作者完成任何进行中的工具调用，发布最终状态，并释放会话。在请求体中传递 `force: true` 以立即中断，而不是等待当前工具调用完成。

因为这些调用从您的运维工具而不是工作者主机运行，`ANTHROPIC_WORK_ID` 不会自动设置。在运行以下示例之前，将其设置为目标工作项的 ID。

<CodeGroup>
  ```bash cURL nocheck
  curl -sS "https://api.anthropic.com/v1/environments/$ANTHROPIC_ENVIRONMENT_ID/work/$ANTHROPIC_WORK_ID/stop" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-beta: managed-agents-2026-04-01" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{}'
  ```

  ```bash CLI nocheck
  ant beta:environments:work stop \
    --environment-id "$ANTHROPIC_ENVIRONMENT_ID" \
    --work-id "$ANTHROPIC_WORK_ID"
  ```

  ```python Python nocheck
  import os

  import anthropic

  client = anthropic.Anthropic()

  work = client.beta.environments.work.stop(
      os.environ["ANTHROPIC_WORK_ID"],
      environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"],
  )
  print(work.state)
  ```

  ```typescript TypeScript nocheck
  import Anthropic from "@anthropic-ai/sdk";

  const client = new Anthropic();

  const work = await client.beta.environments.work.stop(process.env.ANTHROPIC_WORK_ID!, {
    environment_id: process.env.ANTHROPIC_ENVIRONMENT_ID!
  });

  console.log(work.state);
  ```

  ```csharp C# nocheck
  using Anthropic;

  var client = new AnthropicClient();

  var work = await client.Beta.Environments.Work.Stop(
      Environment.GetEnvironmentVariable("ANTHROPIC_WORK_ID")!,
      new()
      {
          EnvironmentID = Environment.GetEnvironmentVariable("ANTHROPIC_ENVIRONMENT_ID")!
      }
  );

  Console.WriteLine(work.State);
  ```

  ```go Go nocheck
  package main

  import (
  	"context"
  	"fmt"
  	"os"

  	"github.com/anthropics/anthropic-sdk-go"
  )

  func main() {
  	client := anthropic.NewClient()

  	work, err := client.Beta.Environments.Work.Stop(
  		context.Background(),
  		os.Getenv("ANTHROPIC_WORK_ID"),
  		anthropic.BetaEnvironmentWorkStopParams{
  			EnvironmentID: os.Getenv("ANTHROPIC_ENVIRONMENT_ID"),
  		},
  	)
  	if err != nil {
  		panic(err)
  	}
  	fmt.Println(work.State)
  }
  ```

  ```java Java nocheck
  import com.anthropic.client.AnthropicClient;
  import com.anthropic.client.okhttp.AnthropicOkHttpClient;
  import com.anthropic.models.beta.environments.work.BetaSelfHostedWork;
  import com.anthropic.models.beta.environments.work.BetaSelfHostedWorkStopRequest;
  import com.anthropic.models.beta.environments.work.WorkStopParams;

  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      BetaSelfHostedWork work = client.beta().environments().work().stop(
          WorkStopParams.builder()
              .environmentId(System.getenv("ANTHROPIC_ENVIRONMENT_ID"))
              .workId(System.getenv("ANTHROPIC_WORK_ID"))
              .betaSelfHostedWorkStopRequest(BetaSelfHostedWorkStopRequest.builder().build())
              .build()
      );

      IO.println(work.state());
  }
  ```

  ```php PHP nocheck
  <?php

  use Anthropic\Client;

  $client = new Client();

  $work = $client->beta->environments->work->stop(
      getenv('ANTHROPIC_WORK_ID'),
      environmentID: getenv('ANTHROPIC_ENVIRONMENT_ID'),
  );

  echo $work->state . "\n";
  ```

  ```ruby Ruby nocheck
  require "anthropic"

  client = Anthropic::Client.new

  work = client.beta.environments.work.stop(
    ENV.fetch("ANTHROPIC_WORK_ID"),
    environment_id: ENV.fetch("ANTHROPIC_ENVIRONMENT_ID")
  )

  puts work.state
  ```
</CodeGroup>

## 后续步骤

<CardGroup cols={2}>
  <Card title="托管智能体会话" icon="settings" href="/docs/en/managed-agents/sessions">
    创建会话以运行您的智能体并开始执行任务。
  </Card>
  <Card title="MCP 隧道概览" icon="bolt" href="/docs/en/agents-and-tools/mcp-tunnels/overview">
    从任何执行环境访问您私有网络内的 MCP 服务器。
  </Card>
  <Card title="安全模型" icon="lock" href="/docs/en/managed-agents/self-hosted-sandboxes-security">
    了解自托管沙箱环境的共享责任模型。
  </Card>
</CardGroup>
