Skip to content

Optional

Features with their own cost on AWS, off by default: they are only activated by passing the option or instantiating the class. Each docstring carries its "Coste y activación" (cost and activation) block. Guides: Secrets, Metadata index, S3 mounts, Sizes, Events and webhooks, OTLP export, Secret gateway, Declarative templates, Optional stacks, EFS volumes (experimental), Custom domain (experimental), Management dashboard and Optional features.

Optional stacks

OptionalStacks(*, region: str | None = None, session: boto3.session.Session | None = None, provisioner: StackProvisioner | None = None)

Construirlo no hace ninguna llamada a AWS.

components() -> tuple[StackComponent, ...]

Metadata pura del catálogo; nunca llama a AWS.

parameter_changes(component: str | StackComponent, *, stack_name: str | None = None, parameters: dict[str, str] | None = None, artifact_bucket: str | None = None) -> tuple[ParameterChange, ...]

Qué parámetros cambiaría deploy() con estos mismos argumentos, sin desplegar nada (sólo un DescribeStacks): lo que rayito stack deploy imprime antes de pedir confirmación. Los que no se pasan y la pila ya tiene no aparecen: deploy() los conserva.

deploy(component: str | StackComponent, *, stack_name: str | None = None, parameters: dict[str, str] | None = None, artifact_bucket: str | None = None, tags: dict[str, str] | None = None, wait: bool = True, wait_timeout: float = DEFAULT_WAIT_TIMEOUT_SECONDS) -> StackStatus

Crea la pila o, si ya existe, la actualiza (ver deploy_with_outcome).

deploy_with_outcome(component: str | StackComponent, *, stack_name: str | None = None, parameters: dict[str, str] | None = None, artifact_bucket: str | None = None, tags: dict[str, str] | None = None, wait: bool = True, wait_timeout: float = DEFAULT_WAIT_TIMEOUT_SECONDS) -> StackDeployResult

Como deploy(), y además dice si CloudFormation cambió algo. Crea la pila o, si ya existe, la actualiza. Al crear, los parámetros no pasados toman su valor por defecto del catálogo; al actualizar, los no pasados conservan el valor con el que la pila está desplegada (UsePreviousValue), así redesplegar sin repetir cada parámetro no deshace la configuración anterior.

AsyncOptionalStacks(*, region: str | None = None, session: boto3.session.Session | None = None, provisioner: StackProvisioner | None = None)

StackComponent(name: str, description: str, cost: CostStatement, parameters: tuple[StackParameter, ...] = (), artifacts: tuple[StackArtifact, ...] = (), capabilities: tuple[str, ...] = (), supported: bool = True, validate: Callable[[Mapping[str, str]], None] | None = None) dataclass

StackParameter(name: str, description: str, required: bool = False, default: str | None = None, no_echo: bool = False) dataclass

StackStatus(name: str, state: str | None, outputs: dict[str, str] = dict(), reason_code: str | None = None, parameters: dict[str, str] = dict(), tags: dict[str, str] = dict()) dataclass

CostStatement(creates: tuple[str, ...], idle_monthly: str, per_use: tuple[str, ...] = (), removal: str = 'destroy() borra todo lo que esta pila creó', source: str = '') dataclass

El bloque "Coste y activación" de cada componente, de forma estructurada: creates son los recursos que la pila crea, idle_monthly el coste en reposo, per_use el coste por uso (vacío si no lo hay), removal qué se conserva o se borra con destroy y source la referencia con fecha de la página de precios consultada.

StackArtifact(name: str, parameter_key: str, bucket_parameter_key: str | None = None) dataclass

Un fichero que deploy() sube a artifact_bucket antes de desplegar la plantilla (código Lambda); parameter_key es el parámetro de la plantilla al que se pasa la clave S3 resultante (rayito/stacks/<componente>/<sha256>.zip, _packaging.artifact_key). bucket_parameter_key, si la plantilla también necesita el bucket (p. ej. Code.S3Bucket de una Lambda), es el parámetro al que deploy() pasa el mismo artifact_bucket: así ni la CLI genérica ni la fachada de la función tienen que repetirlo como --param.

S3 mounts

S3Mount(bucket: str, prefix: str = '', read_only: bool = True, allow_overwrite: bool = False, allow_delete: bool = False) dataclass

Un bucket (o un prefijo suyo) montado en el guest por rayd vía mount-s3/FUSE. Sólo tiene efecto sobre rayito-base-caps (o una variante derivada por tamaño): _role_policy.require_caps_for lo comprueba antes de lanzar cuando la imagen se conoce.

read_only=True (el valor por defecto) es la postura de mínimo privilegio: allow_overwrite/allow_delete sólo tienen efecto, y sólo se aceptan, con read_only=False.

Coste y activación

Activa: Sandbox.create(mounts={"/mnt/data": S3Mount(...)}); construir un S3Mount no llama a AWS. Recursos y llamadas AWS: ningún recurso nuevo; mount-s3 hace las peticiones S3 normales (GetObject/ListObjectsV2, y PutObject/DeleteObject con read_only=False) con el execution role del sandbox. Coste aproximado: $0 propio de Rayito; las peticiones y el almacenamiento normales de S3 del bucket (us-east-1, consultado 2026-10-01); la pila s3-mounts es $0 (sólo IAM). IAM: RayitoS3MountAccess (rayito stack deploy s3-mounts) en el execution role, y el bucket en RAYITO_ALLOWED_MOUNT_BUCKETS de la imagen. Cómo apagarla: no pases mounts=; rayito stack destroy s3-mounts quita la política (no borra objetos ni el bucket). Ejemplo: Sandbox.create("rayito-base-caps", execution_role_arn=role_arn, mounts={"/mnt/data": S3Mount(bucket="mi-bucket", prefix="team7/")})

MountStatus(state: MountPhase, last_error_class: str | None = None) dataclass

Lo que sbx.mounts[path] expone, leído de ConfigureStatus (o del resultado de la última Configure): last_error_class es None salvo cuando state == "failed".

Events and webhooks

LifecycleEvents(*, stack_name: str = DEFAULT_STACK_NAME, region: str | None = None, session: boto3.session.Session | None = None, stacks: OptionalStacks | None = None, gateway: EventsGateway | None = None)

deploy(*, artifact_bucket: str, log_group_name: str, reconciler_interval_minutes: int = DEFAULT_RECONCILER_INTERVAL_MINUTES, tags: dict[str, str] | None = None, wait: bool = True) -> StackStatus

Despliega infra/events-webhooks.yaml.

Coste y activación

Activa: una llamada explícita a este método (o rayito events deploy). Recursos y llamadas AWS: un secreto de Secrets Manager (la clave HMAC del stack), una tabla DynamoDB on-demand con streams, tres funciones Lambda con un log group cada una, una suscripción de CloudWatch Logs, dos colas SQS de fallos, una regla de EventBridge Scheduler (rate(reconciler_interval_minutes), mínimo 2) y cuatro políticas IAM gestionadas. Coste aproximado: $0,40/mes el secreto; DynamoDB, Lambda y SQS son on-demand/por uso ($0 en reposo); el Scheduler invoca el reconciliador cada reconciler_interval_minutes minutos (~$0,0000002 por invocación, us-east-1, 2026-09-30). IAM: la pila emite una política por tarea; adjunta a cada identidad sólo la suya: EventsLauncherPolicy (events=: lee la clave del stack), EventsReaderPolicy (get_events: sólo filas de eventos) y EventsWebhookAdminPolicy (register_webhook/ list_webhooks/delete_webhook: sólo filas WEBHOOK). Todas incluyen DescribeStacks sobre la pila. EventsOperatorPolicy (la unión de las tres) queda obsoleta. Cómo apagarla: destroy() (fuerza el borrado del secreto: cualquier webhook registrado deja de poder verificarse); desvincula antes esas políticas de quien las tenga, o la pila acaba en DELETE_FAILED (AWS_API_NOTES.md Q108). Ejemplo: ev = LifecycleEvents() ev.deploy(artifact_bucket="mi-bucket", log_group_name="/rayito/rayito-base")

destroy(*, wait: bool = True) -> None

Borra la pila entera: el secreto del stack (force-delete), la tabla con todos sus eventos y webhooks, las tres Lambdas, la suscripción, las colas de fallos y el scheduler. No toca los secretos de cada webhook (rayito/webhooks/..., de SecretStore) ni el log group de la imagen, que esta pila nunca creó. Si alguna de sus políticas (EventsLauncherPolicy, EventsReaderPolicy, EventsWebhookAdminPolicy o EventsOperatorPolicy) sigue vinculada a algún usuario o rol, CloudFormation no puede borrarla y la pila termina en DELETE_FAILED (StackException): desvincúlala y repite (AWS_API_NOTES.md Q108).

AsyncLifecycleEvents(*, stack_name: str = DEFAULT_STACK_NAME, region: str | None = None, session: boto3.session.Session | None = None, stacks: OptionalStacks | None = None, gateway: EventsGateway | None = None)

WebhookInfo(webhook_id: str, url: str, types: tuple[str, ...]) dataclass

Lo que register_webhook/list_webhooks exponen; nunca el secreto (ni su nombre en Secrets Manager, ni mucho menos su valor).

EventRecord(event_id: str, sandbox_id: str, kind: str, kill_reason: str | None, generation: int, occurred_at_ms: int, image_arn: str, image_version: str) dataclass

Una fila de get_events(). sandbox_execution_id es f"{sandbox_id}#{generation}" (documentado para el payload del webhook); sandbox_template_id es el ARN de la imagen.

Declarative templates

Template(spec: TemplateSpec | None = None)

Builder inmutable de un TemplateSpec. Template() crea uno vacío (sin imagen base): llama a from_base_image() antes de compilar.

from_base_image(name: str = DEFAULT_BASE_IMAGE_NAME, *, version: str | None = None) -> Self

Parte del zip de codeArtifact de name (ya publicada con rayito image publish): Template.build() lo descarga (s3:GetObject), le añade las instrucciones de cable compiladas y vuelve a subir el resultado. version fija una versión en vez de la última ACTIVE.

