---
title: Implementar workers autoalojados
url: https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-workers
description: "Elige cómo los workers de sandbox autoalojados reclaman trabajo y dónde se ejecutan las sesiones: siempre activos o activados por webhook, en un solo proceso o en un sandbox por sesión."
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

El [inicio rápido](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes#quickstart) ejecuta un worker de la CLI `ant` que sondea continuamente y ejecuta cada sesión en un solo proceso. Esta página cubre las otras formas de ejecutar un worker y cómo elegir entre ellas.

## Elige un patrón de implementación

Al implementar workers, debes tomar dos decisiones: cómo el worker reclama trabajo y dónde se ejecuta cada sesión.

**Cómo el worker reclama trabajo:**

* **Siempre activo:** Un proceso de larga duración sondea la cola continuamente y solo necesita HTTPS saliente. Esta es la configuración más sencilla.
* **Activado por webhook:** Un controlador se activa con `session.status_run_started` y comienza a sondear. Esto evita un sondeador inactivo, pero requiere un endpoint de [webhook](https://platform.claude.com/docs/es/managed-agents/webhooks) al que Anthropic pueda acceder.

**Dónde se ejecuta cada sesión:**

* **En proceso:** El worker que reclama una sesión también ejecuta sus llamadas a herramientas, en un único directorio de trabajo compartido.
* **Sandbox por sesión:** Un sondeador lanza un sandbox nuevo para cada sesión reclamada. Elige esta opción para un aislamiento más fuerte: un sistema de archivos nuevo, límites de recursos o controles de red por sesión.

Los workers de la CLI y del SDK admiten diferentes combinaciones:

| Capacidad                                                                                                            | CLI `ant`                                                 | SDK (Python, TypeScript, Go)        |
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ----------------------------------- |
| Sondeo siempre activo                                                                                                | Sí                                                        | Sí                                  |
| Activado por webhook                                                                                                 | No                                                        | Sí                                  |
| Sandbox por sesión                                                                                                   | Sí                                                        | Sí                                  |
| [Almacenes de memoria](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-memory)              | Sí, con la configuración de sincronización predeterminada | Sí, con sincronización configurable |
| [Herramientas personalizadas](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-custom-tools) | No                                                        | Sí                                  |

Consulta la [referencia de workers autoalojados](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-reference) para ver cada flag de la CLI y cada opción del SDK. Para tener más control, llama directamente a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work) e implementa tu propio worker.

## Ejecuta un worker siempre activo

Inicia un worker que sondee la cola continuamente. Se autentica con la [clave del entorno](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes#run-your-first-session) del inicio rápido:

<CodeGroup exclude="shell:cURL, csharp, java, php, ruby">
  ```bash CLI
  ant beta:worker poll --workdir /workspace
  ```

  ```python Python
  import asyncio
  import contextlib
  import os
  import signal
  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:
          worker = EnvironmentWorker(
              client,
              environment_id=environment_id,
              environment_key=environment_key,
              workdir="/workspace",
          )
          task = asyncio.create_task(worker.run())
          # Cancelar la tarea, en lugar de matar el proceso, permite al worker detener su
          # elemento de trabajo en curso y subir los archivos de memoria modificados antes de salir.
          loop = asyncio.get_running_loop()
          for signum in (signal.SIGINT, signal.SIGTERM):
              loop.add_signal_handler(signum, task.cancel)
          with contextlib.suppress(asyncio.CancelledError):
              await task


  asyncio.run(main())
  ```

  ```typescript 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 controller = new AbortController();
  // Abortar con cualquiera de las señales permite al worker subir los archivos de memoria modificados y eliminar sus
  // directorios de almacenamiento antes de que el proceso termine.
  process.once("SIGINT", () => controller.abort());
  process.once("SIGTERM", () => controller.abort());

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

  <CodeGroupItem language="C#">
    El SDK de C# no tiene `EnvironmentWorker`. En su lugar, ejecuta el worker de la CLI `ant`: `ant beta:worker poll --workdir /workspace`.
  </CodeGroupItem>

  ```go 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)
  	}
  }
  ```

  <CodeGroupItem language="Java">
    El SDK de Java no tiene `EnvironmentWorker`. En su lugar, ejecuta el worker de la CLI `ant`: `ant beta:worker poll --workdir /workspace`.
  </CodeGroupItem>

  <CodeGroupItem language="PHP">
    El SDK de PHP no tiene `EnvironmentWorker`. En su lugar, ejecuta el worker de la CLI `ant`: `ant beta:worker poll --workdir /workspace`.
  </CodeGroupItem>

  <CodeGroupItem language="Ruby">
    El SDK de Ruby no tiene `EnvironmentWorker`. En su lugar, ejecuta el worker de la CLI `ant`: `ant beta:worker poll --workdir /workspace`.
  </CodeGroupItem>
</CodeGroup>

## Activa workers desde webhooks

<Steps>
  <Step title="Suscríbete a los webhooks de sesión">
    En la [Console](https://platform.claude.com/settings/workspaces/default/webhooks), define un endpoint de webhook que escuche eventos `session.status_run_started`. Consulta [Webhooks](https://platform.claude.com/docs/es/managed-agents/webhooks) para obtener más detalles.
  </Step>

  <Step title="Exporta la clave de firma del webhook">
    Junto con el ID y la clave del entorno del [inicio rápido](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes#run-your-first-session), exporta la clave de firma del webhook en el host de tu controlador. El controlador la usa para verificar las cargas útiles entrantes.

    ```bash
    export ANTHROPIC_WEBHOOK_SIGNING_KEY="whsec_..."
    ```
  </Step>

  <Step title="Implementa el controlador del webhook">
    Invoca el worker cuando se dispare `session.status_run_started`. El controlador vacía la cola y entrega cada elemento de trabajo reclamado a `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`), que descarga skills, ejecuta llamadas a herramientas, publica los resultados y retorna.

    <CodeGroup exclude="shell, csharp, java, php, ruby">
      <CodeGroupItem>
        Para verificar las firmas de webhook, instala el extra de webhooks: `pip install "anthropic[webhooks]"`.

        ```python Python
        import asyncio
        import os
        import anthropic
        import standardwebhooks  # installed by the anthropic[webhooks] extra

        environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
        environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
        client = anthropic.AsyncAnthropic(
            auth_token=environment_key,
        )
        # Cancelado por shutdown() para que un elemento de trabajo en curso pueda subir archivos de memoria modificados y
        # eliminar sus directorios de almacén antes de que el proceso termine.
        inflight: set[asyncio.Task[None]] = set()


        # Espera esto desde el hook de apagado del host, como un apagado de lifespan ASGI (el código después de
        # `yield` en un lifespan de FastAPI), que uvicorn ejecuta en SIGTERM. uvicorn deja que las solicitudes abiertas
        # terminen antes de ejecutar ese hook, así que define --timeout-graceful-shutdown para limitar la espera.
        async def shutdown() -> None:
            for task in inflight:
                task.cancel()
            await asyncio.gather(*inflight, return_exceptions=True)


        async def handle(raw: bytes, headers: dict[str, str]) -> tuple[dict[str, str], int]:
            try:
                event = client.beta.webhooks.unwrap(raw.decode(), headers=headers)
            except standardwebhooks.WebhookVerificationError:
                return {"error": "signature verification failed"}, 401
            if event.data.type != "session.status_run_started":
                return {"status": "ignored"}, 200
            task = asyncio.create_task(run_queued_work())
            inflight.add(task)
            task.add_done_callback(inflight.discard)
            try:
                # Protegido (shield): una entrega descartada o con timeout no debe cancelar el elemento; shutdown() sí lo hace.
                await asyncio.shield(task)
            except asyncio.CancelledError:
                return {"status": "shutting down"}, 503
            return {"status": "ok"}, 200


        async def run_queued_work() -> None:
            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,
                    # El secreto por sesión es lo que permite al worker montar los almacenes de memoria de la sesión.
                    work_secret=work.secret,
                )
        ```
      </CodeGroupItem>

      ```typescript 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
      });
      // Llama a shutdown.abort() desde el manejador SIGTERM/SIGINT del host, junto con cerrar el servidor,
      // luego espera las llamadas handle() en curso antes de salir: el abort permite que un elemento de trabajo en ejecución
      // suba primero los archivos de memoria modificados y elimine sus directorios de almacén.
      export const shutdown = new AbortController();

      export async function handle(req: Request): Promise<Response> {
        // Nunca confirmes una entrega cuyo trabajo no se ejecutará aquí; un 503 hace que el remitente reintente.
        if (shutdown.signal.aborted) {
          return Response.json({ status: "shutting down" }, { status: 503 });
        }
        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,
          signal: shutdown.signal
        })) {
          await client.beta.environments.work.worker({ workdir: "/workspace" }).handleItem({
            workId: work.id,
            environmentId,
            sessionId: work.data.id,
            environmentKey,
            // El secreto por sesión es lo que permite al worker montar los almacenes de memoria de la sesión.
            workSecret: work.secret ?? undefined,
            signal: shutdown.signal
          });
        }
        // El poller y handleItem retornan silenciosamente al abortar, así que un drenaje interrumpido termina aquí.
        if (shutdown.signal.aborted) {
          return Response.json({ status: "shutting down" }, { status: 503 });
        }
        return Response.json({ status: "ok" });
      }
      ```

      <CodeGroupItem language="C#">
        El SDK de C# no tiene `EnvironmentWorker`. Para gestionar elementos de trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
      </CodeGroupItem>

      ```go Go
      package main

      import (
      	"context"
      	"encoding/json"
      	"errors"
      	"io"
      	"log/slog"
      	"net/http"
      	"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"
      	"github.com/anthropics/anthropic-sdk-go/packages/param"
      )

      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",
      	})
      	// Se cancela con SIGINT o SIGTERM (definido en main) para que un elemento de trabajo en curso pueda
      	// subir los archivos de memoria modificados y eliminar sus directorios de almacén antes de salir.
      	shutdown context.Context
      )

      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
      	}

      	// El SDK de Go no ofrece una utilidad RunOne: drena los elementos pendientes
      	// con WorkPoller y ejecuta cada uno con HandleItem.
      	// Desacoplar de r.Context(): la sesión puede sobrevivir al tiempo de espera de entrega del webhook.
      	// El contexto de apagado de todo el proceso aún finaliza el elemento limpiamente con SIGTERM.
      	ctx := shutdown
      	poller := environments.NewWorkPoller(ctx, client, environments.WorkPollerOptions{
      		EnvironmentID:      environmentID,
      		EnvironmentKey:     environmentKey,
      		BlockMs:            param.Null[int64](),
      		ReclaimOlderThanMs: param.NewOpt[int64](2000),
      		Drain:              true,
      		AutoStop:           param.NewOpt(false),
      	})
      	defer poller.Close()
      	for poller.Next() {
      		item := poller.Current()
      		if err := worker.HandleItem(ctx, environments.HandleItemOptions{
      			WorkID:         item.ID,
      			EnvironmentID:  item.EnvironmentID,
      			SessionID:      item.Data.ID,
      			EnvironmentKey: environmentKey,
      			// El secreto por sesión es lo que permite al worker montar los almacenes de memoria de la sesión.
      			WorkSecret: item.Secret,
      		}); 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() {
      	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
      	defer stop()
      	shutdown = ctx

      	server := &http.Server{Addr: ":8080"}
      	http.HandleFunc("POST /webhook", handle)
      	go func() {
      		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
      			slog.Error("http server", "err", err)
      			os.Exit(1)
      		}
      	}()
      	// Ante una señal, deja de aceptar entregas y retorna solo después de que los manejadores en curso,
      	// y por tanto el desmontaje de memoria de sus elementos de trabajo, hayan terminado.
      	<-ctx.Done()
      	if err := server.Shutdown(context.Background()); err != nil {
      		slog.Error("http shutdown", "err", err)
      	}
      }
      ```

      <CodeGroupItem language="Java">
        El SDK de Java no tiene `EnvironmentWorker`. Para gestionar elementos de trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        El SDK de PHP no tiene `EnvironmentWorker`. Para gestionar elementos de trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        El SDK de Ruby no tiene `EnvironmentWorker`. Para gestionar elementos de trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
      </CodeGroupItem>
    </CodeGroup>

    Como el controlador reclama el trabajo por sí mismo, debe [reenviar el secreto del elemento de trabajo](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-workers#forward-the-work-items-secret), como lo hace aquí el argumento `work_secret` (typescript: `workSecret`; go: `WorkSecret`).
  </Step>
</Steps>

Este controlador ejecuta cada elemento reclamado en un solo proceso en un solo host. Si tus sesiones adjuntan el mismo almacén de memoria, consulta [Aislar sesiones que comparten un almacén](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-memory#isolate-sessions-that-share-a-store).

## Ejecuta un sandbox por sesión

Un sondeador en el host reclama trabajo y llama a tu script una vez por cada elemento de trabajo. El script lanza un sandbox para esa sesión.

<Steps>
  <Step title="Construye la imagen del sandbox">
    Instala `ant` y establece `ant beta:worker run` como punto de entrada. Cuando un sandbox se inicia, lee los detalles de la sesión desde variables de entorno, gestiona esa sesión y termina. La imagen base debe proporcionar `/bin/bash`; `curl` solo se usa en tiempo de construcción.

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

  <Step title="Escribe el script de lanzamiento">
    El script reenvía los detalles de la sesión a un sandbox nuevo. Requiere `jq` en el host del sondeador.

    ```bash
    #!/bin/bash
    # spawn.sh: se llama una vez por cada elemento de trabajo reclamado
    # El elemento de trabajo reclamado llega como JSON por stdin.
    ANTHROPIC_WORK_SECRET="$(jq -r '.secret // empty')"
    export ANTHROPIC_WORK_SECRET
    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 \
      -e ANTHROPIC_WORK_SECRET \
      -v "/host/outputs/$ANTHROPIC_SESSION_ID":/workspace \
      your-image
    ```

    El sondeador establece las variables `ANTHROPIC_*` que el script reenvía, excepto el secreto. Consulta [Variables de entorno](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-reference#environment-variables).

    `/host/outputs` es un directorio del host que tú eliges. Montarlo en `/workspace` te permite recuperar los entregables de la sesión después de que el sandbox termine. El montaje también recoge el árbol `skills/` descargado y cualquier archivo intermedio.
  </Step>

  <Step title="Inicia el sondeador">
    ```bash
    ant beta:worker poll --on-work ./spawn.sh
    ```
  </Step>
</Steps>

### Reenvía el secreto del elemento de trabajo

Cada elemento de trabajo reclamado puede llevar un `secret` (csharp, go: `Secret`) por sesión, que emite Anthropic. El worker que ejecuta la sesión lo necesita para montar [almacenes de memoria](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-memory).

Un worker que reclama y ejecuta sesiones en un solo proceso (`ant beta:worker poll` sin `--on-work`, o `EnvironmentWorker` con `run()` (go: `Run()`)) transmite el secreto por sí mismo. Cuando tu propio código se sitúa entre la reclamación y el worker, tú lo reenvías:

| Reclamas trabajo con                                         | El secreto llega como                                                                  | Pásalo al worker como                                                                                                                                                                            |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ant beta:worker poll --on-work`                             | El campo `secret` del JSON del elemento de trabajo en la entrada estándar de tu script | `ANTHROPIC_WORK_SECRET` en el entorno del sandbox                                                                                                                                                |
| `work.poller()` (go: `environments.NewWorkPoller()`) del SDK | El campo `secret` (go: `Secret`) de cada elemento de trabajo reclamado                 | `ANTHROPIC_WORK_SECRET` en el entorno del sandbox, o el argumento `work_secret` (typescript: `workSecret`; go: `WorkSecret`) de `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`) |

