Saltar a contenido

Pool

Guía: Pool. El calentamiento de plazas para un agente (PoolConfig.warmup) usa agent_pool_warmup y WarmupStep, documentados en Subclientes: imagen y arranque rápido; cuándo compensa: ¿Qué uso?.

SandboxPool(config: PoolConfig, *, backend: PoolBackend | None = None, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ControlPlane | None = None, transport: TransportSettings | None = None, monotonic: Callable[[], float] = time.monotonic, now: Callable[[], datetime] = utc_now, sleep: Callable[[float], None] | None = None, random: Callable[[], float] = random.random)

Pool de sandboxes suspendidos. Arrancar con start() (o with), tomar con take(), cerrar con close().

sleep es la espera del backoff del relleno; por defecto es interrumpible por close(). monotonic, now y random son inyectables para los tests.

session: boto3.session.Session | None property

La sesión boto3 del pool: con ella firman sus plazas las URLs y las transferencias enrutadas de transfer=S3Staging(...).

start() -> Self

Resuelve la imagen, recupera el backend, barre y arranca el hilo de relleno. Idempotente.

close(*, drain: bool = True) -> None

Para el relleno y, con drain, termina todas las plazas ready. drain=False sólo con un backend persistente: deja las plazas aparcadas para que otro pool las recupere. Idempotente.

take(*, wait: float = 0.0, ready_timeout: float = DEFAULT_READY_TIMEOUT_SECONDS, request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECONDS, reconnect_timeout: float = DEFAULT_RECONNECT_TIMEOUT_SECONDS, secrets: Mapping[str, str | SecretRef] | None = None, secret_cache: SecretCache | None = None, gateways: Mapping[str, SecretGateway] | None = None) -> Sandbox

La plaza ready más vieja con al menos min_remaining_seconds de vida, reanudada y abierta con su token; sin plaza (tras esperar hasta wait s) o con una plaza muerta, un create() normal.

Coste y activación

Activa: secrets={"ENV": "nombre" | SecretRef} enlaza secretos al sandbox que sale del pool (sólo en take(): las plazas calientes nunca los llevan, ni en su lanzamiento ni en su SlotRecord). Recursos y llamadas AWS: secretsmanager:GetSecretValue una vez por secreto y TTL de SecretCache, antes de reclamar la plaza. Coste aproximado: $0,05 por 10 000 llamadas (us-east-1, 2026-09-30). IAM: secretsmanager:GetSecretValue en las credenciales del llamante. Cómo apagarla: secrets=None y secret_cache=None (por defecto). Ejemplo: sbx = pool.take(secrets={"OPENAI_API_KEY": "openai"}) sbx.commands.run("python agent.py")

gateways={"nombre": SecretGateway(...)} abre la pasarela de secretos (ADR-023) en el sandbox que sale del pool, igual que Sandbox.create(gateways=): las plazas calientes nunca la llevan. Recursos y llamadas AWS: secretsmanager:GetSecretValue una vez por secreto inyectado y TTL de SecretCache (y uno por secreto en cada sbx.gateways.refresh()); ningún recurso nuevo. Coste aproximado: el mismo $0,05 por 10 000 llamadas de arriba. IAM: secretsmanager:GetSecretValue en las credenciales del llamante. Cómo apagarla: gateways=None (por defecto); sin él, take() no hace ni un ConfigureSandbox. Exige una imagen 0.6.0 o posterior: con una anterior, take() termina la plaza tomada y lanza UnimplementedError. Ejemplo: sbx = pool.take(gateways={"anthropic": SecretGateway( upstream="https://api.anthropic.com", headers={"x-api-key": "anthropic"}, allow=[("POST", "/v1/messages")], )})

AsyncSandboxPool(config: PoolConfig, *, backend: PoolBackend | None = None, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ControlPlane | None = None, transport: TransportSettings | None = None, monotonic: Callable[[], float] = time.monotonic, now: Callable[[], datetime] = utc_now, sleep: AsyncSleeper | None = None, random: Callable[[], float] = random.random)

Pool de sandboxes suspendidos para asyncio: await start() (o async with), await take(), await close(); stats() es síncrono.

session: boto3.session.Session | None property

La sesión boto3 del pool: con ella firman sus plazas las URLs y las transferencias enrutadas de transfer=S3Staging(...).

take(*, wait: float = 0.0, ready_timeout: float = DEFAULT_READY_TIMEOUT_SECONDS, request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECONDS, reconnect_timeout: float = DEFAULT_RECONNECT_TIMEOUT_SECONDS, secrets: Mapping[str, str | SecretRef] | None = None, secret_cache: SecretCache | None = None, gateways: Mapping[str, SecretGateway] | None = None) -> AsyncSandbox async

Misma semántica que SandboxPool.take, secrets= y gateways= incluidos.

Coste y activación

Activa: secrets= enlaza secretos al sandbox que sale del pool. Recursos y llamadas AWS: secretsmanager:GetSecretValue una vez por secreto y TTL de SecretCache, antes de reclamar la plaza. Coste aproximado: $0,05 por 10 000 llamadas (us-east-1, 2026-09-30). IAM: secretsmanager:GetSecretValue en las credenciales del llamante. Cómo apagarla: secrets=None y secret_cache=None (por defecto). Ejemplo: sbx = await pool.take(secrets={"OPENAI_API_KEY": "openai"})