dnf_install(packages: str | list[str]) -> Self

Instala paquetes del sistema con dnf (Amazon Linux 2023), sin paquetes débiles y limpiando la caché en la misma capa. Cada nombre se escapa para el shell. Siempre corre como root: si un set_user() previo dejó otro usuario, el paso se envuelve en USER root y vuelve a ese usuario después.

npm_install(packages: str | list[str] | None = None, g: bool = False) -> Self

Instala paquetes con npm, con la firma de E2B (npm_install(packages=None, g=False)). Sin packages ejecuta un npm install plano en el WORKDIR (el package.json copiado antes); g=True instala globalmente y, como en E2B, corre como root aunque un set_user() previo haya dejado otro usuario. La imagen necesita Node.js: rayito-base no lo trae, así que antes hay que llamar a dnf_install(NODE_PACKAGES); sin npm el build falla con un mensaje que lo explica.

skip_cache(*, skip_cache: bool = True) -> Self

0.6 no tiene caché de capas propia (investigación §3.4): esto hace que Template.build()/build_in_background() se comporten como con force=True — envían un build nuevo aunque ya exista una versión con la misma configuración.

set_start_cmd(start_cmd: str, ready_cmd: ReadyCommand | str | None = None, *, user: str = DEFAULT_TEMPLATE_USER, workdir: str | None = None, envs: dict[str, str] | None = None) -> Self

Hornea /etc/rayito/template.json (rayito.template/1). Al arrancar, un rayd 0.6 o posterior lo lee y, antes del /ready del build (el snapshot ya lo lleva en marcha), lanza start_cmd como proceso gestionado (visible en commands.list()); si hay ready_cmd, /ready responde 503 hasta que el comando sale con 0, y 500 al agotarse ReadyPoll.timeout_seconds (wait_for_port/wait_for_url/ wait_for_process/wait_for_file en _ready_cmds.py; una cadena cruda se sondea con la cadencia por defecto, 0,5 s durante 60 s). Requiere que la imagen base se haya publicado con rayd 0.6 o posterior: un rayd anterior ignora el fichero.

to_dockerfile() -> str

El Dockerfile real que Template.build() sube (investigación §3.6: to_dockerfile() ya existe en E2B).

to_json() -> str

Serialización estable del TemplateSpec (depurar/diffear un template entre builds; no es lo que rayd lee: eso es /etc/rayito/template.json, un subconjunto sólo del StartSpec).

AsyncTemplate(spec: TemplateSpec | None = None)

Bases: Template