Pasa el secreto solo al sandbox que atiende esa sesión y nunca lo registres en logs. Consulta [Modelo de seguridad](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-security) para ver cómo se relaciona con la clave de entorno.

### Lanza sandboxes desde el sondeador del SDK

Para reclamar trabajo desde tu propio código en lugar de `ant beta:worker poll --on-work`, usa `work.poller()` (go: `environments.NewWorkPoller()`). Sondea la cola y te entrega cada sesión reclamada, y tú lanzas el sandbox:

<CodeGroup exclude="shell, csharp, java, php, ruby">
  <CodeGroupItem language="cURL">
    Reclamar trabajo en un bucle y lanzar un sandbox para cada sesión requiere más lógica que un solo comando de shell. Desde la shell, ejecuta `ant beta:worker poll --on-work` con un script de lanzamiento, como en [Ejecuta un sandbox por sesión](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-workers#run-one-sandbox-per-session).
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    Para reclamar trabajo desde la shell y lanzar un sandbox para cada sesión, ejecuta `ant beta:worker poll --on-work` con un script de lanzamiento, como en [Ejecuta un sandbox por sesión](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-workers#run-one-sandbox-per-session).
  </CodeGroupItem>

  ```python Python
  import asyncio
  import os

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

  SANDBOX_ENV = (
      "ANTHROPIC_ENVIRONMENT_ID",
      "ANTHROPIC_ENVIRONMENT_KEY",
      "ANTHROPIC_WORK_ID",
      "ANTHROPIC_SESSION_ID",
      "ANTHROPIC_WORK_SECRET",
      "ANTHROPIC_BASE_URL",  # forwarded only when set on this host
  )


  async def launch_container(work: BetaSelfHostedWork) -> None:
      print(f"claimed session {work.data.id}")
      # Reemplaza `docker run` con tu propio lanzador de sandbox. Reenvía la clave del
      # entorno (nunca tu clave de API) y el secreto por sesión del elemento de trabajo: el worker
      # interno necesita el secreto para montar los almacenes de memoria de la sesión.
      env = os.environ | {
          "ANTHROPIC_WORK_ID": work.id,
          "ANTHROPIC_SESSION_ID": work.data.id,
          "ANTHROPIC_WORK_SECRET": work.secret or "",
      }
      forward = [arg for name in SANDBOX_ENV for arg in ("-e", name)]
      launcher = await asyncio.create_subprocess_exec(
          "docker", "run", "--rm", "--detach", *forward, "your-image", env=env
      )
      await launcher.wait()


  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,  # the launched sandbox owns the stop call
          ):
              await launch_container(work)


  asyncio.run(main())
  ```

  ```typescript TypeScript
  import { spawn } from "node:child_process";
  import { once } from "node:events";
  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 SANDBOX_ENV = [
    "ANTHROPIC_ENVIRONMENT_ID",
    "ANTHROPIC_ENVIRONMENT_KEY",
    "ANTHROPIC_WORK_ID",
    "ANTHROPIC_SESSION_ID",
    "ANTHROPIC_WORK_SECRET",
    "ANTHROPIC_BASE_URL" // forwarded only when set on this host
  ];

  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> {
    console.log(`claimed session ${work.data.id}`);
    // Reemplaza `docker run` con tu propio lanzador de sandbox. Reenvía la clave del
    // entorno (nunca tu clave de API) y el secreto por sesión del elemento de trabajo: el worker
    // interno necesita el secreto para montar los almacenes de memoria de la sesión.
    const env = {
      ...process.env,
      ANTHROPIC_WORK_ID: work.id,
      ANTHROPIC_SESSION_ID: work.data.id,
      ANTHROPIC_WORK_SECRET: work.secret ?? ""
    };
    const forward = SANDBOX_ENV.flatMap((name) => ["-e", name]);
    const launcher = spawn("docker", ["run", "--rm", "--detach", ...forward, "your-image"], {
      env,
      stdio: "inherit"
    });
    await once(launcher, "close");
  }

  const poller = new WorkPoller({
    client,
    environmentId,
    environmentKey,
    autoStop: false // the launched sandbox owns the stop call
  });

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

  <CodeGroupItem language="C#">
    El SDK de C# no tiene un helper de sondeo de trabajo. Para reclamar trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
  </CodeGroupItem>

  ```go Go
  package main

  import (
  	"context"
  	"fmt"
  	"log"
  	"os"
  	"os/exec"

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

  var sandboxEnv = []string{
  	"ANTHROPIC_ENVIRONMENT_ID",
  	"ANTHROPIC_ENVIRONMENT_KEY",
  	"ANTHROPIC_WORK_ID",
  	"ANTHROPIC_SESSION_ID",
  	"ANTHROPIC_WORK_SECRET",
  	"ANTHROPIC_BASE_URL", // forwarded only when set on this host
  }

  func launchContainer(ctx context.Context, work *anthropic.BetaSelfHostedWork) error {
  	fmt.Printf("claimed session %s\n", work.Data.ID)
  	// Reemplaza `docker run` con tu propio lanzador de sandbox. Reenvía la clave del
  	// entorno (nunca tu clave de API) y el secreto por sesión del elemento de trabajo: el worker
  	// interno necesita el secreto para montar los almacenes de memoria de la sesión.
  	args := []string{"run", "--rm", "--detach"}
  	for _, name := range sandboxEnv {
  		args = append(args, "-e", name)
  	}
  	launcher := exec.CommandContext(ctx, "docker", append(args, "your-image")...)
  	launcher.Env = append(os.Environ(),
  		"ANTHROPIC_WORK_ID="+work.ID,
  		"ANTHROPIC_SESSION_ID="+work.Data.ID,
  		"ANTHROPIC_WORK_SECRET="+work.Secret,
  	)
  	launcher.Stdout, launcher.Stderr = os.Stdout, os.Stderr
  	return launcher.Run()
  }

  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,
  		AutoStop:       param.NewOpt(false), // the launched sandbox owns the stop call
  	})
  	defer poller.Close()

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

  <CodeGroupItem language="Java">
    El SDK de Java no tiene un helper de sondeo de trabajo. Para reclamar trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
  </CodeGroupItem>

  <CodeGroupItem language="PHP">
    El SDK de PHP no tiene un helper de sondeo de trabajo. Para reclamar trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
  </CodeGroupItem>

  <CodeGroupItem language="Ruby">
    El SDK de Ruby no tiene un helper de sondeo de trabajo. Para reclamar trabajo desde tu propio código, llama a los [endpoints de Environments Work](https://platform.claude.com/docs/es/api/beta/environments/work).
  </CodeGroupItem>
</CodeGroup>

### Ejecuta el worker del SDK dentro del sandbox

Reemplaza el punto de entrada `ant beta:worker run` por un punto de entrada del SDK cuando el sandbox deba [servir herramientas personalizadas](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-custom-tools) o usar [configuraciones de sincronización de memoria no predeterminadas](https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-memory#configure-sync). El punto de entrada construye `EnvironmentWorker` y llama a `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`), que lee las mismas variables `ANTHROPIC_*` que reenvía el script de lanzamiento.

<CodeGroup exclude="shell, csharp, java, php, ruby">
  ```python Python
  import asyncio
  import contextlib
  import os
  import signal
  from anthropic import AsyncAnthropic
  from anthropic.lib.environments import EnvironmentWorker


  async def main() -> None:
      async with AsyncAnthropic(
          auth_token=os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
      ) as client:
          worker = EnvironmentWorker(client, workdir="/workspace")
          # Sin argumentos, handle_item() lee las variables ANTHROPIC_* que el script de
          # lanzamiento reenvió, incluida ANTHROPIC_WORK_SECRET.
          task = asyncio.create_task(worker.handle_item())
          # Cancelar la tarea cuando se detiene el contenedor permite al worker subir
          # los archivos de memoria modificados y eliminar los directorios del almacén antes de salir.
          loop = asyncio.get_running_loop()
          for signum in (signal.SIGINT, signal.SIGTERM):
              loop.add_signal_handler(signum, task.cancel)
          with contextlib.suppress(asyncio.CancelledError):
              await task


  asyncio.run(main())
  ```

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

  const client = new Anthropic({ authToken: process.env.ANTHROPIC_ENVIRONMENT_KEY });
  const controller = new AbortController();
  // Abortar cuando el contenedor se detiene permite al worker subir los archivos de memoria
  // modificados y eliminar los directorios del almacén antes de salir.
  process.once("SIGTERM", () => controller.abort());
  process.once("SIGINT", () => controller.abort());

  // Sin argumentos, handleItem() lee las variables ANTHROPIC_* que el script de
  // lanzamiento reenvió, incluida ANTHROPIC_WORK_SECRET.
  await new EnvironmentWorker({
    client,
    workdir: "/workspace",
    signal: controller.signal
  }).handleItem();
  ```

  <CodeGroupItem language="C#">
    El SDK de C# no tiene `EnvironmentWorker`.
  </CodeGroupItem>

  ```go 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() {
  	// Cancelar el contexto cuando el contenedor se detiene permite al worker subir
  	// los archivos de memoria modificados y eliminar los directorios del almacén antes de salir.
  	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
  	defer stop()

  	client := anthropic.NewClient(option.WithAuthToken(os.Getenv("ANTHROPIC_ENVIRONMENT_KEY")))
  	worker := environments.NewEnvironmentWorker(client, environments.EnvironmentWorkerOptions{
  		Workdir: "/workspace",
  	})
  	// Con opciones de valor cero, HandleItem lee las variables ANTHROPIC_* que el script
  	// de lanzamiento reenvió, incluida ANTHROPIC_WORK_SECRET.
  	if err := worker.HandleItem(ctx, environments.HandleItemOptions{}); err != nil {
  		log.Fatalf("worker: %v", err)
  	}
  }
  ```

  <CodeGroupItem language="Java">
    El SDK de Java no tiene `EnvironmentWorker`.
  </CodeGroupItem>

  <CodeGroupItem language="PHP">
    El SDK de PHP no tiene `EnvironmentWorker`.
  </CodeGroupItem>

  <CodeGroupItem language="Ruby">
    El SDK de Ruby no tiene `EnvironmentWorker`.
  </CodeGroupItem>
</CodeGroup>

## Prepara archivos para una sesión

Anthropic no monta archivos ni repositorios de GitHub en sandboxes autoalojados. Para que los archivos específicos de la sesión estén disponibles:

1. Pasa referencias de archivos, como una ruta de S3 o un SHA de commit, en el campo `metadata` de la sesión.
2. En tu script de lanzamiento o controlador `--on-work`, recupera la sesión (`GET /v1/sessions/{session_id}`) y lee `metadata`. El elemento de trabajo reclamado lleva el ID de la sesión, pero no los metadatos.
3. Coloca los archivos en el directorio de trabajo antes de que comience la ejecución de herramientas.

<CodeGroup>
  ```bash cURL
  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 '{
      "agent": "'"$AGENT_ID"'",
      "environment_id": "'"$ANTHROPIC_ENVIRONMENT_ID"'",
      "metadata": {"input_file": "s3://my-bucket/data.csv"}
    }'
  ```

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

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

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

  ```csharp C#
  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 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 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 PHP
  $session = $client->beta->sessions->create(
      agent: $agent->id,
      environmentID: $environment->id,
      metadata: ['input_file' => 's3://my-bucket/data.csv'],
  );
  ```

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

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Supervisar y solucionar problemas" icon="lightning" href="https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-operations">
    Lee la profundidad de la cola, detén sesiones y workers de forma limpia y corrige fallos comunes.
  </Card>

  <Card title="Modelo de seguridad" icon="lock" href="https://platform.claude.com/docs/es/managed-agents/self-hosted-sandboxes-security">
    Modelo de responsabilidad compartida para entornos de sandbox autoalojados.
  </Card>
</CardGroup>