gateways= abre la pasarela de secretos (ADR-023) en el sandbox tomado; mismo coste, IAM y forma de apagarla (gateways=None, por defecto) que en SandboxPool.take.

PoolConfig(size: int, template: str | None = None, template_version: str | None = None, timeout: int = MAX_DURATION_SECONDS, idle: IdlePolicy = DEFAULT_IDLE_POLICY, envs: Mapping[str, str] | None = None, metadata: Mapping[str, str] | None = None, cpu_time_limit: int | None = None, execution_role_arn: str | None = None, ingress: Sequence[str] | None = None, egress: Sequence[str] | None = None, logging: LoggingOption = 'disabled', min_remaining_seconds: int = DEFAULT_MIN_REMAINING_SECONDS, fill_concurrency: int = DEFAULT_FILL_CONCURRENCY, sweep_interval_seconds: float = DEFAULT_SWEEP_INTERVAL_SECONDS, ready_timeout: float = DEFAULT_READY_TIMEOUT_SECONDS, index: DynamoDbIndex | None = None, warmup: Sequence[WarmupStep] = (), allow_internet_access: bool = True, network: NetworkInput = None) dataclass

Configuración de lanzamiento, inmutable y común a todas las plazas.

Cada campo se corresponde con el kwarg homónimo de Sandbox.create(): envs, metadata, cpu_time_limit, la política de idle, la vida máxima y los conectores son por pool, nunca por toma. timeout es 28 800 por defecto (aparcar todo lo que AWS permite) y min_remaining_seconds la vida mínima con la que se entrega una plaza: por debajo se recicla. idle es obligatorio y con auto_resume=True: sin política de idle el comportamiento de un VM suspendido por la API no está documentado y con ella una plaza olvidada se termina sola en timeout. No hay access_token (uno por plaza) ni allowed_ports (get_host(port) acuña por puerto tras la toma).

index=DynamoDbIndex(...) (None por defecto) escribe la fila del índice de metadatos de cada plaza al lanzarla, con la metadata del pool: así Sandbox.list(metadata=..., states=["SUSPENDED"], index=...) encuentra también las plazas aparcadas. Una escritura fallida con on_write_failure='terminate' descarta la plaza como cualquier otro calentamiento fallido.

warmup (vacío por defecto) son pasos de calentamiento que cada plaza corre tras asentarse y antes de su pause(): uno de primer plano espera su salida (un exit distinto de 0 o su plazo es un calentamiento fallido: la plaza se termina y el relleno aplica el backoff); uno con background=True se arranca y se suelta. Un reciclado relanza la plaza y vuelve a correrlos. agent_pool_warmup() da los del agente de IA; para los turnos de una misma conversación basta con pausar su VM. allow_internet_access y network son la política de egress de todas las plazas, como en Sandbox.create(); un pool de agentes debe pasar allow_internet_access=False. Las pasarelas siguen aplicándose sólo en take().

Coste y activación

Activa: PoolConfig(index=DynamoDbIndex("tabla")) escribe una fila por plaza lanzada (relleno, reciclado y caídas a create()). Recursos y llamadas AWS: dynamodb:PutItem una vez por plaza lanzada; sin index= ningún cliente dynamodb. Coste aproximado: ~1 WRU por plaza ≈ $0,000000625 (DynamoDB on-demand, us-east-1, consultado 2026-09-30) + $0,25/GB-mes almacenado. IAM: dynamodb:PutItem sobre la tabla (política RayitoIndexWriter de infra/metadata-index.yaml) en las credenciales del proceso del pool. Cómo apagarla: index=None (por defecto). Ejemplo: idx = DynamoDbIndex("rayito-sandboxes") pool = SandboxPool(PoolConfig(size=2, metadata={"pool": "a"}, index=idx))

PoolStats(size: int, ready: int, warming: int, takes: int, hits: int, misses: int, launched: int, recycled: int, lost: int, failed: int, slots: tuple[PoolSlotInfo, ...] = ()) dataclass

Contadores acumulados desde start() y las plazas actuales.

takes == hits + misses; launched cuenta cada run-microvm aceptado (calentamientos y fallbacks), recycled los reciclados del sweeper, lost las plazas perdidas (reconciliación, toma o recuperación) y failed los calentamientos y tomas que lanzaron una excepción.

PoolSlotInfo(sandbox_id: str, state: SlotState, started_at: datetime, expires_at: datetime, parked_at: datetime | None) dataclass

Una plaza vista desde stats(): nunca lleva el token.

PoolBackend

Bases: Protocol

Registros de plaza indexados por sandbox_id.

InMemoryPoolBackend()

Un dict: los registros (y sus secretos) mueren con el proceso.

JsonFilePoolBackend(path: str | os.PathLike[str])

{"schema": "rayito.pool/1", "slots": [...]} en path.

Cada escritura vuelca el fichero completo a un temporal de nombre aleatorio en el mismo directorio, creado en exclusiva y con el modo fijado sobre el descriptor (en Windows el modo no tiene efecto), y lo promueve con os.replace, así un corte a mitad deja el fichero anterior intacto y nadie puede precrear el temporal para quedarse con los tokens. La lectura se niega a seguir un enlace en la propia ruta del estado. Un fichero ausente carga (); otro schema es InvalidArgumentException. El mismo esquema lo lee y escribe el SDK TypeScript.