Template con build/build_in_background/get_build_status/ exists en versión async (asyncio.to_thread, como el resto del SDK async: ver sandbox_async/*). El resto de la superficie (DSL, to_dockerfile, to_json) es pura y se hereda tal cual.

BuildInfo(template_id: str, build_id: str, alias: str) dataclass

Lo que devuelve un build que terminó bien. template_id es el ARN de la imagen (pásalo a Sandbox.create(template_id)); build_id es "<imageVersion>/<buildId>", para get_build_status/template logs.

BuildHandle(arn: str, version: str, name: str, region: str | None = None, session: Any = None) dataclass

Lo que devuelve build_in_background(): deja la petición ya aceptada (create-microvm-image/update-microvm-image respondió) y guarda lo necesario para que get_build_status() vuelva a preguntar sin repetir el build.

BuildStatus(state: BuildState, info: BuildInfo | None = None, error_message: str | None = None) dataclass

El estado de un build en curso o terminado. info/error son mutuamente excluyentes y ambos None mientras state == "IN_PROGRESS".

ReadyCommand(cmd: str, poll: ReadyPoll) dataclass

Un ready_cmd compilado: el comando de shell y el sondeo con el que rayd lo ejecuta.

timeout(seconds: float) -> ReadyCommand

Sustituye ReadyPoll.timeout_seconds por seconds (debe ser > 0); la cadencia de sondeo no cambia.

ReadyPoll(interval_seconds: float, timeout_seconds: float) dataclass

Cuánto y con qué cadencia rayd sondea ready_cmd antes de declarar el build fallido (ver _ready_cmds.py).

TemplateSpec(base: BaseImageRef | None = None, steps: tuple[WireStep, ...] = tuple(), start: StartSpec | None = None, skip_cache: bool = False) dataclass

El estado inmutable que acumula el builder Template (_dsl.py): de qué imagen parte, en qué orden se compilan las instrucciones de cable, y el StartSpec opcional. skip_cache no activa ninguna caché de capas propia (0.6 no tiene una, investigación §3.4): sólo fuerza Template.build(..., force=True) a reconstruir en vez de reusar una versión existente con la misma configuración.

BaseImageRef(kind: str, name: str, version: str | None = None) dataclass

Qué imagen compone from_base_image(). name es el nombre o ARN de la imagen ya publicada (rayito-base, rayito-base-caps, ...); version fija una versión en vez de "la última ACTIVE" (por defecto None).

StartSpec(start_cmd: str, ready_cmd: str | None = None, user: str = DEFAULT_TEMPLATE_USER, workdir: str | None = None, envs: tuple[tuple[str, str], ...] = (), ready_poll: ReadyPoll | None = None) dataclass

Lo que horneado en /etc/rayito/template.json lee rayd (dominio compartido con rayd_core::template::StartSpec, mismo esquema JSON): el comando que arranca como proceso gestionado y, si lo hay, el que rayd sondea antes de responder 200 en /ready.

RunStep(cmd: str) dataclass

RUN <cmd>, ejecutado con /bin/sh -c.

CopyStep(src: str, dst: str) dataclass

COPY <src> <dst>: src es una ruta relativa al contexto (bajo el directorio desde el que se llama a Template.build), dst una ruta absoluta en la imagen.

EnvStep(key: str, value: str) dataclass

ENV <key>=<value>: horneado en la imagen y en todos sus snapshots. Nunca un secreto (investigación §3.7): set_envs no acepta nada que deba rotar.

WorkdirStep(path: str) dataclass

WORKDIR <path>: afecta a los RunStep/CopyStep posteriores y al workdir por defecto del start cmd.

UserStep(user: str) dataclass

USER <user>: afecta a los RunStep posteriores. DockerfileRenderer siempre vuelve a USER root antes del CMD de rayd final (investigación §3.5: "rayd sigue siendo PID 1").

wait_for_port(port: int) -> ReadyCommand

Listo cuando algo escucha en port (TCP, loopback).

wait_for_url(url: str, *, status: int = 200) -> ReadyCommand

Listo cuando url responde status (curl, sólo cabeceras).

wait_for_process(name: str) -> ReadyCommand

Listo cuando hay un proceso cuyo nombre contiene name (pgrep -f).

wait_for_file(path: str) -> ReadyCommand

Listo cuando path existe.

Secrets

SecretStore(*, region: str | None = None, session: boto3.session.Session | None = None, prefix: str = DEFAULT_SECRET_PREFIX, kms_key_id: str | None = None)

CRUD de secretos de Rayito sobre AWS Secrets Manager, en tu cuenta.

Coste y activación

Activa: SecretStore(...) es la opción explícita del CRUD; construirlo no llama a AWS: el cliente boto3 secretsmanager se crea en la primera llamada a un método. Recursos y llamadas AWS: un secreto de Secrets Manager por create (CreateSecret); update = DescribeSecret + PutSecretValue (+ UpdateSecret si cambias metadata); get_info/exists = DescribeSecret; list = ListSecrets (eventualmente consistente: un secreto recién creado o cambiado puede tardar ~3-5 s en salir); destroy = DescribeSecret + DeleteSecret (sin ventana de recuperación). Coste aproximado: $0,40 por secreto y mes hasta destroy + $0,05 por 10 000 llamadas (us-east-1, 2026-09-30). IAM: secretsmanager:CreateSecret, PutSecretValue, UpdateSecret, DescribeSecret, DeleteSecret sobre ...:secret:rayito/* y secretsmanager:ListSecrets sobre * (política RayitoSecretsAdmin de infra/secrets-access.yaml); con kms_key_id=, kms:GenerateDataKey y kms:Decrypt sobre esa clave. Logs: ni el valor ni el nombre en los logs de Rayito; el DEBUG de botocore sí imprime el valor (no lo actives con secretos). Cómo apagarla: no instancies SecretStore; borra con destroy() los secretos que ya no uses (se facturan hasta entonces). Ejemplo: store = SecretStore(region="us-east-1") info = store.create("openai", "sk-...", metadata={"team": "ml"}) store.update("openai", "sk-nuevo") page = store.list(limit=20) store.destroy("openai")

name es un nombre bajo prefix (rayito/ por defecto; '' para ninguno) o un ARN completo. Los errores nunca repiten el nombre ni el valor. Una instancia es reutilizable y segura entre hilos.

region: str | None property

La región pedida (o la de la sesión); None = la cadena por defecto.

create(name: str, value: str, *, metadata: Mapping[str, str] | None = None) -> SecretInfo

CreateSecret con la versión 1. Si el nombre aún se está borrando (destroy reciente: AWS tarda ~20-30 s en liberarlo), reintenta con backoff y jitter durante CREATE_RETRY_BUDGET_SECONDS (60 s) con el mismo ClientRequestToken y después lanza SecretException. created_at/updated_at son el reloj del cliente (get_info da los de AWS).

update(name: str, value: str, *, metadata: Mapping[str, str] | None = None) -> SecretInfo

Nueva versión n + 1 (DescribeSecret + PutSecretValue); con metadata, además la sustituye (UpdateSecret). Los lectores con SecretCache ven el valor nuevo al vencer su TTL o con refresh(). Más de una llamada cada 600 s para el mismo secreto avisa una vez (UserWarning).

get_info(name: str) -> SecretInfo

DescribeSecret. Un secreto programado para borrarse es SecretNotFoundException.

list(*, limit: int | None = None, next_token: str | None = None) -> SecretPage

Una página de ListSecrets filtrada por el prefijo (sin los programados para borrarse). limit 1-100. Es eventualmente consistente: un secreto recién creado o actualizado puede tardar ~3-5 s en aparecer (o en reflejar el cambio); get_info no.

destroy(name: str) -> bool

DescribeSecret y, si existe, DeleteSecret(ForceDeleteWithoutRecovery=True): deja de facturarse y no se puede recuperar. True si lo borró; False si no existía (o ya estaba programado para borrarse, u otro lo borró a la vez), sin llamar a DeleteSecret: el borrado forzado de AWS no distingue un nombre inexistente.

read_value(ref: SecretRef) -> str

GetSecretValue de una referencia; lo usa SecretCache (y sólo ella: nadie más debería leer valores sin caché). Nunca lee un secreto de firma de webhook (refuse_webhook_signing_secret).

cache_identity() -> tuple[str | None, object | None]

La parte (región, credenciales) de la clave de SecretCache.

api() -> SecretsManagerApi

El cliente secretsmanager, construido en el primer uso.

SecretCache(*, ttl_seconds: int | float = DEFAULT_TTL_SECONDS, store: SecretStore | None = None, region: str | None = None, session: boto3.session.Session | None = None, prefix: str | None = None)

Caché en memoria de valores de secretos: lo que hace que secrets= nunca traiga el secreto en cada llamada.

Coste y activación

Activa: secret_cache=SecretCache(ttl_seconds=300) fija la caché (y su TTL) que usa secrets=; sin él, secrets= usa una caché compartida del proceso por (región, sesión) con TTL 300, creada la primera vez. Construirla no llama a AWS. Recursos y llamadas AWS: GetSecretValue en el primer uso de cada (región, credenciales, secreto, versión) y otra vez sólo al vencer el TTL o con refresh(); un acierto hace 0 llamadas. Una sola petición en vuelo por clave (10 hilos a la vez = 1 llamada). Coste aproximado: con TTL 300, ≤ 12 llamadas/hora por secreto y proceso ≈ $0,04/mes ($0,05 por 10 000 llamadas, us-east-1, 2026-09-30); el secreto en sí cuesta $0,40/mes aparte. IAM: secretsmanager:GetSecretValue (política RayitoSecretsReader de infra/secrets-access.yaml) y kms:Decrypt si el secreto usa una CMK. Cómo apagarla: no pases secrets= ni secret_cache=. ttl_seconds=0 no existe (sería traer en cada llamada): 1..86400. Ejemplo: cache = SecretCache(ttl_seconds=600, region="us-east-1") sbx = Sandbox.create(secrets={"OPENAI_API_KEY": "openai"}, secret_cache=cache) sbx.commands.run("python agent.py") # 1 GetSecretValue sbx.commands.run("python agent.py") # 0: acierto cache.refresh("openai") # fuerza una lectura nueva

Los valores viven sólo en la memoria de este proceso; repr/str nunca los muestran. Un secreto que no existe no se guarda.

get(secret: str | SecretRef) -> str

El valor (de la caché si sigue vigente; si no, GetSecretValue). Cada llamada descarta antes los valores vencidos de esta caché.

aget(secret: str | SecretRef) -> str async

get para asyncio: un acierto no sale del bucle; un fallo corre en asyncio.to_thread con la misma petición única por clave.

refresh(name: str | SecretRef | None = None) -> None

Fuerza una lectura nueva: con name, la hace ya (y lanza si el secreto no existe); sin él, descarta todo y cada secreto se relee en su siguiente uso.

invalidate(name: str | SecretRef | None = None) -> None

Descarta los valores de name (todas sus versiones si es un str) o de todos.

sweep() -> int

Descarta los valores vencidos (sin llamar a AWS) y devuelve cuántos: un valor no sobrevive en memoria a su TTL más allá del siguiente uso de la caché.

SecretRef(name: str, version_id: str | None = None, version_stage: str | None = None) dataclass

Una referencia a un secreto, nunca su valor: name es un nombre bajo el prefijo del SecretStore ("openai" → rayito/openai) o un ARN completo (arn:..., que se usa tal cual). version_id o version_stage (como mucho uno) fijan la versión; sin ninguno se lee AWSCURRENT. Una cadena simple en secrets= equivale a SecretRef(name).

selector: tuple[str, str] property

La versión pedida como parte de la clave de caché.

SecretInfo(secret_id: str, name: str, version: int, metadata: dict[str, str], created_at: datetime, updated_at: datetime) dataclass

Metadatos de un secreto (los mismos campos que e2b.SecretInfo). El valor es de sólo escritura y nunca vuelve en ninguna lectura.

secret_id es el ARN de Secrets Manager (E2B usa sec_…); version es el entero que Rayito codifica en el ClientRequestToken (1 al crear, +1 en cada update; 0 si la versión actual no la escribió Rayito); metadata vive en Description (≤ 2048 caracteres codificados).

SecretPage(items: list[SecretInfo], next_token: str | None) dataclass

Una página de SecretStore.list(): next_token es None en la última.

Metadata index

DynamoDbIndex(table_name: str, *, region: str | None = None, session: boto3.session.Session | None = None, on_write_failure: WriteFailurePolicy = 'terminate', ttl_margin_seconds: int = DEFAULT_TTL_MARGIN_SECONDS)

Índice de metadatos de sandboxes sobre una tabla DynamoDB de tu cuenta.

Coste y activación

Activa: index=DynamoDbIndex("rayito-sandboxes") en Sandbox.create() (escribe una fila por sandbox), Sandbox.list()/paginate() con metadata= (filtra también SUSPENDED sin sondear Health) y PoolConfig(index=) (escribe al rellenar). Construirlo no llama a AWS: el cliente boto3 dynamodb se crea en su primer uso. Recursos y llamadas AWS: dynamodb:PutItem (1 por sandbox creado, condicional attribute_not_exists(pk)) y dynamodb:BatchGetItem (1 por página de list-microvms, ≤ 100 claves, lectura eventualmente consistente). Ningún recurso: la tabla la despliegas tú (infra/metadata-index.yaml). Nunca DeleteItem (el TTL borra). Coste aproximado: on-demand, us-east-1, consultado 2026-09-30: ~1 WRU por create ≈ $0,000000625; 0,5 RRU por ítem listado; $0,25/GB-mes; 10 000 sandboxes/mes < $0,10; tabla vacía $0 (https://aws.amazon.com/dynamodb/pricing/on-demand/). IAM: dynamodb:PutItem (escritor) y dynamodb:BatchGetItem (lector) sobre el ARN de la tabla, en las credenciales del llamante. Cómo apagarla: no pases index= (None por defecto) y borra el stack de la tabla si ya no la usas. Ejemplo: idx = DynamoDbIndex("rayito-sandboxes", region="us-east-1") sbx = Sandbox.create(metadata={"user": "42"}, index=idx) sbx.pause() items = list(Sandbox.list(metadata={"user": "42"}, states=["SUSPENDED"], index=idx))

on_write_failure decide qué hace create() si PutItem falla: 'terminate' (por defecto) termina el MicroVM recién lanzado (salvo keep_on_failure=True) y lanza IndexWriteException; 'warn' avisa en el logger rayito.index y devuelve el sandbox, que no aparecerá en los listados con índice. ttl_margin_seconds es el margen sobre la vida máxima del sandbox antes de que la fila caduque. region/session son los de la tabla (por defecto la cadena de boto3); sin región, create() lanza InvalidArgumentException antes de run-microvm, sin lanzar ningún MicroVM. Reutilizable y seguro entre hilos.

now() -> float

Segundos epoch con los que se descartan filas caducadas.

put(record: IndexRecord) -> None

PutItem condicional: nunca sobrescribe una fila existente.

batch_get(sandbox_ids: Sequence[str]) -> dict[str, IndexRecord]

Las filas vigentes de sandbox_ids, en trozos de 100 con lectura eventualmente consistente; reintenta UnprocessedKeys con un backoff acotado y, si siguen sin procesar, lanza (nunca una lista incompleta en silencio). Las filas caducadas o con otra forma se descartan.

prepare() -> None

Comprobación previa sin llamadas a AWS que create() hace antes de run-microvm: construye el cliente dynamodb (construirlo no llama a AWS) y lanza InvalidArgumentException si no hay región, así un error de configuración nunca lanza (ni factura) un MicroVM.

api() -> DynamoDbApi

El cliente dynamodb, construido en el primer uso.

Sizes

SizeRequest(memory_mib: int) dataclass

size=SizeRequest(memory_mib=3000): una petición explícita en MiB, para cuando ningún nombre corto ("512mb".."8gb") encaja exactamente. Siempre se redondea hacia arriba, nunca hacia abajo: un sandbox nunca recibe menos memoria de la que se pidió.

Secret gateway

SecretGateway(upstream: str, headers: Mapping[str, str | SecretRef], allow: Sequence[tuple[str, str]], rate_per_minute: int = 0) dataclass

Una entrada de gateways={"nombre": SecretGateway(...)} en Sandbox.create(): un único upstream fijo al que rayd reenvía, inyectando en cada petición permitida el valor de cada cabecera de headers (resuelto de Secrets Manager por su nombre, nunca aquí) y rechazando lo que allow no cubre o exceda rate_per_minute.

Coste y activación

Activa: gateways={"nombre": SecretGateway(...)} en Sandbox.create() (o SandboxPool.take(gateways=)); sin él, rayd no abre ningún listener de loopback y el SDK no hace ninguna llamada a ConfigureSandbox ni a Secrets Manager. Recursos y llamadas AWS: secretsmanager:GetSecretValue una vez por cabecera y TTL de la SecretCache que ya usa secrets= (un acierto no llama a AWS), y una por cabecera en cada sbx.gateways.refresh(); ningún recurso nuevo (reutiliza infra/secrets-access.yaml). Coste aproximado: el de SecretCache ($0,05 por 10 000 llamadas, us-east-1, 2026-09-30) más el del secreto en sí si no existía ya. IAM: secretsmanager:GetSecretValue en las credenciales del llamante, igual que secrets= (no el execution role: la pasarela corre en rayd, no necesita rol de ejecución). Cómo apagarla: no pases gateways= (por defecto None). Ejemplo: sbx = Sandbox.create(gateways={ "anthropic": SecretGateway( upstream="https://api.anthropic.com", headers={"x-api-key": "anthropic"}, allow=[("POST", "/v1/messages")], rate_per_minute=600, ) }) url = sbx.gateways["anthropic"].url sbx.commands.run("python agent.py", envs={"ANTHROPIC_BASE_URL": url})

GatewayStatus(port: int, last_error_class: str | None = None) dataclass

Espejo de SecretGatewayRouteStatus: el estado de una ruta tras el Configure (o ConfigureStatus) más reciente.

url: str property

"http://127.0.0.1:<port>": no es secreto (nada más que uid 1000 puede alcanzar el puerto dentro del sandbox).

GatewayHandle(statuses: Mapping[str, GatewayStatus], *, refresher: Callable[[], Mapping[str, GatewayStatus]] | None = None, async_refresher: Callable[[], Awaitable[Mapping[str, GatewayStatus]]] | None = None, recovered: bool = False)

Bases: Mapping[str, GatewayStatus]

sbx.gateways: un mapeo de sólo lectura nombre -> GatewayStatus. Sin gateways= es siempre un GatewayHandle vacío cuyo refresh()/ arefresh() no hacen nada, así que llamarlos nunca es una rama especial para quien los use. Con gateways=, releen cada cabecera de Secrets Manager (aunque la SecretCache no haya vencido) y mandan un Configure nuevo — la forma de rotar un secreto sin recrear el sandbox. Cada ruta conserva su puerto; si rayd rechaza la sección, lanzan y el estado anterior sigue en pie.

Sandbox (síncrono) pasa refresher; AsyncSandbox pasa async_refresher (mismo patrón que SecretCache.get/aget): cada uno sabe envolver al otro, así que da igual cuál dio el handle.

recovered: bool property

True si connect() lo reconstruyó desde ConfigureStatus (no sabe rotar secretos); False si viene de create(gateways=).

refresh() -> None

Para Sandbox. Un AsyncSandbox usa arefresh(): llamar a refresh() desde una corrutina bloquearía su propio loop.

arefresh() -> None async

Para AsyncSandbox. Sobre un handle síncrono, corre el refresher en un hilo (como SecretCache.aget sobre get).

OTLP export

Guide: OTLP export.

TelemetryExport(interval_s: int = DEFAULT_INTERVAL_S, service_name: str = DEFAULT_SERVICE_NAME, names: NameStyle = 'rayito', auth: OtlpAuth = OtlpAuth.execution_role()) dataclass

telemetry= de Sandbox.create()/AsyncSandbox.create(). Validado al construirse: un valor fuera de rango es InvalidArgumentException antes de run-microvm, nunca una sección rechazada por rayd después de lanzar la MicroVM.

OtlpAuth(kind: Literal['execution_role', 'bearer'], secret_name: str | None = None) dataclass

Cómo firma rayd cada exportación (research §6.3: opciones B1/B1').

execution_role() es SigV4 sobre las credenciales IMDS del execution role: exige rayito-base-caps (o una variante derivada, por tamaño). bearer(secret_name=...) es experimental (B1'): secret_name se resuelve igual que en secrets= y SecretStore, bajo el prefijo rayito/ ("otlp-key" lee rayito/otlp-key; un ARN completo se usa tal cual), así que basta la política RayitoSecretsReader de infra/secrets-access.yaml. El valor se lee al enviar la sección por la SecretCache compartida (_section.resolve_bearer_token) y viaja a rayd por ConfigureSandbox, nunca por variables de entorno (regla 4 de ADR-014) ni en texto plano en ningún lado; funciona en rayito-base, sin caps.

TelemetryHealth(exported: int = 0, dropped: int = 0, last_error_class: str | None = None) dataclass

sbx.get_telemetry_status(): espejo de TelemetryExportStatus (ConfigureService.ConfigureStatus). Todo ceros/None cuando rayd nunca aplicó una sección (no hay telemetry=, o la imagen no soporta la función).

EFS volumes (experimental)

VolumeStore(*, file_system_id: str, region: str | None = None, session: boto3.session.Session | None = None)

CRUD de volúmenes (access points) de un sistema de ficheros EFS que ya existe (desplegado con rayito stack deploy efs-volumes o uno propio). Una instancia es reutilizable y segura entre hilos.

Coste y activación

Activa: VolumeStore(...); construirlo no llama a AWS (el cliente boto3 efs se crea en el primer método). Experimental: lo que devuelve se monta con Sandbox.create(volumes={ruta: vol}) sobre la imagen opcional rayito-base-caps-efs (rayito image publish --with-efs); ese create() añade una DescribeMountTargets por sistema de ficheros si el volumen no trae mount_target_ip. Recursos y llamadas AWS: ningún recurso nuevo (el sistema de ficheros lo crea infra/efs-volumes.yaml, por separado); create = CreateAccessPoint, get/list = DescribeAccessPoints, destroy = DeleteAccessPoint. Coste aproximado: los access points no tienen cargo propio listado; el sistema de ficheros se factura por infra/efs-volumes.yaml (ver ese componente en rayito stack list). IAM: elasticfilesystem:CreateAccessPoint, DescribeAccessPoints, DeleteAccessPoint (y DescribeMountTargets para montar sin mount_target_ip) sobre el sistema de ficheros (credenciales del LLAMANTE, no del execution role del MicroVM). Cómo apagarla: no instancies VolumeStore; borra con destroy() los volúmenes que ya no uses (el directorio que cubrían no se borra, sólo el access point: borrar los datos exige montar el sistema de ficheros desde un sandbox, fuera de alcance en 0.6). Ejemplo: store = VolumeStore(file_system_id="fs-0123abcd", region="us-east-1") vol = store.create("datos-agente-7") store.get("datos-agente-7") store.list() store.destroy("datos-agente-7")

create(name: str) -> EfsVolume

CreateAccessPoint con uid/gid 1000:1000 y la ruta raíz /rayito-volumes/<name> (research doc §4.5). Idempotente: llamarla dos veces con el mismo nombre no crea dos access points (ClientToken es un hash de file_system_id+nombre). AWS no devuelve el access point ya creado para un ClientToken repetido: responde AccessPointAlreadyExists, que esta función atrapa para devolver get(name) en su lugar, reintentado durante LIST_VISIBILITY_BUDGET_SECONDS porque el listado de EFS tarda en mostrar un access point recién creado (Q125).

get(name: str) -> EfsVolume

DescribeAccessPoints(FileSystemId=...) filtrado por la etiqueta rayito:volume=<name>. VolumeNotFoundException si no existe ninguno con ese nombre.

list() -> tuple[EfsVolume, ...]

Todos los access points del sistema de ficheros con la etiqueta de volumen de Rayito, paginando DescribeAccessPoints hasta agotar NextToken. Eventualmente consistente, como el propio DescribeAccessPoints (Q125): un volumen recién creado puede tardar unos segundos en aparecer y uno recién borrado seguir apareciendo (también en get).

destroy(name: str) -> bool

DeleteAccessPoint; los ficheros bajo su directorio raíz no se borran (honesto: borrarlos exigiría montar el sistema de ficheros, fuera de alcance en 0.6). True si existía y se borró; False si no existía ningún volumen con ese nombre.

api() -> EfsApi

El cliente efs, construido en el primer uso.

AsyncVolumeStore(*, file_system_id: str, region: str | None = None, session: boto3.session.Session | None = None)

EfsVolume(file_system_id: str, access_point_id: str, name: str | None = None, region: str | None = None, read_only: bool = False, mount_target_ip: str | None = None) dataclass

Un volumen EFS: el access point que un sandbox puede montar bajo volumes={path: EfsVolume(...)}. Validado en construcción, sin ninguna llamada a AWS; name/region son metadata del cliente (VolumeStore los rellena al crear o leer uno), nunca parte de la identidad que manda al guest.

VolumeStatus(state: MountState, last_error_class: str | None = None) dataclass

Lo que sbx.volumes[path] reporta (una ConfigureStatus por lectura). last_error_class es una cadena cerrada de efs_volumes.proto (credentials_expired, flush_timeout, stale, iam_denied...), nunca un mensaje de AWS.

EfsVolumes(*, stack_name: str = DEFAULT_STACK_NAME, region: str | None = None, session: boto3.session.Session | None = None, stacks: OptionalStacks | None = None, network: NetworkInspector | None = None, efs: Callable[[], EfsFileSystemApi] | None = None)

Volúmenes EFS en tu propia VPC: comprueba, despliega, consulta y borra el componente efs-volumes. Construirlo no llama a AWS.

Coste y activación

Activa: una llamada explícita a deploy(vpc_id=, subnet_ids=) (o rayito stack deploy efs-volumes --param VpcId=... --param SubnetIds=...). check() es de sólo lectura y no crea nada. Experimental: Sandbox.create(volumes=...) monta sobre la imagen opcional rayito-base-caps-efs (rayito image publish --with-efs), con el execution role y este conector como único egress=: no puede usar además INTERNET_EGRESS. Recursos y llamadas AWS: check() = ec2:DescribeVpcs, DescribeVpcAttribute, DescribeSubnets, DescribeRouteTables. deploy() crea, sólo dentro de la VPC dada y etiquetado: un sistema de ficheros EFS cifrado (clave KMS gestionada por AWS), un mount target por subred, dos grupos de seguridad nuevos (el de los mount targets sólo admite TCP 2049 desde el del conector), el AWS::Lambda::NetworkConnector de salida a la VPC para MicroVMs, su rol de operador y la política RayitoEfsVolumeClient. Nunca modifica la VPC, sus subredes, tablas de rutas, NACLs ni grupos existentes. Coste aproximado: $0 con el sistema de ficheros vacío; $0,30/GB-mes (Standard) y $0,016/GB-mes tras 30 días (IA); Elastic Throughput $0,03/GB leído y $0,06/GB escrito; sin cargo listado por mount targets, access points ni ENIs del conector (us-east-1, 2026-09-11). IAM: el llamante necesita cloudformation:*Stack* sobre la pila, crear los recursos de arriba (CAPABILITY_IAM), los ec2:Describe* de check() y, para borrar el sistema de ficheros, elasticfilesystem:DescribeFileSystems/DescribeMountTargets/ DescribeAccessPoints/DeleteAccessPoint/DeleteFileSystem; el execution role del MicroVM, la política CallerPolicyArn de la salida (ClientMount/ClientWrite sólo sobre este sistema de ficheros y sus access points; nunca ClientWrite sobre read_only_access_point_arns). Cómo apagarla: destroy() borra la pila (conector, grupos, mount targets, rol y política) y conserva el sistema de ficheros con sus datos; destroy(delete_file_system=True) borra además sus access points y el propio sistema de ficheros: todo lo que deploy() creó. Tras rayito stack destroy efs-volumes (que siempre lo conserva), delete_file_system("fs-…") borra el que quedó. Ejemplo: efs = EfsVolumes(region="us-east-1") network = {"vpc_id": "vpc-0123456789abcdef0", "subnet_ids": ["subnet-0123456789abcdef0"]} report = efs.check(network) efs.deploy(network) store = efs.volume_store() efs.destroy(delete_file_system=True)

check(*, vpc_id: str, subnet_ids: Sequence[str] | str) -> EfsNetworkReport

Valida, sin crear nada, que la VPC y las subredes sirven: que existen, que las subredes son de la VPC y están en AZs distintas, que les quedan IPs libres y si la VPC resuelve DNS; avisa de una sola AZ y de cómo saldría a internet un sandbox. El informe incluye lo que deploy() crearía y su coste. Sólo llama a ec2:DescribeVpcs/DescribeVpcAttribute/DescribeSubnets/ DescribeRouteTables.

deploy(*, vpc_id: str, subnet_ids: Sequence[str] | str, allow_write: bool = True, access_point_arns: Sequence[str] = (), read_only_access_point_arns: Sequence[str] = (), connector_name: str | None = None, tags: dict[str, str] | None = None, wait: bool = True) -> StackStatus

Corre check() y, sólo si ningún hallazgo es FAIL, despliega (o actualiza) la pila en la VPC dada. access_point_arns acota la política RayitoEfsVolumeClient a esos access points; vacío, a cualquier access point de la cuenta y región sobre este sistema de ficheros. allow_write=False sólo concede ClientMount. read_only_access_point_arns deja esos access points en sólo lectura aunque allow_write sea True (la política les deniega ClientWrite): es lo que hace de sólo lectura un volumen, porque la opción ro del montaje no impide escribir por el túnel de efs-proxy (AWS_API_NOTES.md §16 Q133). Ver el bloque "Coste y activación" de la clase.

volume_store() -> VolumeStore

Un VolumeStore sobre el sistema de ficheros de la pila ya desplegada (lee FileSystemId de sus salidas). VolumeException si la pila no existe.

destroy(*, delete_file_system: bool = False, wait: bool = True) -> None

Borra la pila. El sistema de ficheros se conserva siempre (DeletionPolicy: Retain) salvo con delete_file_system=True, que después borra sus access points y el propio sistema de ficheros (y con él todos los datos): eso exige wait=True, porque DeleteFileSystem falla mientras quede un mount target.

delete_file_system(file_system_id: str) -> None

Borra un sistema de ficheros que esta pila creó y conservó (p. ej. tras rayito stack destroy efs-volumes, que siempre lo deja): espera a que no le quede ningún mount target, borra sus access points y después el sistema de ficheros, con todos sus datos. Se niega (VolumeException) si no lleva la etiqueta que infra/efs-volumes.yaml le pone (rayito=efs-volumes): nunca borra uno ajeno. Uno que ya no existe es un no-op.

AsyncEfsVolumes(*, stack_name: str = DEFAULT_STACK_NAME, region: str | None = None, session: boto3.session.Session | None = None, stacks: OptionalStacks | None = None, network: NetworkInspector | None = None, efs: Callable[[], EfsFileSystemApi] | None = None)

Ver EfsVolumes (su bloque "Coste y activación" vale igual aquí).

EfsNetworkReport(vpc_id: str, subnet_ids: tuple[str, ...], availability_zones: tuple[str, ...], findings: tuple[NetworkFinding, ...], cost: CostStatement) dataclass

Lo que EfsVolumes.check() devuelve sin crear nada: los hallazgos, las AZs que cubrirían los mount targets y lo que deploy() crearía con su coste (el CostStatement del componente efs-volumes, la misma fuente que rayito stack list).

ok: bool property

True si ningún hallazgo es FAIL: deploy() se niega si no.

NetworkFinding(level: FindingLevel, code: str, message: str) dataclass

Un resultado de la comprobación: code es un conjunto cerrado (vpc-missing, vpc-unavailable, vpc-dns-support, vpc-dns-hostnames, subnet-missing, subnet-other-vpc, subnet-unavailable, subnet-same-az, subnet-free-ips, single-az, internet-egress).

Custom domain (experimental)

Guide: Custom domain. Not yet verified end to end on real AWS.

CustomDomain(*, public_domain: str, stack_name: str | None = None, kvs_arn: str | None = None, region: str | None = None, session: boto3.session.Session | None = None, provisioner: StackProvisioner | None = None, kvs_writer: KeyValueStoreWriter | None = None, clock: Callable[[], float] = time.time)

Dominio propio sobre CloudFront (experimental). Construirlo no hace ninguna llamada a AWS.

Experimental: la API puede cambiar en una minor. La CloudFront Function de enrutado y la creación de su KeyValueStore se comprobaron en AWS real (AWS_API_NOTES.md Q121, Q140), pero crear la distribución y servir tráfico por ella no (Q141: la cuenta de pruebas lo deniega con una SCP). Si lo pruebas, cuéntalo en un issue de GitHub.

Coste y activación

Activa: CustomDomain(public_domain=...).deploy(certificate_arn=...). Recursos y llamadas AWS: una distribución CloudFront, su CloudFront Function de enrutado y un KeyValueStore (infra/custom-domain.yaml, rayito stack deploy custom-domain). En uso, register()/ unregister()/refresh() llaman a DescribeKeyValueStore/PutKey/ DeleteKey (AWS_API_NOTES.md §29); nada de esto se llama sin invocarlos explícitamente. No hay refresher automático en este cambio (seguimiento no bloqueante, ver _stacks/components/ custom_domain.py): llama a refresh() tú mismo antes de que caduque el JWE de una ruta (DOM-7). Coste aproximado: CloudFront ~$0,085/GB + $0,0075/10 000 peticiones HTTPS (datos salientes, us-east-1); CloudFront Functions ~$0,10 por 1 000 000 de invocaciones (una por petición); KeyValueStore $0 en reposo, ~$0,50 por 1 000 000 de lecturas (las de la Function) y ~$5 por 1 000 000 de llamadas de gestión (PutKey/DeleteKey de register/unregister/refresh) — tres líneas separadas, no una cifra combinada (_stacks/components/custom_domain.py, cifras de lista de CloudFront Functions/KeyValueStore desde su lanzamiento, reconfirmar en la etapa de aceptación AWS). IAM: cloudformation:*Stack* para deploy/destroy (vía OptionalStacks); cloudfront-keyvaluestore:DescribeKeyValueStore/PutKey/DeleteKey sobre el KVS de la pila para register/unregister/refresh. Cómo apagarla: no llames a deploy(); destroy() borra la distribución, la Function y el KVS — ningún dato de sandbox se conserva, las rutas son efímeras. Ejemplo: domain = CustomDomain(public_domain="sbx.example.com") domain.deploy(certificate_arn="arn:aws:acm:us-east-1:...:certificate/...") route = domain.register( "ws-7", 8000, endpoint=sbx.endpoint, jwe=jwe, traffic_token=secrets.token_urlsafe(32), ttl_seconds=2400, ) route.host # "8000-ws-7.sbx.example.com" domain.unregister("ws-7", 8000)

deploy(*, certificate_arn: str, alternate_domain_names: Sequence[str] | None = None, tags: dict[str, str] | None = None, wait: bool = True, wait_timeout: float = CUSTOM_DOMAIN_WAIT_TIMEOUT_SECONDS) -> StackStatus

Despliega (o actualiza) infra/custom-domain.yaml. certificate_arn debe estar en us-east-1 (requisito de CloudFront) y cubrir los nombres de la distribución: *.<public_domain> por defecto.

alternate_domain_names sustituye ese comodín por una lista explícita (_domain.validate_alternate_domain_names; normalmente host_for(alias, port) de rutas conocidas), p. ej. si otra distribución ya tiene *.<public_domain>. Las rutas cuyo host no esté en la lista no llegan a esta distribución. No cambia el coste. Tras desplegar, apunta un CNAME/alias de DNS de cada nombre al DistributionDomainName de la salida.

kvs_arn() -> str

El ARN del KeyValueStore de la pila: el pasado al construir CustomDomain, o el de la última deploy()/status(). Nunca llama a AWS por sí sola: si no hay ninguno, llama a status() primero.

host_for(alias: str, port: int) -> str

El hostname público de una ruta; no llama a AWS ni exige que la ruta esté ya registrada (pura, _domain.route_host).

register(alias: str, port: int, *, endpoint: str, jwe: str, traffic_token: str | None = None, public: bool = False, ttl_seconds: int) -> CustomDomainRoute

Escribe las dos claves de la ruta (j:/m:) con ETag encadenado (_write_route(..., rollback=True): ninguna de las dos claves existía antes para este label, así que si la segunda escritura falla, la primera se borra best-effort antes de relanzar — nunca se deja un JWE vivo sin sus metadatos). Una ruta es pública (sin traffic_token que comprobar) sólo con public=True, nunca por omisión: sin traffic_token ni public=True se rechaza antes de tocar el KVS (SEC-T25 — el valor por defecto nunca es "pública").

refresh(route: CustomDomainRoute, *, jwe: str, ttl_seconds: int) -> CustomDomainRoute

Reescribe j:<label> con un JWE nuevo (el TokenRefresher del transporte ya renueva el suyo a los 45 min, _transport.TOKEN_REFRESH_AFTER_MINUTES; esto hace lo mismo para la copia que vive en el KVS) y también m:<label>, para que su expiración no quede obsoleta: mismo endpoint/hash de traffic_token que route ya tenía, sólo expires_at al día. _write_route(..., rollback=False): a diferencia de register(), aquí ambas claves ya existían con una ruta sana sirviendo tráfico, así que un fallo a medias (p. ej. el PutKey de m: tras el de j:) no debe borrar lo que sí se escribió — eso dejaría una ruta que funcionaba sin su j: o su m: por un error transitorio del KVS.

unregister(alias: str, port: int) -> None

Idempotente: una ruta que ya no está no es un error (ResourceNotFoundException del DeleteKey se traga aquí, no en el adaptador — llegar aquí ya exige que el almacén exista).

AsyncCustomDomain(*, public_domain: str, stack_name: str | None = None, kvs_arn: str | None = None, region: str | None = None, session: boto3.session.Session | None = None, provisioner: StackProvisioner | None = None, kvs_writer: KeyValueStoreWriter | None = None, clock: Callable[[], float] = time.time)

CustomDomain para asyncio (experimental): los mismos métodos, cada uno en un hilo porque boto3 es bloqueante. Construirlo no hace ninguna llamada a AWS. El bloque "Coste y activación" y el alcance de lo verificado en AWS real son los de CustomDomain.

CustomDomainRoute(alias: str, port: int, public_domain: str, endpoint: str, traffic_token_sha256: str, expires_at: datetime) dataclass

Una ruta ya registrada: su hostname y cuándo caduca el JWE que cubre. endpoint/traffic_token_sha256 son los metadatos que register() ya escribió en m:<label>; refresh() los necesita de vuelta para reescribir esa clave con la misma endpoint/hash y sólo expires_at al día.

Management dashboard

Quotas(*, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ManagementPlane | None = None, source: QuotaSource | None = None, catalog: SizeCatalog = DEFAULT_SIZE_CATALOG)

Cuotas y uso de memoria de una región.

Coste y activación

Activa: una llamada explícita a report(); construir Quotas no llama a AWS. Recursos y llamadas AWS: sólo lecturas, ninguna crea recursos: ListServiceQuotas (código lambda), ListMicrovms y, como mucho una vez por versión de imagen y proceso, GetMicrovmImageVersion. Coste aproximado: $0, sin ninguna llamada de pago. GetMicrovmImageVersion no factura ni tiene cuota propia (AWS_API_NOTES.md §24 y §30); Service Quotas no publica ninguna tarifa por consulta (guía de Service Quotas, https://docs.aws.amazon.com/servicequotas/latest/ userguide/intro.html, consultada 2026-10-13); ListMicrovms cuenta contra un TPS no documentado (a confirmar en la aceptación, tarea 1.1 del cambio dashboard). IAM: servicequotas:ListServiceQuotas, lambda:ListMicrovms y lambda:GetMicrovmImageVersion. Cómo apagarla: no llames a report(); no deja recursos que borrar. Ejemplo: report = Quotas(region="us-east-1").report() print(report.memory.level)

report() -> QuotaReport

Gratuita: sólo lecturas de Service Quotas y de Lambda MicroVMs, sin ninguna llamada de pago ni recursos nuevos.

AsyncQuotas(*, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ManagementPlane | None = None, source: QuotaSource | None = None, catalog: SizeCatalog = DEFAULT_SIZE_CATALOG)

El mismo contrato y el mismo coste que Quotas.

Coste y activación

Activa: una llamada explícita a report(); construir AsyncQuotas no llama a AWS. Recursos y llamadas AWS: sólo lecturas, ninguna crea recursos: ListServiceQuotas, ListMicrovms y GetMicrovmImageVersion. Coste aproximado: $0, sin ninguna llamada de pago (AWS_API_NOTES.md §24 y §30; Service Quotas no publica tarifa por consulta, consultado 2026-10-13). IAM: servicequotas:ListServiceQuotas, lambda:ListMicrovms y lambda:GetMicrovmImageVersion. Cómo apagarla: no llames a report(); no deja recursos que borrar. Ejemplo: report = await AsyncQuotas(region="us-east-1").report() print(report.memory.level)

report() -> QuotaReport async

Gratuita. Ver Quotas.report.

QuotaReport(region: str, entries: tuple[QuotaEntry, ...], memory: MemoryUsage, warnings: tuple[QuotaWarning, ...]) dataclass

Cuotas aplicadas, uso de memoria y avisos de una región.

applied_rate(operation: str) -> float | None

La tasa aplicada de operation (SuspendMicrovm…), o None si Service Quotas no la lista.

QuotaEntry(code: str, name: str, kind: str, operation: str | None, default: int, value: float | None, adjustable: bool, increase_url: str) dataclass

Una cuota del catálogo con su valor aplicado (None si Service Quotas no la lista), el valor por defecto de la región y el enlace de aumento.

QuotaWarning(kind: WarningKind, code: str, message: str, increase_url: str) dataclass

Un aviso con el enlace para pedir el aumento de code.

MemoryUsage(allocated_gb: float, quota_gb: float | None, fraction: float | None, level: QuotaLevel, sandboxes: int, unresolved: int) dataclass

Memoria asignada (RUNNING más suspendidos) contra la cuota de la región. unresolved cuenta los sandboxes cuya memoria de imagen no se pudo leer: el uso real puede ser mayor.

TpsLimit(operation: str, rate: float | None, burst: float | None) dataclass

La tasa y la ráfaga aplicadas de una operación (None si no se listan).

UsageCost(*, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ManagementPlane | None = None, source: CostSource | None = None, catalog: SizeCatalog = DEFAULT_SIZE_CATALOG, cache_ttl_seconds: float = COST_CACHE_TTL_SECONDS, clock: Callable[[], datetime] = utc_now, monotonic: Callable[[], float] = time.monotonic)

Coste facturado y estimado de Lambda MicroVMs.

Coste y activación

Activa: una llamada explícita a billed() (la estimación, estimate(), no usa Cost Explorer); construir UsageCost no llama a AWS. Recursos y llamadas AWS: sólo lecturas. billed() hace ce:GetCostAndUsage (una petición por página, normalmente una) en us-east-1, donde vive Cost Explorer; estimate() hace ListMicrovms y GetMicrovmImageVersion (una por versión de imagen y proceso). Coste aproximado: billed() es de pago: $0,01 por petición a Cost Explorer (us-east-1, consultado 2026-10-13, https://aws.amazon.com/ aws-cost-management/aws-cost-explorer/pricing/). La caché está en memoria, es de cada instancia (se pierde al terminar el proceso o al crear otra UsageCost), guarda como mucho COST_CACHE_MAX_ENTRIES ventanas y se indexa por inicio, fin, granularidad y región: reutiliza una sola instancia para ahorrar. Con una instancia de larga vida y COST_CACHE_TTL_SECONDS (24 h) salen unas 30 peticiones al mes ≈ $0,30; con una instancia nueva por llamada (un cron, una Lambda, un script) cada billed() paga $0,01. DAILY sobre ventanas largas puede necesitar varias páginas de pago. estimate() es gratuita. IAM: ce:GetCostAndUsage para billed(); lambda:ListMicrovms y lambda:GetMicrovmImageVersion para estimate(). Cómo apagarla: no llames a billed(); no deja recursos que borrar. Opciones: cache_ttl_seconds baja o sube la caducidad de la caché de billed(); bajarla añade peticiones de pago ($0,01 cada una). Ejemplo: cost = UsageCost().billed() print(cost.total_usd, cost.partial)

billed(*, start: date | str | None = None, end: date | str | None = None, granularity: Granularity = 'MONTHLY', region: str | None = None, refresh: bool = False) -> BilledCost

El coste facturado de start (incluido) a end (excluido); por defecto, del día 1 del mes en curso (UTC) a mañana. region filtra por región de AWS (sin filtro, toda la cuenta). Cost Explorer tiene unas 24 h de retraso, y partial avisa de que aún hay periodos estimados por AWS. Una ventana ya leída se sirve de la caché local hasta que caduca.

De pago: cada fallo de caché hace una petición a Cost Explorer ($0,01, us-east-1, por página; consultado 2026-10-13), y refresh=True siempre la hace. Un acierto de caché es gratuito.

estimate() -> CostEstimate

La estimación por sandbox: el tiempo transcurrido desde el arranque de cada RUNNING por el precio de cómputo baseline de su tamaño. Siempre estimated: no incluye snapshots ni el consumo sobre el baseline.

Gratuita: sólo lee Lambda MicroVMs (ListMicrovms y GetMicrovmImageVersion) y nunca usa Cost Explorer.

AsyncUsageCost(*, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ManagementPlane | None = None, source: CostSource | None = None, catalog: SizeCatalog = DEFAULT_SIZE_CATALOG, cache_ttl_seconds: float = COST_CACHE_TTL_SECONDS, clock: Callable[[], datetime] = utc_now, monotonic: Callable[[], float] = time.monotonic)

El mismo contrato y el mismo coste que UsageCost.

Coste y activación

Activa: una llamada explícita a billed(); construir AsyncUsageCost no llama a AWS, y estimate() no usa Cost Explorer. Recursos y llamadas AWS: sólo lecturas. billed() hace ce:GetCostAndUsage (una petición por página) en us-east-1; estimate() hace ListMicrovms y GetMicrovmImageVersion. Coste aproximado: billed() es de pago, $0,01 por petición a Cost Explorer (us-east-1, consultado 2026-10-13, https://aws.amazon.com/ aws-cost-management/aws-cost-explorer/pricing/). La caché está en memoria, es de cada instancia y se pierde con el proceso, guarda como mucho COST_CACHE_MAX_ENTRIES ventanas y se indexa por inicio, fin, granularidad y región: reutiliza una sola instancia (unas 30 peticiones al mes ≈ $0,30); con una instancia nueva por llamada cada billed() paga $0,01. estimate() es gratuita. IAM: ce:GetCostAndUsage para billed(); lambda:ListMicrovms y lambda:GetMicrovmImageVersion para estimate(). Cómo apagarla: no llames a billed(); no deja recursos que borrar. Ejemplo: cost = await AsyncUsageCost().billed() print(cost.total_usd, cost.partial)

billed(*, start: date | str | None = None, end: date | str | None = None, granularity: Granularity = 'MONTHLY', region: str | None = None, refresh: bool = False) -> BilledCost async

De pago: una petición a Cost Explorer ($0,01, us-east-1, por página) en cada fallo de caché; refresh=True siempre paga. Ver UsageCost.billed.

estimate() -> CostEstimate async

Gratuita: no usa Cost Explorer. Ver UsageCost.estimate.

BilledCost(start: date, end: date, region: str | None, lines: tuple[BilledLine, ...], total_usd: float, partial: bool, requests: int, fetched_at: datetime, label: Literal['billed'] = BILLED_LABEL) dataclass

Coste facturado de MicroVMs según Cost Explorer, por usage type. partial es True si algún periodo aún es estimado por AWS (retraso de unas 24 h, AWS_API_NOTES.md §12); requests son las peticiones de pago que costó; fetched_at cuándo se leyó (la caché lo conserva).

BilledLine(usage_type: str, component: str, quantity: float, unit: str, cost_usd: float) dataclass

CostEstimate(items: tuple[SandboxCostEstimate, ...], total_usd: float, pricing_region: str = PRICING_REGION, basis: str = ESTIMATE_BASIS, label: Literal['estimated'] = ESTIMATED_LABEL) dataclass

La estimación por sandbox y su total, siempre rotulada estimated.

SandboxCostEstimate(sandbox_id: str, state: str, memory_mib: int | None, active_seconds: float | None, cost_usd: float | None, label: Literal['estimated'] = ESTIMATED_LABEL) dataclass

Coste estimado de un sandbox. Sólo los RUNNING con memoria conocida llevan importe: de uno suspendido no se conoce el tiempo activo.

Fleet(*, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ManagementPlane | None = None, images: ImageCatalog | None = None, events: EventsHistory | None = None, allow_actions: bool = False, catalog: SizeCatalog = DEFAULT_SIZE_CATALOG, monotonic: Callable[[], float] = time.monotonic, sleep: Callable[[float], None] = time.sleep)

Inventario, imágenes, eventos y acciones sobre los MicroVMs de la cuenta.

Coste y activación

Activa: llamar a sus métodos; construir Fleet no llama a AWS. Las acciones masivas exigen además allow_actions=True. Recursos y llamadas AWS: lecturas (ListMicrovms, ListMicrovmImages, ListMicrovmImageVersions, GetMicrovmImageVersion) y, con allow_actions=True, TerminateMicrovm, SuspendMicrovm y ResumeMicrovm sobre los sandboxes que se indiquen; events() lee la tabla de la pila events-webhooks si se pasó events=. Coste aproximado: $0 las lecturas de sandboxes, imágenes y versiones. events() es de pago, aunque mínimo: lee la tabla DynamoDB de events-webhooks (lecturas bajo demanda, https://aws.amazon.com/ dynamodb/pricing/on-demand/), unas pocas por consulta y sin sondeo. Las acciones (sólo con allow_actions=True) no facturan por la llamada, pero cambian lo que se factura (us-east-1, AWS_API_NOTES.md §12, consultado 2026-10-13): kill deja de facturar cómputo; suspend escribe un snapshot ($0,0038/GB) y, mientras dure, paga almacenamiento ($0,08/GB-mes); resume lee el snapshot ($0,00155/GB) y vuelve a facturar cómputo al precio de su tamaño (vCPU-s y GB-s de §12, p. ej. $0,1261/h con 2 GB y 1 vCPU), así que reanudar muchos sandboxes a la vez sube la factura. IAM: lambda:ListMicrovms, lambda:ListMicrovmImages, lambda:ListMicrovmImageVersions, lambda:GetMicrovmImageVersion; con acciones, lambda:TerminateMicrovm, lambda:SuspendMicrovm y lambda:ResumeMicrovm. Cómo apagarla: no llames a sus métodos; las acciones están apagadas salvo allow_actions=True y no dejan recursos que borrar. events() no consulta nada sin events=. Ejemplo: fleet = Fleet(region="us-east-1") running = fleet.sandboxes(states={"RUNNING"})

sandboxes(*, states: Iterable[str] | None = None, image: str | None = None) -> list[SandboxView]

Los sandboxes con la memoria de su imagen. Sin states, los que no están terminados; image filtra por nombre o ARN de imagen.

Gratuita: ListMicrovms y GetMicrovmImageVersion no facturan.

images(*, name_filter: str | None = None) -> list[ImageSummary]

Las imágenes de la cuenta. Gratuita: ListMicrovmImages no factura.

image_versions(image: str) -> list[ImageVersionSummary]

Las versiones de image (nombre o ARN), la más nueva primero.

Gratuita: ListMicrovmImageVersions no factura.

events(*, sandbox_id: str | None = None, types: Sequence[str] | None = None, limit: int = DEFAULT_GET_EVENTS_LIMIT, order: str = 'desc') -> list[EventRecord]

El historial de eventos de ciclo de vida.

De pago, aunque mínimo: lee la tabla DynamoDB de events-webhooks (lecturas bajo demanda, AWS_API_NOTES.md §25; un puñado por consulta, sin sondeo). Sin events= no llama a nada y falla.

plan_bulk(action: BulkAction, count: int, *, applied_rate: float | None = None) -> BulkPlan

Cuánto tardaría una acción masiva; applied_rate es la cuota de TPS aplicada (QuotaReport.applied_rate).

Gratuita: es un cálculo local, no llama a AWS.

run_bulk(action: BulkAction, sandbox_ids: Iterable[str], *, applied_rate: float | None = None, on_progress: BulkProgressCallback | None = None) -> BulkResult

Aplica action a cada sandbox, de uno en uno y al ritmo de la cuota de TPS de su operación (suspender, 2 TPS). Un fallo de un sandbox no detiene a los demás: queda failed con el código de AWS. Un id repetido se aplica una sola vez. on_progress se llama tras cada sandbox, desde el hilo que ejecuta la cola.

Exige allow_actions=True. La llamada no factura, pero el efecto sí (AWS_API_NOTES.md §12, us-east-1): kill deja de facturar cómputo; suspend escribe un snapshot ($0,0038/GB) y paga almacenamiento ($0,08/GB-mes) mientras esté suspendido; resume lee el snapshot ($0,00155/GB) y vuelve a facturar cómputo al precio de su tamaño.

AsyncFleet(*, region: str | None = None, session: boto3.session.Session | None = None, control_plane: ManagementPlane | None = None, images: ImageCatalog | None = None, events: EventsHistory | None = None, allow_actions: bool = False, catalog: SizeCatalog = DEFAULT_SIZE_CATALOG, monotonic: Callable[[], float] = time.monotonic, sleep: Callable[[float], None] = time.sleep)

El mismo contrato y el mismo coste que Fleet.

Coste y activación

Activa: llamar a sus métodos; construir AsyncFleet no llama a AWS. Las acciones masivas exigen además allow_actions=True. Recursos y llamadas AWS: lecturas (ListMicrovms, ListMicrovmImages, ListMicrovmImageVersions, GetMicrovmImageVersion) y, con allow_actions=True, TerminateMicrovm, SuspendMicrovm y ResumeMicrovm; events() lee la tabla DynamoDB de la pila events-webhooks si se pasó events=. Coste aproximado: $0 las lecturas de sandboxes, imágenes y versiones; events() es de pago, aunque mínimo (lecturas bajo demanda de DynamoDB, sin sondeo). Las acciones no facturan por la llamada, pero cambian la factura (AWS_API_NOTES.md §12, us-east-1, consultado 2026-10-13): kill deja de facturar cómputo; suspend escribe un snapshot ($0,0038/GB) y paga almacenamiento ($0,08/GB-mes); resume lee el snapshot ($0,00155/GB) y vuelve a facturar cómputo al precio de su tamaño. IAM: las de lectura de Fleet; con acciones, lambda:TerminateMicrovm, lambda:SuspendMicrovm y lambda:ResumeMicrovm. Cómo apagarla: no llames a sus métodos; las acciones están apagadas salvo allow_actions=True. Ejemplo: fleet = AsyncFleet(region="us-east-1") running = await fleet.sandboxes(states={"RUNNING"})

sandboxes(*, states: Iterable[str] | None = None, image: str | None = None) -> list[SandboxView] async

Gratuita. Ver Fleet.sandboxes.

images(*, name_filter: str | None = None) -> list[ImageSummary] async

Gratuita. Ver Fleet.images.

image_versions(image: str) -> list[ImageVersionSummary] async

Gratuita. Ver Fleet.image_versions.

events(*, sandbox_id: str | None = None, types: Sequence[str] | None = None, limit: int = DEFAULT_GET_EVENTS_LIMIT, order: str = 'desc') -> list[EventRecord] async

De pago, aunque mínimo: lecturas de DynamoDB. Ver Fleet.events.

plan_bulk(action: BulkAction, count: int, *, applied_rate: float | None = None) -> BulkPlan

Gratuita: cálculo local. Ver Fleet.plan_bulk.

run_bulk(action: BulkAction, sandbox_ids: Iterable[str], *, applied_rate: float | None = None, on_progress: BulkProgressCallback | None = None) -> BulkResult async

Exige allow_actions=True; el efecto factura según la acción (resume vuelve a facturar cómputo). Ver Fleet.run_bulk.

SandboxView(sandbox_id: str, state: str, template: str, template_name: str, template_version: str, started_at: datetime, memory_mib: int | None) dataclass

Un sandbox del inventario con la memoria de su imagen (None si no se pudo leer). Sin envs, runHookPayload ni tokens: no existen en el modelo.

ImageSummary(name: str, image_arn: str, state: str, latest_active_version: str | None, latest_failed_version: str | None, created_at: datetime) dataclass

ImageVersionSummary(image_version: str, state: str, status: str, base_image_version: str | None, artifact: str | None, memory_mib: int | None, created_at: datetime) dataclass

Una versión de imagen; artifact es sólo el nombre del fichero del artefacto (nunca el bucket ni la ruta) y memory_mib sólo si la lista lo trae.

BulkPlan(action: BulkAction, operation: str, count: int, tps: float, estimated_seconds: float) dataclass

Lo que tardará una acción masiva con la cuota de TPS de su operación.

BulkProgress(done: int, total: int, last: BulkItemResult) dataclass

BulkItemResult(sandbox_id: str, outcome: BulkOutcome, code: str | None = None) dataclass

El resultado de una acción sobre un sandbox. code es el código de AWS de un fallo, nunca su mensaje.

BulkResult(plan: BulkPlan, items: tuple[BulkItemResult, ...] = tuple()) dataclass

CDK constructs (rayito.cdk)

Since 0.15.0, behind the cdk extra (pip install "rayito[cdk]": aws-cdk-lib 2.261.0 or later and constructs 10.5.0 or later). It is a separate module: import rayito does not load it, and without the extra import rayito.cdk fails with a message saying it is missing. The TypeScript equivalents are in the TypeScript reference, and usage, cost and errors in Deploy with CDK.

RayitoAgent(scope: Construct, id: str, *, project: str | os.PathLike[str], name: str | None = None, bedrock_models: Sequence[str] = (), readable_secrets: Sequence[str] = (), idle_session_timeout_seconds: int | None = None, max_session_lifetime_seconds: int | None = None, tags: Mapping[str, str] | None = None, build: AgentImageBuild | None = None, export_outputs: bool = False, external_model: bool = False)

Bases: Construct

Un agente de Rayito en Amazon Bedrock AgentCore Runtime, desplegado con cdk deploy (ADR-032).

project es el directorio (o el fichero de proyecto) del agente, igual que rayito agent deploy --target agentcore; las demás opciones son las de AgentCoreOptions (external_model=True admite un modelo fuera de Bedrock sin dar al rol permisos de Bedrock que no pidas). Valida con el plan de AgentCore del SDK (los errores salen como AgentCoreException) y sintetiza: la pila anidada Build con infra/agentcore-runtime.yaml (repositorio de ECR, proyecto de CodeBuild, rol de build y rol del runtime, con la misma IAM que la CLI), el asset Source con el contexto de build, el recurso ImageBuild que construye la imagen si su etiqueta no existe, el runtime (AWS::BedrockAgentCore::Runtime) y el recurso Mmds que le exige MMDSv2. La región del stack tiene que ser concreta (region_unresolved); la cuenta puede ser un token.

La CLI y los SDK leen e invocan el agente (rayito agent list, status, invoke, AgentCoreFleet.connect) pero rayito agent deploy y delete lo rechazan con name_conflict: se cambia con cdk deploy y se borra con cdk destroy.

Coste y activación

Activa: sólo cdk deploy de un stack que contiene el constructo; sintetizar no llama a AWS ni cuesta nada. Recursos y llamadas AWS: la pila anidada (repositorio de ECR que conserva 5 imágenes, proyecto de CodeBuild, grupo de logs de 14 días y dos roles), el zip del contexto en el bucket del bootstrap (queda hasta cdk gc), dos funciones Lambda y la máquina de estados del Provider (con sus grupos de logs de 14 días), un build de CodeBuild cuando el contenido cambia y el runtime de AgentCore. Coste aproximado: minutos de CodeBuild arm1.small por build, céntimos al mes de ECR y logs, céntimos por despliegue de Lambda y Step Functions, y AgentCore por uso (vCPU-hora y GB-hora por sesión). Trazas, VPC y entrega de logs quedan apagadas. IAM: la ejecución de CloudFormation crea roles (CAPABILITY_IAM); el manejador sólo puede arrancar y leer builds de su proyecto, describir imágenes de su repositorio, leer y actualizar su runtime y pasar el rol del runtime. Cómo apagarla: quitar el constructo del stack o cdk destroy. Ejemplo: agent = RayitoAgent( stack, "Agent", project="./agent", bedrock_models=["amazon.nova-lite-v1:0"] ) agent.grant_invoke(my_function)

grant_invoke(grantee: iam.IGrantable) -> iam.Grant

bedrock-agentcore:InvokeAgentRuntime sobre el runtime y sus endpoints, para grantee (una función, un rol...).

AgentImageBuild(kind: BuildKind, timeout_minutes: int)

Cómo se construye la imagen linux/arm64 del agente.

code_build() (por defecto) la construye en AWS con el proyecto de CodeBuild de infra/agentcore-runtime.yaml, igual que la CLI, sin Docker local. local_docker() la construye cdk deploy con el Docker (o Finch) de la máquina, con soporte arm64, y la sube al repositorio del bootstrap de CDK.

Coste y activación

Activa: nada por sí mismo; lo usa RayitoAgent(build=...). Recursos y llamadas AWS: code_build hace un build de CodeBuild en cada cdk deploy cuyo contenido cambió; local_docker ninguno en AWS al construir (sube la imagen a ECR). Coste aproximado: code_build, minutos de CodeBuild arm1.small; local_docker, nada en CodeBuild. IAM: ver RayitoAgent. Cómo apagarla: no aplica (es una opción del constructo). Ejemplo: RayitoAgent(stack, "Agent", project="./agent", build=AgentImageBuild.local_docker())

code_build(*, timeout_minutes: int | None = None) -> AgentImageBuild staticmethod

El build en CodeBuild (por defecto). timeout_minutes (5-110, por defecto 30) es el plazo del build; el recurso se rinde 10 minutos después.

local_docker() -> AgentImageBuild staticmethod

El build con el Docker local (DockerImageAsset, linux/arm64).

RayitoMicrovmImage(scope: Construct, id: str, *, name: str, artifact: MicrovmImageArtifact, base_image_version: str, variant: str = DEFAULT_VARIANT, with_efs: bool = False, os_capabilities: bool = False, memory_mib: int | None = None, sizes: Sequence[str] = (), allowed_mount_buckets: Sequence[str] = (), environment: Mapping[str, str] | None = None, tags: Mapping[str, str] | None = None)

Bases: Construct

Una imagen de Lambda MicroVMs con rayd, publicada con cdk deploy (ADR-032 D9): la misma configuración que rayito image publish (hooks, memoria, arquitectura, capacidades, logs y variables), para la imagen name y una más por cada tamaño de sizes (<name>-<size>).

Sintetiza el asset Artifact con el zip (uno para todos los tamaños), el rol BuildRole (lo asume lambda.amazonaws.com; sólo lee ese objeto y escribe en los grupos de logs de sus imágenes) y, por imagen, un grupo de logs /rayito/<nombre> de 14 días y un AWS::Lambda::MicrovmImage. Valida sin AWS: región concreta, nombre (los rayito-base* son de rayito setup), base_image_version (obligatoria; latest no), la variante y el marcador de EFS del zip, y los tamaños.

Los sandboxes se crean con el SDK de siempre: Sandbox.create(template="<name>"). rayito image publish rechaza una imagen de CDK; rayito image prune puede borrar sus versiones antiguas.

Coste y activación

Activa: sólo cdk deploy de un stack que contiene el constructo; sintetizar no llama a AWS ni cuesta nada. Recursos y llamadas AWS: el zip en el bucket del bootstrap (queda hasta cdk gc), un rol de IAM, un grupo de logs y una imagen por tamaño; cada cambio de configuración o de artefacto construye una versión nueva de cada imagen. Coste aproximado: el build de cada versión y su almacenamiento de snapshot (≈ $0,04 por semana y versión), más los logs del build. Los MicroVMs se cobran al usarlos, como siempre. IAM: la ejecución de CloudFormation necesita lambda:CreateMicrovmImage, lambda:PassNetworkConnector sobre el conector gestionado INTERNET_EGRESS e iam:PassRole del rol de build (el rol de ejecución por defecto de CDK los tiene). Cómo apagarla: quitar el constructo del stack o cdk destroy (borra las imágenes); rayito image prune borra versiones antiguas. Ejemplo: image = RayitoMicrovmImage( stack, "Image", name="team-sandbox", artifact=MicrovmImageArtifact.download_release(), base_image_version="1", )

MicrovmImageArtifact(path: Path, sha256: str)

El zip de la imagen (rayito-image.zip, el de make image-zip o el de una release de Rayito) que RayitoMicrovmImage publica, comprobado en local antes de sintetizar.

Coste y activación

Activa: nada por sí mismo; download_release descarga por HTTPS dos ficheros públicos de GitHub (sin AWS) sólo si no están ya en su directorio. Recursos y llamadas AWS: ninguno. Coste aproximado: ninguno. IAM: ninguna. Cómo apagarla: usar from_file con un zip propio. Ejemplo: artifact = MicrovmImageArtifact.download_release()

from_file(path: str | os.PathLike[str], *, sha256: str | None = None) -> MicrovmImageArtifact staticmethod

Un zip local: artifact_not_found si no existe y artifact_checksum_mismatch si sha256 no es el suyo.

download_release(*, version: str | None = None, directory: str | os.PathLike[str] | None = None) -> MicrovmImageArtifact staticmethod

rayito-image.zip de la release rayd-v<version> (por defecto la del SDK, como rayito setup), verificado contra su SHA256SUMS. Se guarda con SHA256SUMS en directory (por defecto .rayito/releases/<release>/) y la siguiente síntesis lo reutiliza sin red, verificándolo otra vez. Se llama antes de construir el stack.

RayitoCdkException(code: CdkErrorCode, **fields: object)

Bases: Exception

Un constructo de rayito.cdk no se puede sintetizar con lo que se le pasó: la región del stack es un token, el nombre de la imagen no vale o está reservado, el artefacto no existe o no casa, o una opción no vale. Se lanza en el constructor, antes de añadir ningún recurso. code es una lista cerrada y fields los valores que interpola el mensaje.