Skip to content

Changelog

Each component is versioned separately, with the same MAJOR.MINOR series: the Python SDK, the TypeScript SDK and the rayd agent (the image). Which rayd version each SDK needs: Compatibility. The format follows Keep a Changelog and the versioning follows SemVer.

Unreleased

Corregido

  • rayito setup pone al día la pila de IAM de una versión anterior. Antes reutilizaba rayito-m0-iam si su ArtifactBucket coincidía, aunque se hubiera desplegado con un infra/iam.yaml más antiguo (en 0.17.0 la pila de 0.16 se quedaba sin los permisos de la receta de la flota). La plantilla lleva ahora el parámetro TemplateFingerprint (el sha256 de la plantilla empaquetada, el mismo en los dos SDK) y su salida; setup lo manda al crear o actualizar y planifica update si la pila no lo tiene o lleva otro, con el texto «actualizar a la plantilla infra/iam.yaml de este SDK» y previousBucket: null en --json cuando el bucket no cambia. Una pila desplegada a mano (sin el parámetro) se actualiza una vez; los demás parámetros conservan su valor (UsePreviousValue). Una pila que desplegó un Rayito más nuevo (su etiqueta rayito:sdk-version) nunca vuelve a la plantilla de este SDK: con el mismo bucket se reutiliza («… más nuevo que este SDK: se conserva», stackSdkVersion en --json) y, si el plan le cambia el bucket, falla con el error nuevo stack_newer (salida 2). Al actualizar la pila, setup vuelve a mandar las etiquetas que ya tenía, con las de --tag por encima, porque UpdateStack sustituye la lista entera. StackStatus gana tags (las de DescribeStacks). Una pila creada con la plantilla de 0.6.1 o anterior se actualiza sin revertirse: la plantilla gana el parámetro LegacyCallerPolicy, que conserva la descripción original de CallerPolicy (IAM no la cambia y CloudFormation sustituía la política, que chocaba con su propio nombre fijo y dejaba la pila en UPDATE_ROLLBACK_COMPLETE), y setup lo pone a true en una pila sin la salida SandboxLauncherPolicyArn.
  • Un --token-file que dejó un rayito agent deploy fallido ya no bloquea el reintento. El fichero se escribe antes del build; si el despliegue falla antes de que exista la flota (build, arranque del host, una confirmación rechazada o cualquier otro error), se borra el fichero que creó esa misma ejecución. Con --keep-on-failure se conserva, porque es el único acceso al sandbox que se queda, y un fichero de una ejecución anterior nunca se toca.
  • rayito setup --json informa la versión de cada imagen publicada en su paso. Tras apply, los pasos baseImage y capsImage con publish llevan en imageVersion la versión recién publicada (antes null, aunque result sí la tenía). En --dry-run y si la preparación falla, siguen en null.
  • El DEBUG del SDK de AWS para Python ya no escribe parámetros ni cuerpos de AWS. Con el logger raíz a DEBUG o boto3.set_stream_logger(), botocore, boto3 y s3transfer escribían los parámetros y el cuerpo de cada petición y respuesta: el valor de PutSecretValue/GetSecretValue, el token de runHookPayload, el entorno de CreateMicrovm o el Item de un Table.put_item (la URL de un webhook de eventos de ciclo de vida y el nombre de su secreto). import rayito descarta ahora los registros por debajo de INFO de botocore.endpoint, botocore.parsers, botocore.auth, botocore.regions, boto3.resources.action, boto3.resources.collection, boto3.dynamodb.table, s3transfer.tasks y s3transfer.futures, la lista medida con boto3 1.43.102 (los avisos y errores siguen saliendo, y el resto de loggers no cambia). El host del agente sube además botocore, boto3, s3transfer y urllib3 a WARNING, como httpx y httpcore en 0.17.0, y descarta ese mismo DEBUG aunque el código del agente vuelva a bajar el nivel.
  • Una flota dedicada de un solo VM vuelve a su sesión. Con sessions="dedicated" y max_vms=1, el id de sesión no lleva asa y la flota no recordaba la sesión que asignó el host: la segunda llamada con ese id esperaba un VM libre y acababa en CapacityException tras el plazo de la cola. Ahora cada sesión asignada por el host se recuerda con su VM dentro del proceso, como un id elegido por el llamante.
  • El plazo de los VMs sobrantes de una flota (las tres limitaciones conocidas de 0.17.0):
  • Una invocación que se suelta ya no acorta el plazo de otra que empezó entre medias en el mismo VM: los set_timeout de cada VM van en orden (un candado por VM) y el acortamiento sólo se manda si el VM sigue libre.
  • Una invocación que falla (un 429 hasta agotar la cola, un error del host o de transporte) devuelve el VM sobrante a scale_in_after_seconds; antes se quedaba con el plazo de invocación hasta request_timeout.
  • Un VM acortado que pasa a ser base recupera toda su vida aunque la flota no conozca su tope: pide el máximo de la plataforma y rayd, que lo rechaza informando su tope, recibe ese tope en el reintento. Antes no se mandaba nada y el VM base podía terminar por el plazo corto. Si no queda nada que devolverle, deja de contar como acortado. Vectores nuevos restoreSeconds en testdata/agent-fleet/lifetime.json.

Seguridad

  • opentelemetry-api y opentelemetry-sdk 1.45.1 en uv.lock (GHSA-xcq7-p3pf-p88h, afectaba a 1.45.0 y anteriores), y el extra otel pide ahora opentelemetry-api>=1.45.1,<2 (antes >=1.27), así que ya no resuelve a una versión afectada. Rayito sólo inyecta contexto y nunca lee un tracestate ajeno.

0.17.0 - 2026-10-23

Añadido

  • rayito setup --caps-image publica también rayito-base-caps, la base que usa rayito agent deploy en MicroVM y que antes había que publicar a mano. Es un cuarto paso del plan (capsImage) que sale del mismo rayito-image.zip (una sola descarga) con additionalOsCapabilities=["ALL"]; el plan y --dry-run muestran el paso y su coste (otra versión de imagen, ≈ $0,04 por semana, $0,08 por GB-mes). Va apagado por defecto porque cuesta: sólo --caps-image (SetupRequest.caps_image=True) lo añade; sin la opción el paso sale como skip y no se lee ni se publica esa imagen. Si rayito agent deploy en MicroVM no encuentra rayito-base-caps, el error pide rayito setup --caps-image. El documento de --json gana capsImageArn y capsImageVersion en result.
  • AgentProject.gateway_presets: pasarela -> preset, tal como lo declara [gateways.<nombre>] preset. Lo rellenan la lectura del fichero y la detección; no entra en el documento canónico del proyecto.
  • Receta de la flota (ADR-034): deploy_agent() y rayito agent deploy guardan, tras arrancar la flota de MicroVMs, lo que usaron (el proyecto tras las banderas, la versión de la imagen, los presets de las pasarelas, el escalado) en un parámetro SecureString estándar de Parameter Store (/rayito/agents/<nombre>/recipe), con nombres de secretos y nunca valores. AgentFleet.connect(nombre) sin project la lee y puede crear VMs iguales; use_recipe=False la ignora. Es la única función encendida por defecto (no cuesta en el nivel estándar): AgentDeployOptions(save_recipe=False) / --no-save-recipe la apagan. Un fallo al guardarla es un aviso (recipe_access_denied, recipe_not_saved, recipe_too_large), nunca un despliegue fallido. Nueva sentencia AgentRecipe: CallerPolicy la guarda, la lee y la borra; SandboxLauncherPolicy sólo la lee y la borra (escribirla equivale a poder desplegar) e ImagePublisherPolicy sólo la guarda (AgentRecipeWrite).
  • FleetStatus.scaling_source (explicit, project, recipe o default).
  • AgentFleet.stop(keep_recipe=...) y rayito agent stop --keep-recipe: por defecto stop() borra la receta que leyó o guardó.
  • AgentDeployOptions.scale_in_after_seconds y rayito agent deploy --scale-in-after SECONDS.
  • Renovación opcional de la clave de corta duración de Bedrock (renew_gateway_keys en AgentFleet, AgentFleet.connect, AsyncAgentFleet y AgentDeployOptions; --renew-gateway-keys), apagada por defecto, con el extra bedrock-keys (aws-bedrock-token-generator==1.1.0).
  • rayito agent invoke <flota> --renew-gateway-keys: la CLI pide la renovación al reconectar la flota por su nombre (la receta no la activa). Con el id de un sandbox sale con 2, y con --target agentcore, option_not_supported.
  • AgentDeployment.refresh_gateways().

Cambiado

  • Incompatible con 0.16: rayito agent deploy --env rechaza un nombre que parece un secreto, también *_SECRET y SECRET, los que tienen un segmento PASS, PASSWD, PAT, CREDENTIAL o CREDENTIALS, y los que llevan AUTH o BEARER como último segmento o junto a TOKEN, KEY o HEADER (AUTH, X_AUTH, AUTH_HEADER; sin distinguir mayúsculas; antes pasaban). AUTH_MODE, AUTH_URL o AUTHOR_NAME siguen pasando. Se rechaza antes de cualquier llamada a AWS y con los dos destinos. Migración: pasa el valor por --secret-env NOMBRE=<secreto> o --secret-env-from <secreto>. La regla es sólo de la bandera: los valores de env del proyecto ([env] de rayito-agent.toml o la biblioteca) se guardan tal cual en la receta, así que no pongas en env nada secreto.
  • rayito setup --caps-image publica rayito-base-caps: una versión de imagen más que factura almacenamiento de snapshot (≈ $0,04 por semana). Apagado por defecto (ADR-034 D9).

Corregido

  • Un pool sólo recupera las plazas de su imagen: SandboxPool/AsyncSandboxPool adoptaba al arrancar todas las plazas del backend. Con el fichero de estado por defecto de rayito agent deploy --warm (.rayito/agent-pool.json), desplegar una segunda flota desde el mismo directorio reciclaba (terminaba) las plazas aparcadas de la primera, o las contaba como suyas. Ahora una plaza de otra imagen no se adopta, recicla, termina ni entrega, y se queda en el backend para el pool de su imagen. Visto en la regresión en AWS real.
  • La reducción de la flota (scale_in_after_seconds) por fin ocurre: desde 0.14 los VMs se creaban sin max_lifetime/on_timeout, así que rayd rechazaba el set_timeout que termina un VM sobrante y la flota sólo avisaba («no se pudo acotar la vida de un VM sobrante»). Una flota que puede crecer (max_vms > min_vms) crea ahora todos sus VMs y plazas calientes con on_timeout="kill" y `max_lifetime = serve.timeout_seconds
  • 60; antes de cada invocación el plazo de un VM sobrante sube al de la petición y, al soltar la última invocación en curso, vuelve ascale_in_after_seconds(mientras quede otra no se acorta), siempre por debajo del tope que imponerayd(max_lifetime - 60 sdesde el arranque) menos 5 s. Siraydrechaza un plazo por pasar de su tope, la flota adopta el tope que informa y reintenta una vez, sin marcar el VM. Una flota de tamaño fijo se crea byte a byte como en 0.16; un VM creado sin plazo lógico avisa una vez y no se reintenta.PoolConfigganamax_lifetimeyon_timeoutopcionales (sólokill). Vectores entestdata/agent-fleet/lifetime.json`.

  • El host del agente ya no escribe URLs con credenciales en sus logs: los loggers httpx y httpcore, que a nivel INFO escriben la URL de cada petición con su consulta, arrancan en WARNING en serve (QUIET_HTTP_LOGGERS). El código del agente puede volver a bajarlos. Ver Seguridad.

  • El log de peticiones del host del agente ya no lanza AttributeError tras responder 400 a una línea de petición mal formada; las invocaciones siguientes nunca se vieron afectadas. Ese 400 lleva ahora su línea de estado también en Python < 3.14, y un cliente que cuelga a media respuesta deja una línea http_error code=connection_closed en vez de una traza en stderr.
  • --create-secret busca la clave por el preset de la pasarela: una pasarela [gateways.model] preset = "bedrock" lee AWS_BEARER_TOKEN_BEDROCK en vez de buscar una variable por el nombre model. Con preset = "custom" sigue mirando el nombre de la pasarela, como en 0.16.
  • --create-secret ya no duplica el prefijo rayito/: la detección escribía el secreto de la pasarela como rayito/agents/<proyecto>/<proveedor> y, como la pasarela lo lee bajo rayito/, el secreto acababa en rayito/rayito/agents/.... Ahora escribe agents/<proyecto>/<proveedor> y el secreto queda en rayito/agents/<proyecto>/<proveedor>, el nombre que lee la pasarela. Si sólo existe el secreto de 0.16, el despliegue lo reutiliza y no crea otro; un rayito-agent.toml con un nombre propio no cambia.
  • OpenCode sin fichero de proyecto usa AGENTS.md y los permisos de opencode.json (ADR-034 D8). Antes sólo rayito-agent.toml los aplicaba. Ahora la detección lleva el AGENTS.md de la raíz a agent.instructions_file, y el bloque permission de opencode.json u opencode.jsonc (sin comentarios ni comas finales) a agent.permissions. ask se aplica como deny, porque la ejecución es desatendida, y una clave que no se puede aplicar se ignora. Los dos casos avisan con opencode_permission_ignored. Lo que dice rayito-agent.toml gana, y el informe sólo lleva la evidencia (file:AGENTS.md, config:permission), nunca el texto de los ficheros.
  • Una flota reconectada en otro proceso abría una plaza caliente sin las pasarelas del proyecto (el agente arrancaba sin la pasarela del modelo): la flota las abre ahora con el mismo camino que SandboxPool.take(gateways=) y sustituye la plaza si no puede.
  • rayito agent status <flota> sin proyecto mostraba un escalado inventado (min 1, max 1); ahora imprime el de la receta o escalado desconocido: sin proyecto ni receta.
  • AgentFleet.connect(nombre) sin project ya no falla si Parameter Store no responde (sin endpoint de VPC para SSM, limitación de tasa, un error de KMS): es el aviso recipe_unreadable y la flota de 0.16, y rayito agent stop <nombre> termina sus VMs.
  • La receta ya no activa la renovación de claves de Bedrock: la clave se firma con las credenciales de quien conecta, así que AgentFleet.connect(nombre) sólo renueva con renew_gateway_keys=True explícito. Si la receta lo pedía y no lo pasas, la flota no renueva y avisa recipe_renewal_ignored (sólo el código). Listar, consultar o terminar la flota nunca necesita el extra bedrock-keys: el renovador se carga en la primera renovación, y las renovaciones simultáneas se serializan con un candado (los VMs que arrancan en paralelo escriben una sola clave).
  • renew_gateway_keys no renueva un secreto compartido: si el secreto de la pasarela bedrock lo lee también una pasarela cuyo upstream no es Bedrock, la opción es InvalidArgumentException (nombra las pasarelas, nunca el secreto). Sólo cuenta como Bedrock un host bedrock-runtime.<región>.amazonaws.com con una región de AWS válida; un host como bedrock-runtime.evil.s3.amazonaws.com ya no se toma por Bedrock.
  • Un nombre de flota ., .. o vacío ya no da una ruta de receta (recipe_not_saved).
  • rayito setup informa artifactUploaded también cuando sólo publica rayito-base-caps (una cuenta que viene de 0.16).
  • La ayuda de --scale-in-after sale de la constante del SDK y dice que, sin la bandera, manda el [scaling] del proyecto.
  • La ayuda de rayito agent deploy ya no pierde los corchetes: Typer la pinta con el marcado de rich, que se comía [scaling] en --scale-in-after y [#clave] en --secret-env (éste, desde 0.16) como si fueran estilos.
  • rayito agent deploy con --min-vms 0 desplegaba, guardaba la receta y terminaba con IndexError al imprimir el resultado: una flota sin VMs con servicio no imprime sandbox_id ni url y --json los da como null.

Pruebas

  • Los e2e de la receta (test_fleet_recipe.py, fleet-recipe.e2e.test.ts) ya no inventan ids de sesión: el host sólo acepta los suyos y respondía invalid_session_id.

0.16.0 - 2026-10-22

Añadido

  • El id del modelo es opcional cuando no es Bedrock (ADR-033 D2): un grafo que elige su modelo por configuración (ChatOpenAI(model=settings.llm_model, base_url=settings.base_url)) se detecta y se despliega sin escribir el id. [model] acepta los nombres de variable id_env, base_url_env y api_key_env (nunca valores), y en MicroVMs Rayito los apunta a la pasarela con el marcador como clave. La detección ve base_url= en el constructor del cliente (openai pasa a openai-compatible) y los nombres de variable del modelo, y avisa con model_id_from_code. El id sigue siendo obligatorio con las pasarelas bedrock y gemini y con los runtimes que construyen el modelo; con un entrypoint, ctx.model es None y la telemetría omite gen_ai.request.model.
  • --model-id, --model-provider y --model-base-url en rayito agent detect y deploy (DetectOverrides.model_id, model_provider, model_base_url).
  • --external-model en AgentCore (AgentCoreOptions.external_model, prop external_model de RayitoAgent): admite un modelo fuera de Bedrock sin dar al rol permisos de Bedrock que no pidas; la URL base va al entorno del runtime y la clave nunca. El plan lo dice con la línea externalModel.
  • Entrada estructurada y selección de grafo (ADR-033 D3): un grafo de LangGraph cuyo esquema de entrada no es de mensajes (por ejemplo, InputState con ticket_id) se invoca con invoke(input={...}) y stream(input={...}) en AgentDeployment, AgentFleet, AgentCoreFleet y sus gemelos asíncronos (prompt pasa a ser opcional; exactamente uno de los dos), y devuelve AgentResult.output y Done.output. El host lee el esquema con get_input_jsonschema(), comprueba el input con la biblioteca estándar y pasa la sesión como thread_id; un grafo de mensajes sigue igual. Fallos protocol_error con input_required, invalid_input u output_not_serializable, con su propio mensaje, y InvalidArgumentException con una pista de redespliegue si el host desplegado es anterior. Con varios grafos en langgraph.json, --graph, [runtime] graph y DetectOverrides.graph eligen uno; sin elegir y sin uno llamado agent, el error lista los nombres. rayito agent invoke acepta --input y --input-file (- lee stdin) en lugar de PROMPT. Límite nuevo AGENT_SERVICE_MAX_OUTPUT_BYTES.
  • Secretos de Secrets Manager como variables de entorno del agente (ADR-033 D4): [secret_env] (NOMBRE = "<secreto>" o { secret, key, version_stage }) y [[secret_env_from]] (secret, prefix, keys) en el fichero de proyecto, AgentProject(secret_env=..., secret_env_from=...) con SecretEnvRef y SecretEnvSource, y --secret-env NOMBRE=<secreto>[#clave] y --secret-env-from <secreto>[,prefix=P][,keys=A|B] en rayito agent deploy. En AgentCore el runtime sólo recibe RAYITO_SECRET_ENV (nombres y ARNs), el host lee cada secreto con el rol al arrancar la sesión, antes de cargar el código, y falla en cerrado con secret_env_*; los secretos se suman a ReadableSecretArns (la plantilla no cambia) y el plan lo dice con la línea secretEnv y secretEnvNames. En MicroVMs el SDK los lee con tus credenciales y los pasa sólo al proceso del host. Sin estas claves, el plan, el export, la imagen y las llamadas no cambian.
  • --create-secrets-from FICHERO.env (AgentDeployOptions.create_secrets_from) crea o actualiza el secreto JSON rayito/agents/<nombre>/env con los valores de un .env y lo usa como secret_env_from; enseña sólo los nombres y pide --yes (cuesta $0,40 al mes). --check-env (check_env) avisa (env_missing) de las variables de .env.example que no reciben valor.
  • Protocolo langgraph-api (experimental) para servir un langgraph.json con langgraph dev: HttpApp(protocol="langgraph-api", assistant_id=...) (y [runtime.app] protocol = "langgraph-api" con assistant_id) hace que el host traduzca cada invocación a POST /threads/{thread_id}/runs/wait del LangGraph Platform API, con el hilo derivado de la sesión (la misma sesión sigue el mismo hilo), salud en /ok, el texto de response_field o del último mensaje y el uso de su usage_metadata. Un interrupt del grafo es AgentException(protocol_error) con detail_code graph_interrupted. rayito agent detect y deploy con --protocol langgraph-api sirven así un proyecto con langgraph.json (kind = "custom", el launcher de langgraph dev y el grafo elegido como assistant_id), y sin la opción la detección no cambia. langgraph dev es un servidor de desarrollo en memoria; Rayito no instala langgraph-cli[inmem] (el paquete langgraph-api usa la licencia Elastic-2.0). Sin streaming ni langgraph up en esta versión. HttpApp.served_health_path y served_invoke_path dicen las rutas a las que llama el host. Con invoke(input={...}) el objeto llega tal cual como entrada del grafo y, si el estado final no tiene mensajes, vuelve como AgentResult.output; --graph elige el assistant_id.

Cambiado

  • AgentModel.id pasa de str a str | None, y con él AgentSpec.effective_small_model: sólo cambia el tipo que se lee, ninguna llamada existente. Un proyecto con id se comporta igual, pero mypy avisa donde el código lo usa como str; estréchalo (if spec.model.id is not None).
  • model_required se lanza por fin: AgentCore con un proveedor bedrock sin [model] id ni --bedrock-model (antes reservado); su texto dice «fija [model] id o pásalo con --bedrock-model».
  • Textos de error (los códigos, las rutas y el código de salida no cambian): model_required ya no lleva la pista «(bedrock_models en el SDK)», y un langgraph.json con varios grafos y ninguno llamado agent da el texto de ambiguous_graph (los nombres de los grafos y --graph) en vez del de ambiguous_entrypoint, con el mismo código ambiguous en runtime.entrypoint.
  • La detección sólo convierte openai en openai-compatible por un base_url propio en el constructor del cliente; con la URL oficial de OpenAI, o si el código fija el id del modelo con un literal y no pasas --model-base-url, el proyecto se detecta como en 0.15 (en el segundo caso, con el aviso custom_base_url).
  • Un prompt a un grafo con esquema de entrada propio sin claves required sigue el camino de mensajes, como en 0.15; input_required queda para los grafos que declaran claves obligatorias.

  • Un 400 a una petición con input se explica con el cuerpo del host: el invalid_request de un host anterior pide volver a desplegar; el de un host con entrada estructurada (que añade "accepts_input": true) es una petición que no acepta; cualquier otro cuerpo da un mensaje que sólo sugiere redesplegar. Con input, el SDK mide el cuerpo entero de la petición contra AGENT_SERVICE_MAX_REQUEST_BYTES, no sólo el input; con un prompt no cambia nada.

  • rayito agent invoke sale con el código de uso (2) por un InvalidArgumentException sólo con --input/--input-file; con PROMPT sigue saliendo con 1, como en 0.15 (la CLI de TypeScript ya salía con 2).
  • AgentProject(...) con un runtime que construye el modelo (opencode, hermes, deepagents o strands sin entrypoint) y sin AgentModel.id falla al construirse, antes de cualquier llamada o de crear el secreto de --create-secrets-from.
  • Con langgraph-api, un estado final que es output ya no se repite como texto, el JSON del estado sin mensajes es estricto (invalid_response con un NaN) y health_path = "/ok" (el de la detección) ya no da la pista de rutas ocultas. Un grafo con esquema propio acepta en integer un número sin parte fraccionaria (3.0), y su output serializa modelos de pydantic (model_dump(mode="json")) y dataclasses.

Seguridad

  • Con --external-model el agente llama a un modelo fuera de Bedrock por Internet con una clave que obtiene su código; la clave nunca va a las variables de entorno del runtime (SECURITY.md, T36).
  • T38: los valores de secret_env nunca van al proyecto, al plan, a la imagen, a los logs ni a los errores; el rol del runtime sólo lee esos secretos (GetSecretValue, sin kms:Decrypt), y un test falla si se amplía. El agente y lo que lance pueden leer su entorno: la guía, security.md y SECURITY.md lo dicen.
  • El fichero de --create-secrets-from se excluye del código si está dentro, así que sus valores nunca llegan a la imagen ni a la plantilla; un nombre reservado en él es un error que nombra la variable, nunca su valor. Un --secret-env sin = o con un nombre que no es de variable da el error en secret_env, sin repetir lo escrito. En MicroVMs el host recibe también RAYITO_SECRET_ENV_NAMES (sólo nombres) y redacta los valores de secret_env en el diagnóstico de arranque igual que en AgentCore.
  • El host lee los valores de secret_env por una tubería propia: la herramienta del venv de botocore los escribe en un descriptor que sólo ella hereda, con su stdout y su stderr en /dev/null, y ningún error de JSON queda en el __context__ de la excepción. El diagnóstico de arranque también redacta un valor con CRLF (un PEM) o con caracteres de control.

0.15.0 - 2026-10-21

Añadido

  • rayito setup prepara una cuenta y una región en una orden idempotente: reutiliza el bucket de artefactos (--bucket, RAYITO_BUCKET, el de la pila de IAM o el del bootstrap de CDK; si no hay ninguno, lo crea con la pila rayito-m0-artifacts), crea o actualiza la pila rayito-m0-iam y publica rayito-base desde el rayito-image.zip de la release de tu versión, comprobado contra su SHA256SUMS (o desde --artifact). Muestra el plan y el coste, pide confirmación (--yes, --dry-run), reanuda un build interrumpido en vez de lanzar otro y termina con rayito doctor. Con --json imprime un único documento. La pila de IAM va empaquetada con el SDK y no aparece en rayito stack list. rayito setup crea el bucket de artefactos con la pila rayito-m0-artifacts cuando no hay ninguno, y deploy_agent / rayito agent deploy lo encuentran por su salida BucketName (sólo lectura). Con --json, un error de AWS o de pila que setup no traduce también deja un único documento.
  • deploy_agent(target="agentcore") despliega un agente en Amazon Bedrock AgentCore Runtime sin Docker local: rayito agent deploy ./mi-agente --target agentcore (y deploy_agent / deploy_agent_async / plan_deploy con target="agentcore" y agentcore=AgentCoreOptions(...)) detecta el proyecto, imprime el plan (región, recursos, seguridad y coste, sin llamar a AWS), pide confirmación (--yes, --dry-run, --json) y crea la pila rayito-agentcore-<nombre> (repositorio de ECR, proyecto de CodeBuild y dos roles), construye la imagen arm64 en CodeBuild sólo si cambió el contenido y crea o actualiza el runtime por nombre (sin duplicados), con MMDSv2 exigido y un rol de mínimo privilegio. AgentCoreFleet.invoke() y stream() tienen la API y la semántica de sesión de AgentFleet; rayito agent invoke, status y stop aceptan --target agentcore y hay un rayito agent list y un rayito agent delete nuevos (sólo borran lo que creó Rayito). Las opciones de MicroVMs que no aplican son un error explícito (option_not_supported) o un aviso si vienen del fichero de proyecto. AgentCore no tiene la pasarela de secretos de Rayito: el agente lee las credenciales del rol del runtime; el plan, el README exportado y las guías lo dicen. Nuevos: AgentCoreFleet, AsyncAgentCoreFleet, AgentCoreFleetStream, AsyncAgentCoreFleetStream, AgentCoreOptions, AgentCoreStatus, AgentCoreDeleteResult, AgentCoreDeployPlan y AgentCoreException. El destino por defecto sigue siendo MicroVM y sin target="agentcore" no se crea ningún cliente de AgentCore, CodeBuild ni ECR.
  • El export agentcore se despliega tal cual: su Dockerfile toma la imagen base de un argumento de build (AGENT_BASE_IMAGE, con el mismo digest), el README explica --target agentcore y la seguridad, y un agente del AI SDK de Vercel lleva una herramienta (botocore fijado por hash) con la que el host lee las credenciales del rol y se las pasa al puente de Node en cada invocación. El Dockerfile y el README del export docker no cambian.
  • OpenCode obtiene el rol del runtime con un perfil credential_process: OpenCode sólo arranca su proveedor de Bedrock con AWS_PROFILE u otra variable de credenciales de AWS, y en AgentCore el rol sólo lo sirve el servicio de metadatos de la microVM. El export agentcore de un agente OpenCode añade el ayudante de botocore, un AWS_CONFIG_FILE con un perfil cuyo credential_process lo ejecuta (con Expiration, para que el SDK renueve) y AWS_CONFIG_FILE y AWS_PROFILE en la imagen. MicroVM, el export docker y los demás frameworks no cambian.
  • Códigos de error de rayito setup: bucket_unavailable (un bucket que existe sin acceso, 403), bucket_taken y artifact_stack_conflict, además de los de la pila y la imagen.
  • El host del agente traduce el id de sesión de AgentCore: una cabecera X-Amzn-Bedrock-AgentCore-Runtime-Session-Id con cualquier forma empieza una sesión del driver la primera vez y la continúa después, y la respuesta lleva siempre el id de la cabecera. Una sesión nueva de AgentCore enruta su primera petición en cuanto arranca la microVM, antes de que el agente esté caliente: el host la retiene hasta 25 s esperando el calentamiento en vez de contestar 503 (un calentamiento fallido sigue contestando 503 al instante).
  • Constructos de AWS CDK en rayito.cdk (extra cdk: aws-cdk-lib>=2.261.0,<3 y constructs>=10.5.0,<11). RayitoAgent despliega con cdk deploy el mismo agente que rayito agent deploy --target agentcore, con la misma plantilla infra/agentcore-runtime.yaml (IAM idéntico): la imagen se construye en CodeBuild por defecto (AgentImageBuild.code_build(), sin Docker local) o con tu Docker (AgentImageBuild.local_docker()), y Custom::RayitoRuntimeMmds exige MMDSv2. RayitoMicrovmImage publica una imagen de Lambda MicroVMs con rayd (y sus tamaños) desde MicrovmImageArtifact.download_release() (comprobado contra SHA256SUMS, con caché para síntesis sin red) o from_file(), con su propio rol de build y la misma configuración que rayito image publish. cdk synth no llama a AWS; la región del stack debe ser concreta. Errores propios en RayitoCdkException (region_unresolved, image_name_reserved, artifact_checksum_mismatch…); los del plan siguen siendo AgentCoreException. Sin el extra, import rayito y la CLI no cargan nada de CDK. Guía: guias/desplegar-con-cdk.md.
  • La CLI respeta lo que gestiona CloudFormation: un runtime con la marca rayito.agentcore/1+cdk se lista, se consulta y se invoca como cualquier otro (rayito agent list/status/invoke, AgentCoreFleet.connect), pero rayito agent deploy, rayito agent delete y deploy_agent(target="agentcore") lo rechazan con name_conflict, y rayito image publish rechaza una imagen con la etiqueta rayito:managed-by=rayito-cdk. Los runtimes con la marca rayito.agentcore/1 no cambian.

Cambiado

  • Una vez, el hash de contenido de las imágenes de agentes de MicroVMs: el host del agente (agent_host) y, para los frameworks de Node (Strands TS, LangGraph JS, Deep Agents JS, AI SDK de Vercel, Strands harness TS), el puente node_bridge.mjs llevan ahora el soporte de AgentCore (id de sesión y credenciales del rol). Como los dos forman parte del contexto de build, el primer deploy_agent tras actualizar construye una versión de imagen nueva (unos $0,011 y al menos una semana de almacenamiento de su snapshot) aunque tu código no haya cambiado; los siguientes la reutilizan. El Dockerfile y el README del export docker no cambian; sus ficheros de agent_host/ y, en Node, node_bridge.mjs sí.
  • Los errores de una cuenta sin preparar apuntan a rayito setup: el SandboxNotFoundException de Sandbox.create() cuando falta la imagen y los errores de deploy_agent por falta de bucket o de imagen base terminan con «si la cuenta o la región aún no están preparadas, ejecuta rayito setup». La clase, el código y la causa de cada uno no cambian.

Seguridad

  • Pines de los venvs de agente con los parches de seguridad publicados: certifi 2026.10.19 (retira raíces de emSign con claves intermedias comprometidas) en Strands, el harness de Strands, Deep Agents y Hermes; mcp 1.30.0 en Strands y el harness (strands-agents 1.61.1 exige mcp <2.2, así que es el parche de la rama 1.x); y, en Hermes, PyJWT 2.15.1, Pillow 13.0.0, aiohttp 3.14.4, urllib3 2.8.0, multidict 6.9.1 y yarl 1.25.1 sobre el lock de Hermes v0.22.2. pip-audit no encuentra ninguna vulnerabilidad conocida en ellos. browser-harness (el motor del Browser Use CLI de Hermes, que fija pillow==12.3.0) sale del venv de Hermes: la plantilla no activa el navegador y Hermes usa sus herramientas propias si falta. Como los ficheros de pines forman parte del contexto de build, el primer deploy_agent o build de AgentTemplate de esos frameworks tras actualizar construye una versión de imagen nueva.

0.14.0 - 2026-10-17

Añadido

  • AgentProject.detect() une la detección con deploy_agent: devuelve (proyecto, informe) a partir de una carpeta, sin ejecutar su código, con el proveedor y el modelo que encuentra (también en los ficheros de configuración de Hermes y OpenCode), la pasarela del modelo y los plugins de Node de LangGraph, DeepAgents y Vercel AI SDK como custom con su adaptador. Strands instala con uv cuando el proyecto lo usa, y el host recibe queue_max y queue_timeout_seconds en host.json. La detección de una app HTTP que lee su puerto de PORT ahora lo declara (port_env), de modo que el host se lo pasa y la app escucha donde él la espera.
  • Opciones tipadas para servir un agente en varios VMs (agent-zero-config): ServeOptions suma queue_max y queue_timeout_seconds (la cola del host; con 0, el valor por defecto, la llamada que no cabe recibe 429 como hasta ahora) y max_concurrency queda acotada por las conexiones que admite el MicroVM de memory_mib. AgentProject suma scaling (ScalingOptions: min_vms, max_vms, warm_vms, sesiones shared o dedicated y scale_in_after_seconds), env (sin secretos), network con allow_out y deny_out, region, bucket, base_image_version, language, framework y versions (RuntimeVersions). El fichero rayito-agent.toml admite las tablas [scaling], [env] y [deploy], las claves allow_out, deny_out, queue_max y queue_timeout_seconds, y en [runtime] las de language, framework, python_version y node_version. Todo es aditivo y los valores por defecto son los de un solo VM. Los errores de lectura suman los códigos ambiguous, not_detected y unsupported. Además, la detección estática de un proyecto de agente (lenguaje, framework, punto de entrada, app HTTP, plan de instalación, versiones y proveedor, sin ejecutar su código) y las reglas que comparten los dos SDK (agent-detection.json) quedan listas para deploy_agent.
  • Host y adaptadores de instalación del agente: el host del agente sirve una cola FIFO acotada (queue_max y queue_timeout_seconds de host.json; sin cola por defecto: la llamada que no cabe recibe 429, como hasta ahora) y supervisa las apps de larga vida (http-app y Hermes): una app que muere se relanza con espera creciente, /ping es HealthyBusy y las invocaciones reciben 503 con Retry-After mientras tanto, y agotado el presupuesto el marcador de fallo lleva app_crashloop con la cola saneada de su salida de error. HttpApp(protocol="json") traduce /invocations al contrato propio de una app (request_field y response_field) y reenvía sus demás rutas; agentcore sigue siendo el valor por defecto de un HttpApp explícito. Un único puente de Node (node_bridge.mjs, driver node-callable) ejecuta agentes de Strands (el antiguo strands-ts, que sigue como alias), LangGraph.js y deepagents-js (adapter="langgraph"), el AI SDK de Vercel (adapter="vercel-ai") y una función propia (adapter="callable") con CustomRuntime(entrypoint="ruta.ts#exportacion", package_manager=...). Y CustomRuntime y StrandsRuntime(language="typescript") aceptan package_manager (pip, uv, npm, pnpm, yarn), python_version y node_version: el plan de instalación (_install_plan.py) baja CPython y Node con sha256 fijado en limits.json y hace la instalación congelada del gestor. Cuando el modelo va por la pasarela, el puente fuerza HTTP/1.1 en el AWS SDK de JavaScript, que de otro modo habla HTTP/2 con Bedrock. Nada cambia para los runtimes y los ficheros de proyecto de 0.13.
  • deploy_agent() y AgentFleet: despliega el agente que ya tienes con una llamada (agent-zero-config): deploy_agent(ruta) (y deploy_agent_async()) detecta el proyecto sin ejecutar su código, resuelve la región, la imagen base, el secreto del modelo, el bucket del artefacto (el de RAYITO_BUCKET o el de activos del bootstrap de CDK; nunca crea un bucket) y el access token, construye la imagen sólo si cambió y devuelve un AgentFleet: uno o varios MicroVMs que sirven el agente. ScalingOptions fija min_vms, max_vms, las plazas calientes (warm_vms, del pool de plazas aparcadas) y si las sesiones se comparten o cada una tiene su VM (dedicated). Con más de un VM posible, el session_id del resultado es un handle rs1.<sandbox>.<sesión> que enruta derecho a su VM sin servidor propio; una flota llena espera en una cola cliente y, al agotarse, lanza CapacityException con code="agent_capacity"; un VM que desaparece es SandboxException con code="agent_vm_lost". Los VMs por encima de min_vms se terminan solos (set_timeout), un VM que deja de contestar se sustituye y AgentFleet.connect() reconstruye la flota en otro proceso. plan_deploy() calcula el plan y el coste de lista sin llamar a AWS. AgentDeployOptions, DetectOverrides y PoolConfig(access_token=) son aditivos: AgentProject.deploy() no cambia. Nada que cueste se crea sin pedirlo: un secreto sólo con create_secret_from_env=True, la imagen base sólo con un publicador.

  • rayito agent detect y rayito agent deploy [RUTA]: la CLI detecta el proyecto y lo despliega como una flota con --min-vms, --max-vms, --warm, --sessions, --max-sessions, --memory, --idle, --queue, --env, --allow-out, --bucket, --secret proveedor=nombre, --create-secret, --base-artifact con --yes, --build/--no-build, --dry-run (sólo plan y coste, sin AWS) y --verbose. El token se guarda en .rayito/agent-token (modo 0600) si no se pasa --token-file ni RAYITO_ACCESS_TOKEN, y los logs del build salen resumidos. invoke, status y stop aceptan el nombre de la flota además del id de un sandbox; de los VMs de esa imagen sólo cuentan los que son de un agente (los RUNNING con las etiquetas de un despliegue de Rayito, sondeados con un Health sin token), y si la imagen tiene un RUNNING que no lo es, tampoco se cuentan los suspendidos, que no se pueden sondear sin reanudarlos: stop nunca termina un sandbox ajeno. La salida de 0.13 de agent deploy se conserva (mismas claves, con otras añadidas) y --file sigue valiendo; lo que cambia está en "Cambiado".

  • Strands, Strands harness, Hermes, LangGraph y agentes propios como runtimes de primera clase (agent-runtimes-byo): StrandsRuntime (SDK y harness), HermesRuntime y CustomRuntime con HttpApp suman runtimes a AgentTemplate, Agent y AgentPool, y el tipo de proyecto langgraph cubre un grafo propio. Cada runtime instala sus dependencias con hashes fijados, el modelo sale por la pasarela de secretos (el agente nunca lee la credencial) y todo lo que sale a Internet queda apagado por defecto. Los puertos nuevos InstallableRuntime y ServableRuntime (con HostConfig, GatewayWiring/DirectWiring e InstallTarget) describen cómo se instala y cómo se sirve un runtime sin tocar el puerto AgentRuntime, así que los runtimes de 0.13 siguen funcionando. AgentTemplate acepta nombres del registro u objetos runtime.

  • AgentProject y AgentDeployment: del fichero rayito-agent.toml a un agente desplegado o exportado: AgentProject y AsyncAgentProject construye la imagen, ejecuta una vez, despliega un servicio (rayito.agent-service/1, el contrato HTTP de AgentCore: GET /ping y POST /invocations, con SSE) o exporta un contexto Docker/AgentCore (arm64, puerto 8080) sin llamar a AWS. AgentDeployment y AsyncAgentDeployment invoca, consulta DeploymentStatus y detiene el servicio. Es una función con coste (build, run y deploy crean imágenes y MicroVMs), apagada por defecto: nada corre sin llamar al método.

  • AgentProject y rayito agent: describe un agente en un fichero y construye, ejecuta, despliega o exporta (agent-runtimes-byo): la CLI suma rayito agent init, build, run, deploy, export, invoke, status y stop, conchas sobre AgentProject y AgentDeployment con --json, los valores por defecto del SDK y salida 2 para un fichero de proyecto inválido. init escribe rayito-agent.toml y un andamiaje por runtime (Strands, Strands harness, Hermes, LangGraph, deepagents, OpenCode o propio) sin sobrescribir nada sin --force; export y init no llaman a AWS, y deploy nunca imprime las cabeceras de acceso. El andamiaje de init se comparte con la CLI de TypeScript en testdata/cli/agent-scaffold/. Guía nueva, Despliega tu agente, y un adaptador de Strands Sandbox sobre Rayito en docs/examples/strands_rayito_sandbox.py.

  • AgentProject.deploy() acepta el access token del sandbox y rayito agent deploy|invoke|status|stop se pueden encadenar entre procesos: deploy( access_token=...) lo pasa a Sandbox.create(), y la CLI guarda el token en --token-file (o usa RAYITO_ACCESS_TOKEN) antes de crear el sandbox, igual que sandbox create --detach. Sin ninguno de los dos, deploy sale con 2 sin crear nada: antes dejaba un MicroVM que ni invoke, ni status ni stop podían volver a abrir. invoke y status leen el token con --token-file y sin él salen con 2; stop ya no lo necesita (terminate-microvm por id).

  • AgentDeployment.pause(), resume() y access_token, y deploy(keep_on_failure=) (agent-runtimes-dx): AgentDeployment y AsyncAgentDeployment suspenden y reanudan el sandbox del despliegue (Sandbox.pause() y resume()) y exponen el access token con el que se creó, también el que se generó cuando no se pasó access_token=, para reconectarse con AgentDeployment.connect(sandbox_id, access_token=...). AgentProject.deploy(keep_on_failure=True) deja vivo el sandbox de un despliegue que falla (apagado por defecto: sigue facturando hasta que lo termines), exige un access_token que el llamador conozca y anota el id del sandbox en el error. rayito agent deploy --keep-on-failure hace lo mismo.

  • AgentResult.usage_reported y Done.usage_reported: False cuando el agente no informó de tokens (una HttpApp cuya respuesta no lleva usage, un comando con protocol="text" o una función que no lo devuelve). usage vale entonces cero porque se desconoce, no porque no se gastara nada, y la CLI imprime tokens=desconocido y usageReported en --json. Una HttpApp informa de usage si su respuesta lo trae (input/output, input_tokens/output_tokens o prompt_tokens/completion_tokens).

  • HttpApp.hides_own_routes y el aviso de rayito agent deploy: con protocol="agentcore" u "openai-chat" el despliegue sólo expone /ping y /invocations; si tu health_path o invoke_path son otros, esas rutas no se alcanzan desde fuera (404). rayito agent deploy lo avisa y apunta a protocol="raw"; la guía explica cuándo usar cada protocolo.

  • rayito image publish --base-image-version latest: resuelve la versión más reciente que lista list-managed-microvm-image-versions y la imprime (base_image_version=...), así no hay que buscarla a mano y el cambio de la base nunca pasa en silencio; la opción sigue siendo obligatoria y un número explícito sigue fijándola para una build reproducible.

Cambiado

  • rayito agent deploy ahora despliega una flota, con más llamadas a AWS (agent-zero-config): el mismo comando con un --file de 0.13 ya no crea un VM suelto sin más: comprueba la imagen base (GetMicrovmImage) y el secreto del modelo (DescribeSecret), resuelve el bucket (HeadBucket y GetCallerIdentity, y el de activos del bootstrap de CDK si no pasas --bucket ni RAYITO_BUCKET), construye la imagen del proyecto si su contenido cambió (excluye por defecto .git, .env*, .venv, venv, node_modules, __pycache__, *.pyc y .rayito, así que un proyecto que los subía a la imagen la reconstruye) y devuelve una flota. Las credenciales necesitan por tanto sts:GetCallerIdentity, s3:ListBucket sobre el bucket, lambda-microvms:GetMicrovmImage y secretsmanager:DescribeSecret, además de las de 0.13. Con --no-build y sin bucket reutiliza la imagen existente sin construir nada.

  • ServeOptions(max_concurrency=) queda acotada por la memoria de la VM: un MicroVM de memory_mib admite un número limitado de conexiones (7 con los 2048 MiB por defecto, 15 con 4096 y 31 con 8192); 0.13 aceptaba cualquier valor desde 1. Un proyecto de 0.13 con un valor mayor falla al cargarse con InvalidArgumentException: baja max_concurrency o sube memory_mib.

  • CustomRuntime corre tu app y tu comando desde el código copiado, con su venv primero en el PATH (agent-runtimes-dx): el directorio de trabajo de una HttpApp es el del source copiado (/opt/agents/app) y va el primero en su PYTHONPATH, así que python -m paquete.modulo funciona; con requirements, python y pip son los del venv, y sin él python es el intérprete de la imagen (que sólo trae python3). Los command de un CustomRuntime reciben el mismo PATH y PYTHONPATH, y el directorio de trabajo de un command no cambia. Hace falta reconstruir la imagen para que una ya construida lo use.

  • El uso de un agente que no informa de tokens es desconocido, no cero: el host marca usage_reported en POST /invocations y deja usage sin valor en el step_finished de un comando con protocol="text" o de una función sin usage. Un cliente de 0.13 que no conoce el campo sigue leyendo usage.

  • Un arranque fallido del host cuenta qué falló: el marcador de fallo lleva el detail_code (app_exited, app_not_ready, la clase de la excepción) y, en una HttpApp, el final de su salida de error (acotado a AGENT_STARTUP_DIAGNOSTICS_MAX_BYTES, sin valores de entorno ni credenciales); deploy() lo incluye en la SandboxException en vez del nombre de clase opaco AgentFailure.

  • rayito agent init --runtime custom escribe una app HTTP y langgraph un grafo de LangGraph a secas: custom escribe [runtime.app] (con protocol = "raw", sus rutas /health e /invoke), requirements, install y un serve.py mínimo; langgraph, un create_react_agent en vez de create_deep_agent. El runtime langgraph traduce además el nodo agent de create_react_agent (antes sólo model), así que un grafo de LangGraph a secas devuelve texto, pasos, herramientas y uso.

Corregido

  • Exportar a Docker: el contexto crea los directorios de configuración del agente con permisos de lectura para el usuario sin privilegios (BuildKit los dejaba en 0600 con --chmod), la imagen de OpenCode instala curl antes de descargar los binarios y los módulos y fábricas de un grafo se resuelven como punto de entrada.
  • El host relanzado mata antes lo que dejó el hijo muerto en su grupo: un envoltorio como npm run start que muere dejaba al servidor huérfano ocupando el puerto y el relanzado nunca quedaba listo.
  • exclude de un AgentProject/AgentTemplate(app_exclude=) filtra antes de leer: un .venv excluido se leía entero y luego se descartaba (y su cacert.pem disparaba el aviso de ficheros con secretos). Ahora los patrones de exclude se suman a los de .dockerignore y filtran antes de leer ningún fichero.

0.13.0 - 2026-10-16

Añadido

  • rayito --version: imprime la versión y el runtime (rayito 0.x (python)), para saber cuál de las dos CLI (Python o TypeScript) responde cuando ambas están instaladas.

Cambiado

  • rayito sandbox proxy recorta sólo OWS y valida el tamaño de trozo antes de reenviarlo (ADR-027, vectores compartidos con la CLI de TypeScript en testdata/cli/proxy-vectors.json). Los valores de cabecera, los elementos de Content-Length/Transfer-Encoding y el chunk-size pierden sólo espacios y tabuladores (RFC 9110 §5.6.3): str.strip() quitaba también \x85 o \xa0, así que un Content-Length: 12\x85 tenía longitud 12 en el proxy y un upstream estricto lo rechazaría. Un chunk-size inválido ya no se escribe en el upstream antes de cortar. Los topes y plazos del proxy (PROXY_*) pasan a limits.json, con los mismos valores.

  • La CLI escribe siempre los instantes en UTC (2026-09-15 14:39:02+00:00 en --json y en las tablas, isoformat en UTC en image prune): antes salían en la zona local de la máquina, porque boto3 entrega datetime en esa zona, y la CLI de TypeScript escribe UTC. Los vectores compartidos (testdata/cli/format-vectors.json) y los casos con instantes en otra zona (command-cases.json) hacen fallar el CI de los dos lenguajes si vuelven a separarse.

0.12.0 - 2026-10-15

Añadido

  • Panel: variante AuthMode=aws-iam de la pila dashboard (opt-in, por defecto cognito-jwt): la API tiene su propia Function URL con AuthType AWS_IAM (firma SigV4 con las credenciales de un identity pool de Cognito; Lambda rechaza lo no firmado antes de ejecutar la función) y una segunda función, sin permisos sobre MicroVMs, sirve la página. La SPA firma con SigV4 (verificado contra botocore) y envía el token de Cognito en X-Rayito-Token. Verificada en AWS real (2026-10-14).

  • Panel: cuentas desde el entorno: rayito dashboard lee --account de RAYITO_DASHBOARD_ACCOUNTS (entradas NOMBRE=ARN separadas por espacios), para armar el rol de la cuenta destino en tiempo de ejecución.

  • Panel: guía para probar las gráficas OTLP del dashboard de CloudWatch y scripts/measure/dashboard_apis.py (TPS de ListMicrovms y ListMicrovmImages, utilización de las cuotas de MicroVMs y retraso de Cost Explorer; sólo lectura, el coste facturado tras --billed; no lo corre ninguna puerta).

  • e2e de sólo lectura de Quotas, UsageCost (el coste facturado sólo con RAYITO_E2E_BILLED_COST=1) y Fleet.

  • Coste a la vista en el panel (ADR-026): la ayuda de rayito dashboard y de rayito dashboard cloudwatch dice qué es gratis y qué cuesta (Cost Explorer, DynamoDB de eventos, resume que vuelve a facturar cómputo, el dashboard de CloudWatch y la métrica de --quota), y los textos de coste de la pila salen de limits.json. Los avisos de cuota recuerdan el nivel más alto hasta que el uso vuelve a normal, así que un uso que oscila avisa una vez; rayito sandbox proxy quita la cookie del panel con cualquier separador.

  • Panel, fase 3 (ADR-026), todo opcional y apagado por defecto: rayito dashboard cloudwatch imprime el JSON de un dashboard de CloudWatch (Logs Insights sobre /rayito/*; --otlp y --quota añaden las métricas OTLP y el uso de cuota) sin llamar a AWS, y la pila dashboard-cloudwatch lo despliega; rayito dashboard --account NOMBRE=ARN_DEL_ROL (con --external-id) añade al panel local otras cuentas, de sólo lectura, por roles que asume, y la pila dashboard-read-role crea ese rol en la cuenta destino; la pila quota-alerts (Lambda y regla de EventBridge) publica en SNS cuando la memoria asignada cruza un umbral de la cuota, una vez por cruce, y publica el uso como métrica. rayito dashboard pasa a ser un grupo (sin subcomando sigue arrancando el panel local, con las mismas opciones). Ruta GET /api/v1/accounts (siempre [] salvo con --account). Constantes nuevas en limits.json (dashboardAccountsMax, dashboardAssumeRole*, quotaMetric*, quotaAlerts*).

  • Pila dashboard (el panel de gestión dentro de tu cuenta, ADR-026, fase 2): componente de OptionalStacks/rayito stack deploy|status|destroy dashboard. Una Lambda con Function URL sirve la misma página y la misma API /api/v1 que rayito dashboard, con inicio de sesión de Cognito (el token se valida en la función antes de enrutar), un rol de lectura y un rol de acciones separados (AllowActions=true), una tabla de auditoría sólo de añadir (escrita antes de cada acción; si falla, la acción no corre) y una caché del coste facturado en DynamoDB (EnableCostExplorer=true). Tema propio por ThemeJson o por un parámetro de SSM (ThemeParameterName, LogoParameterName), con los mismos validadores que --theme. Sin CloudFront ni buckets de S3; las etiquetas obligatorias se pasan con --tag. De sólo lectura y sin Cost Explorer por defecto. Constantes nuevas en limits.json (dashboardStack*). El coste facturado tiene tope aunque lo pida cualquier usuario con sesión (ventanas de meses completos, regiones conocidas y un presupuesto diario de consultas de pago para toda la pila); antes de cada acción la función comprueba en Cognito que el usuario sigue habilitado y en el grupo, y la página no entra en bucle de inicio de sesión tras un 401.

  • rayito dashboard (panel de gestión local, ADR-026, fase 1b): un servidor en 127.0.0.1 con una página sin compilación (módulos ES y web components, claro y oscuro) y la API JSON versionada /api/v1 sobre Fleet, Quotas y UsageCost: sandboxes, imágenes, cuotas con barras y avisos y enlace de aumento, coste facturado y estimado rotulados por separado, eventos y logs. De sólo lectura por defecto; --allow-actions habilita matar, suspender y reanudar con confirmación y una cola que respeta el TPS; --cost-explorer y --events-stack activan lo que cuesta o necesita una pila. Defensas: Host/Origin como el proxy local, token de arranque de un solo uso canjeado por una cookie HttpOnly, SameSite=Strict, Sec-Fetch-Site, CSRF en todo POST y en la consulta de coste facturado, CSP estricta y nada registrado. Constantes nuevas en limits.json (dashboard*, costExplorerRequestPriceUsd). La página tiene un resumen inicial (tarjetas y gráficas SVG), tabla densa de sandboxes y tema oscuro por defecto; con --theme (.json o .css, o RAYITO_DASHBOARD_THEME) y --logo (PNG o SVG) se usa la paleta propia, validada y servida del mismo origen. /api/v1/meta añade theme.

  • Servicios del panel de gestión (dashboard, ADR-026, fase 1): Quotas (cuotas aplicadas de Lambda MicroVMs, memoria asignada contra la cuota de la región y avisos al 80 % y al 95 %), UsageCost (coste facturado por Cost Explorer por tipo de uso, con caché local de 24 h, y coste estimado por sandbox, siempre rotulados) y Fleet (sandboxes con la memoria de su imagen, imágenes y versiones, historial de eventos y acciones masivas con una cola que respeta los TPS), cada uno con su versión Async*. Apagados por defecto: construirlos no llama a AWS, Cost Explorer ($0,01 por petición) sólo se toca con UsageCost.billed() y las acciones exigen allow_actions=True. Nueva excepción DashboardException y constantes nuevas en limits.json (catálogo de cuotas, umbrales, TTL y precios de cómputo). rayito doctor y rayito image list reutilizan los lectores de cuotas e imágenes. Cada clase, método y opción dice si es gratuito o de pago (billed() y events() son de pago; resume vuelve a facturar cómputo) con el mismo texto en ambos SDK. Guía: Panel de gestión.
  • Template.dnf_install() y Template.npm_install(packages=None, g=False) (sync y async). Atajos del DSL para instalar paquetes del sistema con dnf (sin paquetes débiles) y paquetes Node con npm; los nombres se escapan para el shell y se rechazan las listas vacías y los nombres que empiezan por -. dnf_install y npm_install(g=True) corren como root aunque un set_user() previo dejara otro usuario. npm_install falla con el código 127 y un mensaje claro si la imagen no tiene Node.js. apt_install sigue lanzando UnimplementedError, ahora apuntando a dnf_install.

Cambiado

  • La variante de imagen se reconoce con un catálogo cerrado (parity-image-variants). resolve_image_variant sólo interpreta rayito-<variante>[-<talla>] con la variante en base, base-caps, base-caps-efs, base-poly y base-slim y la talla del catálogo de tamaños. Una imagen propia que empieza por rayito- (por ejemplo rayito-mi-app-caps-efs) ya no se interpreta como una variante conocida que no es caps ni se rechaza antes de lanzar: la decisión pasa a Health.features, como ya ocurría con un ARN. volumes= (y volume_mounts= del shim) exige además rayito-base-caps-efs cuando la variante se conoce: rayito-base-caps sin amazon-efs-utils lanza UnimplementedError antes de lanzar en lugar de después.

  • Pila dashboard-cloudwatch: DashboardName vacío (el valor por defecto) usa el nombre de la pila en lugar del fijo rayito, así que desplegar ya no choca con un dashboard propio con ese nombre. Una pila ya desplegada conserva su nombre (redesplegar reutiliza el valor anterior).

Corregido

  • Pila dashboard-cloudwatch: con IncludeOtlp o IncludeQuota los widgets añadidos se creaban con la región literal ${AWS::Region}, porque Fn::Sub no sustituye dentro de los valores de sus variables. La plantilla resuelve ahora la región con Fn::Join y Ref AWS::Region, y el test de la plantilla la resuelve como CloudFormation.
  • Panel multicuenta: pedir una acción en una cuenta adicional decía «arranca con --allow-actions»; ahora dice que las cuentas adicionales son de sólo lectura.
  • scripts/measure/dashboard_apis.py: sin los reintentos de botocore el throttling se ve (antes lo ocultaban y falseaban la tasa lograda), y un escalón sólo cuenta como saturado si se rechaza al menos el 10 % de sus llamadas.

0.11.0 - 2026-10-13

Añadido

  • rayito image publish --artifact-run-id RUN_ID (run-scoped-artifact-keys). Sube el zip a rayito/images/runs/<RUN_ID>/rayd-<sha>.zip en vez de a la clave compartida por contenido, para que dos ejecuciones del mismo commit (aceptación, e2e en paralelo) no compartan el artefacto y la limpieza de una no borre el de la otra. El resumen --json añade artifactUploaded (si esta invocación subió el objeto). make image-publish* pasa el flag cuando hay RAYITO_E2E_RUN_ID o ARTIFACT_RUN_ID. Sin el flag, nada cambia.

Cambiado

  • AgentTemplate arranca antes (ai-agent-first-start). Dos cambios en la imagen que construye, medidos en AWS real (AWS_API_NOTES.md Q155 y Q156): kernel_warmup=False por defecto (opción nueva; CLI --kernel-warmup/--no-kernel-warmup) escribe el marcador slim del sidecar, así que el kernel de run_code ya no importa numpy, pandas, matplotlib, scipy y scikit-learn en cada arranque, y el demonio de prefetch lee una vez los binarios de OpenCode y ripgrep antes del snapshot del build, con un ready_cmd que lo espera, para que el snapshot de memoria ya los lleve. create() vuelve antes (6,4 s frente a 19,1 s de mediana en la misma tanda) y el snapshot de memoria pesa 62 MB menos; el primer token mejora poco y con mucha dispersión (20,9 s frente a 23,7 s de mediana). Las imágenes ya construidas no cambian.
  • Cambio de comportamiento: AgentTemplate ya no precalienta el kernel de run_code (kernel_warmup=False por defecto). En una imagen construida con 0.11, la primera celda de run_code que importe numpy, pandas, matplotlib, scipy o scikit-learn paga esa importación. Para volver al comportamiento de 0.10, construye la imagen con AgentTemplate(..., kernel_warmup=True) o rayito agent template build --kernel-warmup.
  • rayito doctor conoce la serie 0.11: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITY y docs/site/docs/limits.md) exige el rayd del tag rayd-v0.11.0 para el SDK 0.11.

0.10.0 - 2026-10-08

Añadido

  • Más proveedores para el modelo del agente (ai-agent-providers). AgentModel.provider admite "openai" (Responses API, también xAI), "google" (Gemini API) y "azure" (Azure OpenAI v1), y hay nueve presets de pasarela con clave de API estática: openai_gateway, gemini_gateway(models=), azure_openai_gateway(resource=), openrouter_gateway, groq_gateway, mistral_gateway, deepseek_gateway, xai_gateway y litellm_gateway(upstream=, base_path="/v1"). OpenCode usa los proveedores nativos openai, google y azure a través de la pasarela; deepagents construye ChatOpenAI(use_responses_api=True), ChatGoogleGenerativeAI o AzureChatOpenAI (Responses API, clave en api-key). El opencode.json declara el paquete del AI SDK de cada proveedor (@ai-sdk/openai, @ai-sdk/google, @ai-sdk/azure, todos dentro del binario fijado), Azure apunta a /openai/v1 y OPENCODE_EXPERIMENTAL_WEBSOCKETS queda apagado; la prueba de humo de AgentTemplate importa también langchain_openai y langchain_google_genai. gen_ai.provider.name vale gcp.gemini y azure.ai.openai para los nuevos. En las APIs al estilo de OpenAI la pasarela no puede limitar el modelo: fija el tope de gasto en el proveedor (SECURITY.md T29). Las suscripciones de consumo (plan de ChatGPT, Claude Pro/Max, Copilot, SuperGrok) no se admiten; la guía "Proveedores del agente" explica por qué. Construir un preset no llama a AWS.

Changed

  • rayito doctor conoce la serie 0.10: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITY y docs/site/docs/limits.md) exige el rayd del tag rayd-v0.10.0 para el SDK 0.10.

Fixed

  • El timeout y abort() de sbx.agent paran también lo que lanzó la herramienta de shell del agente. OpenCode y deepagents corren cada orden en una sesión propia (setsid), fuera del grupo del runtime, y el timeout sólo mataba ese grupo: un sleep o un servidor lanzado por el agente sobrevivía y el siguiente run() encontraba el agente busy. Cada ejecución arranca ahora con kill_tree=True y rayd congela y mata todo su árbol (también lo demonizado); el stream acaba con reason="timeout" cuando ya no queda nada. abort() y los límites del SDK sólo matan el handle: desaparece la orden pgrep que corría antes como segundo comando. commands.run() acepta kill_tree= (sync y async) para lo mismo en cualquier comando. Necesita el rayd de esta versión: con uno anterior el campo se ignora y el timeout y abort() sólo alcanzan al grupo del runtime; reconstruye la imagen.

  • deepagents cuenta los tokens de los modelos compatibles con OpenAI. Con "openai-compatible", ChatOpenAI no pedía el uso en streaming (stream_options.include_usage): cada paso llegaba con 0 tokens, result.usage quedaba a cero y AgentLimits.max_total_tokens no cortaba nunca. El runner crea ahora ChatOpenAI(stream_usage=True). Medido de verdad, con el egress cerrado, contra la Chat Completions compatible con OpenAI de Amazon Bedrock (bedrock-runtime /openai/v1 y bedrock-mantle) y un proxy de LiteLLM; la Responses API ("openai", "azure") ya mandaba el uso.

  • deepagents con modelos que no son de Bedrock: el venv de AgentTemplate (y del guest local) lleva socksio, el extra socks de httpx. rayd exporta ALL_PROXY=socks5h://... en el sandbox y, sin él, httpx fallaba al crear el cliente (AgentFailed con ImportError) con OpenAI, xAI, Azure OpenAI, Gemini, Anthropic y las APIs compatibles con OpenAI, aunque el modelo se llame por la pasarela en loopback. Cambia el sha256 de requirements-deepagents.txt: reconstruye la plantilla de agente.

0.9.1 - 2026-10-07

Fixed

  • Sandbox.create(timeout=120) sin idle ya no lanza InvalidArgumentException. Desde 0.3.0, cualquier timeout (o max_lifetime con on_timeout) de 300 s o menos fallaba porque el SDK validaba su propia IdlePolicy por defecto contra él. Ahora, sin idle explícito, la auto-suspensión por defecto se desactiva si no cabe (el sandbox termina en su timeout, como en E2B) y, con on_timeout="pause", baja a 60 s (PAUSE_DEFAULT_IDLE_FALLBACK_SECONDS). Un idle que pasas tú se valida igual que antes. Igual en Sandbox y AsyncSandbox.

Documentation

  • Los e2e de s3-mounts y rayd-otlp documentan como precondición que la política gestionada de su pila (PolicyArn) esté adjunta al rol de ejecución; rayito stack deploy sólo la crea.

0.9.0 - 2026-10-07

Cambios que rompen

  • rayito.e2b.BuildException y rayito.e2b.TemplateException son las clases nativas. Antes eran clases propias del shim que nada lanzaba, así que except rayito.e2b.BuildException no atrapaba un Template.build() fallido. Ahora son rayito.BuildException/rayito.TemplateException, y BuildException hereda de SandboxException (en E2B hereda de Exception). Migración: except rayito.e2b.BuildException ahora atrapa los builds fallidos reales. Como la clase es un SandboxException, cambian los isinstance/issubclass que suponían lo contrario. Además tiene el constructor nativo (BuildException(message, *, reason=..., step=..., command=..., exit_code=..., log_tail=...)): el código que la lanza o la hereda debe pasar un único mensaje; BuildException() o BuildException(a, b) fallan con TypeError.

  • Se retira la opción D del arranque rápido del agente (pool con opencode serve residente), sin periodo de obsolescencia: apenas ganaba unas décimas a la opción C (AWS_API_NOTES.md Q154) y costaba más memoria y más por plaza. Desaparecen agent_pool_warmup(..., serve=), sbx.agent.prepare(serve=), attach= de sbx.agent.run()/stream() (sync y async) y RunRequest.attach; el script de OpenCode ya no busca ni se engancha a un servidor (--attach) ni relee la vuelta de él, y el span rayito.agent.run deja de llevar rayito.agent.attached. El puerto AgentRuntime pierde abort_command (ningún runtime lo usaba ya) y warmup_steps() no lleva argumentos; OPENCODE_SERVE_PORT desaparece. Migración: quita serve=True y attach=; para un primer mensaje rápido usa agent_pool_warmup(runtime) (opción C) o el arranque normal (Sandbox.create(...) y sbx.agent.run(...)). Un runtime propio borra abort_command y el parámetro serve de warmup_steps.

  • Se elimina GatewayException (no estará en 0.9.0). Nunca se lanzaba: lo que la pasarela de secretos rechaza por petición llega al proceso del sandbox como respuesta HTTP (403, 429, 502, 504), y una configuración que rayd rechaza es SandboxException. Migración: quita el import y cualquier except GatewayException; si capturabas fallos de la pasarela al crear o refrescar, captura SandboxException.
  • AWS_REGION gana a AWS_DEFAULT_REGION y a la región del perfil en el SDK de Python (ver "Fixed"). Si exportas AWS_REGION con un valor distinto de AWS_DEFAULT_REGION o de la region de tu perfil, el SDK pasa a operar (sandboxes, secretos, índice, EFS…) en la región de AWS_REGION. Migración: para conservar la región anterior, quita AWS_REGION del entorno o pasa region= explícita.

Fixed

  • El SDK de Python honra AWS_REGION, como el de TypeScript y la CLI. Antes boto3 sólo leía AWS_DEFAULT_REGION o el perfil. Toda sesión boto3 del SDK (plano de control, staging S3, secretos, índice DynamoDB, eventos, EFS, CloudFormation…) resuelve la región en un único sitio con la prioridad region= > sesión del llamante > AWS_REGION > AWS_DEFAULT_REGION > perfil; una sesión del llamante sin región sigue esa misma cadena. Quien sólo exporta AWS_DEFAULT_REGION no nota cambios.

  • Sandbox.connect() y AsyncSandbox.connect() recuperan sbx.gateways: otro proceso ya puede usar sbx.agent sobre un sandbox creado con gateways= sin recrearlo. connect() lee una vez el ConfigureStatus de rayd (nombre, puerto y último error de cada ruta; nunca el upstream ni las cabeceras) si el agente anuncia secret_gateway y el handle no aplicó gateways= él mismo. En un handle recuperado, refresh() sólo relee el estado y cada connect() vuelve a leerlo con el timeout de petición de esa llamada. Rotar la clave (refresh() o reincarnate()) sigue siendo cosa del handle que llamó a create(gateways=): en uno recuperado, reincarnate() lanza. Si rayd rechaza el token en esa lectura, connect() no falla: se salta la recuperación y el AuthenticationException sale en la primera llamada autenticada, como hasta ahora.

Changed

  • rayito doctor conoce la serie 0.9: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITY y docs/site/docs/limits.md) exige el rayd del tag rayd-v0.9.0 para el SDK 0.9.
  • Docstrings, ayuda de la CLI y mensajes alineados con la documentación (barrido de docs de 0.8): las excepciones sin docstring lo tienen, los códigos de MountException, AgentException (runtime_version_mismatch y output_limit, reservados) dicen lo que pasa de verdad, reincarnate() lista todo lo que reaplica, Sandbox.create(pool=) lista los kwargs que pasan, y los errores sin región del índice y de Template.build piden AWS_REGION. La ayuda de rayito image sizes, doctor, sandbox proxy, agent template build y domain ya no nombra detalles internos.

0.8.0 - 2026-10-07

Cambios que rompen

  • register_webhook (eventos de ciclo de vida) rechaza con InvalidArgumentException, antes de cualquier llamada a AWS, una URL cuyo host es localhost (o *.localhost) o una IP literal no pública (loopback, privada, link-local, CGNAT, reservada…). Hasta 0.7.1 se guardaba y el deliverer la descartaba en cada entrega, así que nunca se entregó nada. Migración: registra una URL HTTPS con un nombre público (detalle en "Security").

Added

  • Agente de IA, adaptador de deepagents (ai-agent-deepagents): runtime="deepagents" o runtime=DeepAgents(entrypoint="pkg.mod:build") corre un grafo de deepagents/LangGraph con el mismo contrato que OpenCode (eventos cerrados, límites del SDK, credenciales sólo por la pasarela). Un runner que va como dato del paquete (rayito/_agent/_runner/deepagents_runner.py) habla el protocolo JSONL v1 de Rayito por un descriptor privado (un print del usuario no lo rompe), construye el modelo contra la pasarela con un marcador como clave, aplica los permisos con un middleware, guarda las sesiones rda_… dentro del sandbox y emite TextDelta. mcp, raw_config y attach=True fallan con InvalidArgumentException antes de cualquier RPC. make agent-runner-test lo prueba con el venv real en arm64.
  • Agente de IA, arranque rápido (ai-agent-fast-start): AgentTemplate/AsyncAgentTemplate y rayito agent template build construyen la imagen del agente con OpenCode y ripgrep fijados por sha256 y el venv de deepagents con --require-hashes (sin --no-deepagents), todo de root, el manifiesto rayito.agent-template/1 y, salvo prefetch=False, un demonio que precarga los binarios tras cada restauración del snapshot. PoolConfig gana warmup (pasos que cada plaza corre antes de aparcarse; un paso fallido es un calentamiento fallido), allow_internet_access y network; agent_pool_warmup() da los pasos del agente y, con serve=True, deja un opencode serve residente con contraseña propia de cada VM al que agent.run se engancha tras el take(). Coste sólo si construyes la plantilla o pasas warmup (bloques "Coste y activación").
  • Agente de IA, adaptador de OpenCode (ai-agent-core): runtime="opencode" ya resuelve a un adaptador (rayito._agent._opencode) que escribe opencode.json sin credenciales, lanza opencode run con el prompt por stdin, un cerrojo por sandbox y --attach a un opencode serve residente si responde, y traduce su JSONL a eventos. Configuración y script idénticos byte a byte entre Python y TypeScript (testdata/agent/).
  • Agente de IA, dominio (ai-agent-core, ADR-025): los tipos con los que sbx.agent describirá una ejecución, validados al construirlos y sin ninguna llamada a AWS: AgentSpec, AgentModel (Bedrock, Anthropic o una API compatible con OpenAI, siempre a través de una pasarela de sbx.gateways), AgentPermissions (sólo allow/deny; question, webfetch y websearch denegadas por defecto), SubAgent, McpLocal, McpRemote y AgentLimits (50 pasos, 600 s, 16 MiB y 1 000 000 de tokens por defecto); los eventos (TextDelta, Text, Reasoning, ToolCall, StepStarted, StepFinished, AgentFailed, Done), TokenUsage y AgentResult; AgentException con un reason de una lista cerrada y un mensaje fijo que nunca lleva texto del proveedor; y las pasarelas ya hechas bedrock_gateway, anthropic_gateway y openai_compatible_gateway, cuyo allow cubre sólo los modelos elegidos. sbx.agent llega en un cambio posterior.
  • Agente de IA, API pública (ai-agent-core, design.md §4 y §7): sbx.agent.run()/.stream()/.prepare(), síncronos y async. run() corre el agente hasta el final y lanza AgentException si falla; stream() devuelve un AgentStream iterable (gestor de contexto) que nunca lanza por un fallo del agente —el último evento es Done o AgentFailed— y expone .abort(), .session_id y .result(); prepare() dispara los pasos de calentamiento del runtime en segundo plano y vuelve enseguida. Aplica la configuración del runtime con files.write_files (una sola vez por sha de configuración), lanza el script del runtime con commands.run(background=True, stdin=True) y trocea su stdout en eventos; AgentLimits.max_steps y max_total_tokens los impone el propio SDK sobre esos eventos, no el runtime. sbx.agent es una propiedad perezosa: tocarla no manda ningún RPC. Con tracer_provider=, cada ejecución abre un span rayito.agent.run con atributos gen_ai.* y rayito.agent.* (nunca el prompt, el texto de la respuesta ni argumentos de herramienta). Un AgentRuntime concreto (el adaptador de OpenCode) llega en un cambio posterior; mientras tanto, runtime= admite cualquier objeto que implemente el Protocol AgentRuntime.

Fixed

  • sbx.agent con un opencode serve residente (agent_pool_warmup(serve=True), opción D): opencode run --attach (1.18.34, igual en 1.18.35) sale en cuanto el servidor contesta el prompt, sin esperar a sus propios eventos, y el SDK veía AgentFailed(reason="protocol_error") o un Done sin texto aunque el servidor completaba la respuesta. Ahora el script crea la sesión en el servidor, y al salir run relee de él los mensajes de esa vuelta y emite las partes que faltaban (sin duplicar las ya emitidas), incluido el error del modelo. Un session_id que no tiene forma de id de OpenCode es InvalidArgumentException.
  • AgentTemplate con prefetch: el demonio de precarga leía los binarios nada más restaurar el snapshot y su E/S competía con el arranque, así que create() tardaba ≈ 8 s más (Q146). Ahora espera a que el guest lleve 1 s sin E/S en curso (como mucho 60 s) antes de leer.
  • AgentTemplate: instala el runner de deepagents en /opt/agents/rayito/deepagents_runner.py (root, 0755) y pone su sha256 en runner_sha256 del manifiesto. Sin él, runtime="deepagents" fallaba siempre en una imagen construida con AgentTemplate (aceptación en AWS, AWS_API_NOTES.md Q150).
  • sbx.agent: abort() y los límites max_steps/max_total_tokens paran también lo que lanzó la herramienta de shell del agente (OpenCode y deepagents la corren en una sesión propia, fuera del grupo que mata rayd); antes un sleep o un servidor lanzado por el agente sobrevivía. Un límite del SDK, además, ya no deja el runtime trabajando (y gastando tokens) en segundo plano, y el siguiente run() no lo encuentra busy.
  • sbx.agent: un timeout de AgentLimits terminaba con reason="runtime_error" en vez de "timeout".
  • runtime="deepagents": las herramientas de ficheros escribían una ruta absoluta dentro del workdir (/home/user/x acababa en /home/user/home/user/x); ahora usan la ruta tal cual, como el shell.
  • sbx.agent.prepare() arranca los pasos en segundo plano sin plazo: con serve=True el servidor residente moría al agotar timeout_seconds.
  • import rayito vuelve a no cargar el agente de IA (rayito._agent.*, rayito.sandbox_*.agent) ni el adaptador boto3 de los eventos de ciclo de vida (rayito._lifecycle_events._aws): sus nombres públicos (AgentSpec, LifecycleEvents…) se importan en el primer acceso (PEP 562), Sandbox.agent se construye en el primer uso y PoolConfig(warmup=) importa WarmupStep sólo al validarlo. Unas 20 entradas menos en sys.modules y menos tiempo de import; tests/unit/test_lazy_imports.py lo vigila.

Changed

  • rayito doctor conoce la serie 0.8: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITY y docs/site/docs/limits.md) exige el rayd del tag rayd-v0.8.0 para el SDK 0.8. Sin la fila, doctor daba FAIL en cada instalación 0.8 y scripts/prepare_release_pr.py se negaba a preparar la release.
  • Montajes S3: el MountException de un bucket que no está en el allowlist de la imagen (code="not_allowed") ya no dice sólo que la sección se rechazó: explica que falta en RAYITO_ALLOWED_MOUNT_BUCKETS y cómo publicar la imagen con él (--env RAYITO_ALLOWED_MOUNT_BUCKETS=<bucket> o make image-publish-caps MOUNT_BUCKETS=<bucket>), sin nombrar el bucket. El texto vive en MOUNT_ERROR_HINTS, igual en los dos SDKs (testdata/s3-mounts/error-hints.json).

Security

  • Eventos de ciclo de vida (webhook-url-public-address): register_webhook rechaza también, con InvalidArgumentException y antes de cualquier llamada a AWS, un host localhost (o bajo .localhost) y una IP literal que el guardián SSRF del deliverer bloquearía: loopback, privada, link-local (incluida la de metadatos), multicast, reservada, sin especificar o CGNAT, también como IPv6 con una IPv4 dentro (::ffff:127.0.0.1) o en las formas numéricas de IPv4 (0x7f.1, 2130706433). Antes se guardaba y el deliverer la descartaba en cada entrega. is_blocked_webhook_address aplica la misma regla que el deliverer y los tres pasan testdata/lifecycle-events/ssrf-address-vectors.json. Un nombre DNS sigue sin resolverse al registrarlo: el DNS rebinding lo para el deliverer.

0.7.1 - 2026-10-06

Changed

  • rayito image zip (y scripts/image_zip.py) se niega a empaquetar un directorio de imagen que lleva rayd sin sus avisos de licencia en licenses/ (LICENSE, NOTICE y THIRD_PARTY_LICENSES.md, que deja make image-licenses): image/Dockerfile los copia a /usr/share/doc/rayd/, y sin ellos la build de la imagen fallaría en AWS (third-party-licenses). Un directorio sin rayd no cambia.

0.7.0 - 2026-10-05

Added

  • Dominio propio (m15-custom-domain, ADR-024, experimental, apagado por defecto): CustomDomain/AsyncCustomDomain despliegan una distribución CloudFront, una CloudFront Function de enrutado (cloudfront-js-2.0) y un KeyValueStore (infra/custom-domain.yaml, rayito domain deploy|status|destroy), y register/unregister/ refresh/host_for gestionan las rutas {puerto}-{alias}.<tu-dominio> (hostname HTTPS normal, sin las cabeceras x-aws-proxy-*). register() exige traffic_token salvo public=True explícito. deploy(alternate_domain_names=[...]) (--alternate-domain-name) sustituye el alias comodín por hostnames exactos. Nuevo extra rayito[custom-domain] (awscrt), porque el plano de datos del KeyValueStore firma con SigV4A. Sin instanciar CustomDomain no se crea ningún cliente de CloudFront ni de CloudFormation. Sandbox.create(domain=) sigue lanzando UnimplementedError: la integración con get_host()/expose() llega en un cambio posterior. Experimental porque no se ha verificado de punta a punta en AWS real: la Function compila y enruta en el runtime real (TestFunction), y la pila crea el KeyValueStore y la Function, pero la cuenta de pruebas deniega cloudfront:CreateDistribution por una SCP, así que nunca se ha servido tráfico por una distribución desplegada con esta plantilla (AWS_API_NOTES.md Q121, Q140, Q141). La API puede cambiar en una minor.
  • rayito image zip --with-efs y rayito image publish --with-efs (m15-efs-volumes): publican rayito-base-caps-efs, la imagen caps con amazon-efs-utils que necesita volumes= (+~198 MB de imagen, snapshot igual; exige --os-capabilities ALL y un zip con el marcador efs_variant, comprobado antes de llamar a AWS). make image-zip-efs / make image-publish-caps-efs. Las imágenes por defecto no cambian.
  • rayito-base-caps-efs (y sus sufijos de tamaño) cuenta como variante caps para mounts=, volumes= y telemetry= (también en TypeScript).
  • Volúmenes EFS (m15-efs-volumes, ADR-018, experimental, apagado por defecto): VolumeStore/AsyncVolumeStore (CRUD real de access points EFS: CreateAccessPoint/DescribeAccessPoints/ DeleteAccessPoint, sin cliente efs hasta el primer uso) y Sandbox.create(volumes=), que valida la petición (tipos, rutas, variante caps y un único conector propio en egress=: sin él, con INTERNET_EGRESS o con dos conectores es InvalidArgumentException, porque un MicroVM sólo admite un conector de egress, AWS_API_NOTES.md §16 Q131), como mucho 4 volúmenes y execution_role_arn= antes de cualquier llamada a AWS; resuelve la IP de mount target que falte con una DescribeMountTargets por sistema de ficheros antes de run-microvm, manda la sección efs_volumes en el único ConfigureSandbox de create() (con plazo de 65 s), sólo vuelve con todos los volúmenes mounted y, si uno falla, termina el sandbox (salvo keep_on_failure) y lanza VolumeMountException (code: network, iam_denied, not_found, tls, helper_missing, timeout, invalid_path); sobre una imagen sin amazon-efs-utils, UnimplementedError. reincarnate() la vuelve a mandar. sbx.volumes (await sbx.volumes() en AsyncSandbox) da el estado en vivo. EfsVolume (valida mount_target_ip como IPv4), VolumeStatus y las excepciones VolumeException/VolumeMountException/VolumeNotFoundException/ VolumePathNotFoundException. El shim de E2B (rayito.e2b.Volume/AsyncVolume) hace CRUD real sobre E2B(volume_store=...) (volume_id es el nombre del volumen, el mismo que reciben connect/get_info/destroy); sus operaciones de contenido (UnimplementedError("volume.content")) siguen sin plano de datos, y Sandbox.create(volume_mounts=) monta con E2B(volume_store=..., volume_connector_arn=...): ese sandbox sale sólo por el conector del volumen y allow_internet_access=True explícito es InvalidArgumentException. Componente rayito stack {deploy,status,destroy} efs-volumes (infra/efs-volumes.yaml: sistema de ficheros EFS cifrado, un mount target por subred de SubnetIds, grupos de seguridad NFS nuevos y conector de egress dedicado, sólo dentro de una VPC que ya existe). EfsVolumes/AsyncEfsVolumes: check(vpc_id=, subnet_ids=) comprueba la VPC sin crear nada (sólo ec2:Describe*: AZs distintas, IPs libres, DNS y ruta por defecto de cada subred, a un NAT o a otra puerta como un transit gateway; también rayito doctor --efs-vpc-id ... --efs-subnet-ids ...), deploy() se niega si algún hallazgo es FAIL, volume_store() da un VolumeStore sobre la pila, y destroy(delete_file_system=True)/ delete_file_system(id) borran el sistema de ficheros conservado (sólo uno con la etiqueta rayito=efs-volumes). AccessPointArns acota la política del execution role a access points exactos y deploy(read_only_access_point_arns=...) (ReadOnlyAccessPointArns) le deniega ClientWrite a los de sólo lectura: la opción ro del montaje no basta, porque el usuario del sandbox alcanza el puerto local de efs-proxy (Q133). El hallazgo internet-egress de check() explica que un sandbox con volumen sólo tiene internet por la VPC. list/get son eventualmente consistentes, como DescribeAccessPoints (medido en AWS real, AWS_API_NOTES.md §16 Q125: hasta 11 s en listar un access point nuevo y 8 s en dejar de listar uno borrado): create de un nombre que ya existe reintenta get hasta 30 s y destroy de un access point que el listado aún mostraba pero ya no existe devuelve False.

Changed

  • rayito doctor conoce la serie 0.7: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITY y docs/site/docs/limits.md) exige el rayd del tag rayd-v0.7.0 para el SDK 0.7, porque los volúmenes EFS y el endurecimiento del agente viven en rayd.

Security

  • Eventos de ciclo de vida (events-webhooks): una línea maliciosa ya no tira los eventos legítimos de su lote. El forwarder verifica el MAC con la clave del sandbox que nombra el log stream antes de leer el contenido, parsea de forma estricta y nunca falla la invocación por una línea: la cuenta (malformed_line, mac_invalid, stale_event, internal_error) y sigue. Si falla una escritura en la tabla, el resto del lote se escribe igual y el lote va a la nueva cola ForwarderFailuresQueue (destino OnFailure de su invocación asíncrona) en vez de perderse. El nombre del stream deja de presentarse como prueba de identidad: sólo el MAC autentica (SECURITY.md T22).
  • Eventos: sólo lo que emite rayd, y reciente. Tras el MAC, el evento debe ser del mismo sandbox que el stream, con un event_id de 32 hex (nunca un id del reconciliador), kill_reason sólo en killed y sólo request, un ARN de microvm-image y un occurred_at_ms de menos de 24 h (y no más de 5 min en el futuro): una línea capturada y repetida cuando su fila de deduplicación ya caducó se rechaza. La deduplicación de entregas pasa a DELIVERY#<sandbox_id>#<event_id>, así que un sandbox no puede marcar como entregado el evento de otro (las entregas en curso al actualizar la pila pueden repetirse una vez).
  • Webhooks: uno roto o lento ya no bloquea a los demás. El deliverer trata una URL que no puede parsear como un fallo permanente de ese webhook (invalid_url), aísla cualquier error inesperado a su webhook (internal_error) y el plazo de cada intento cubre la resolución DNS y la respuesta entera. register_webhook rechaza con InvalidArgumentException una URL que el deliverer no podría alcanzar (puerto fuera de 1-65535, host inválido o no codificable en IDNA, espacios o barras invertidas), con el mismo criterio en los dos SDKs.
  • Código de las pilas opcionales: nunca se fía de un objeto que ya exista. OptionalStacks.deploy() sube el zip a rayito/stacks/<componente>/<sha256>.zip (dentro del espacio rayito/ que protegen las plantillas de IAM, ya no en la raíz del bucket), manda ExpectedBucketOwner con la cuenta del llamante y, si el objeto ya existe, compara su contenido antes de reutilizarlo (si no coincide, lo sobrescribe con ChecksumSHA256 y avisa por el logger).
  • infra/iam.yaml: el lanzador ya no puede publicar imágenes. Nuevas políticas SandboxLauncherPolicy (sólo lanzar y manejar sandboxes: la del rol de un servicio en producción) e ImagePublisherPolicy (sólo publicar); CallerPolicy sigue igual, como su unión. El Deny del execution role sobre los artefactos de imagen existe ya siempre, no sólo con PersistenceBucket.
  • infra/templates.yaml: RayitoTemplateBuilder sólo lee rayito/* del bucket de artefactos o de la imagen base, nunca el bucket entero (que puede guardar los HOME persistidos y las transferencias).
  • Secretos de firma de webhooks fuera de secrets=. RayitoSecretsReader niega rayito/webhooks/* y el SDK se niega a leer esos secretos por secrets=/SecretCache (InvalidArgumentException). SecretPrefix de infra/secrets-access.yaml debe terminar en /.
  • Mínimo privilegio en events-webhooks: cada Lambda escribe sólo sus tipos de fila (dynamodb:LeadingKeys) y sólo en su propio log group (la pila crea uno por función y lo borra con ella), y el rol del Scheduler exige aws:SourceAccount.
  • Eventos: el código del sandbox ya no puede inundar la pila con paused/resumed. Esos dos eventos los emite rayd cuando corren sus hooks de suspensión y reanudación, que el código sin privilegios del sandbox puede invocar. El forwarder sólo admite ahora eventos que hagan avanzar el ciclo de vida de su sandbox (nada tras killed, ningún created repetido, ninguna posición repetida o anterior) y limita paused/resumed con un cubo por sandbox (20 seguidos, después uno cada 30 s); lo rechazado se cuenta como invalid_transition o rate_limited y nunca se guarda ni se entrega. El reconciliador consulta un índice disperso de sandboxes abiertos en vez de escanear la tabla entera, y el deliverer sólo se invoca por eventos nuevos. paused/resumed quedan documentados como orientativos (SECURITY.md T22). Al actualizar la pila, un sandbox que ya estaba abierto entra en el índice con su siguiente evento.
  • Webhooks: segunda firma con marca de tiempo. Cada entrega lleva además rayito-signature: t=<segundos>,v1=<HMAC-SHA256> sobre "<t>.<webhook_id>." y el cuerpo; la guía de eventos explica cómo verificarla con una ventana de 5 minutos. e2b-signature no cambia.
  • Webhooks: un secreto rotado deja de usarse en 5 minutos. El deliverer guardaba cada secreto en caché mientras viviera su contenedor; ahora lo relee cada 5 minutos y, si el receptor responde 401/403, lo relee en el acto y reintenta una vez con el nuevo.
  • Políticas del llamante por tarea en events-webhooks. La pila emite EventsLauncherPolicy (events=: sólo la clave del stack), EventsReaderPolicy (get_events: sólo filas de eventos; ya no puede registrar un webhook ni leer la clave del stack) y EventsWebhookAdminPolicy (register_webhook/listar/borrar: sólo filas WEBHOOK). La guía de eventos tiene la tabla de qué llamada necesita cuál y avisa de que los webhooks son de toda la pila.
  • sizes-guard: RayitoRunAllowedSizes niega también publicar imágenes. Sin ello, una identidad con la CallerPolicy estándar podía reconstruir una imagen permitida a un tamaño mayor y lanzarla sin salir de la lista. La política ya no dice que valga "sin importar lo demás": una versión antigua y mayor bajo un nombre permitido sigue siendo lanzable con imageVersion (SECURITY.md T27).
  • SECURITY.md: filas T20, T23, T26 y T27, que los cambios de M15 planearon y nunca se aplicaron (infra/otlp-export.yaml ya citaba T23).
  • infra/ci-oidc-role.yaml y infra/README.md piden una cuenta dedicada a e2e (o imágenes de test propias): el rol puede acuñar tokens y terminar cualquier sandbox de las imágenes de TestImageArns.
  • SecretGateway rechaza una ruta de allow que rayd nunca dejaría pasar (sec-rayd-agent-hardening): las rutas de allow siguen ahora la misma lista de permitidos que rayd aplica a cada petición (caracteres de ruta de RFC 3986 salvo ;, sin segmentos vacíos salvo el último, y cada segmento decodificado una sola vez UTF-8 válido, sin /, \, %, ; ni bytes de control y distinto de ./..), y una que no la cumpla lanza InvalidArgumentException antes de cualquier llamada, con los mismos vectores compartidos que rayd (testdata/secret-gateway/request-paths.json).
  • rayito sandbox proxy comprueba Host y Origin. El proxy añade tu token a todo lo que reenvía, así que una web abierta en tu navegador podía usar el servicio del sandbox (DNS rebinding, un POST entre sitios o un WebSocket de otro origen). Ahora sólo reenvía si Host es de loopback con el puerto local, la dirección de --bind, <id>.localhost o un nuevo --allowed-host (si no, 421), y si el Origin, cuando viene, es uno de esos orígenes o un nuevo --allow-origin (si no, 403). Cambio: --bind 0.0.0.0/:: exige --allowed-host, y un cliente que mande otro Host (p. ej. un proxy inverso) necesita --allowed-host. Como mucho --max-connections (8) conexiones a la vez (503 después). Anuncia también http://<id>.localhost:<puerto>, con cookies separadas de tus otras apps locales.
  • rayito sandbox proxy reenvía un solo mensaje por conexión: la primera petición, con su cuerpo delimitado por Content-Length o chunked; lo que el cliente mande después por esa conexión ya no llega al upstream sin reescribir, y un upgrade al que el sandbox no responde 101 se reenvía con Connection: close y se cierra en vez de quedar como túnel sin filtrar. Cambio: una delimitación ambigua (Transfer-Encoding junto a Content-Length, un Transfer-Encoding que no acaba en chunked, varios Content-Length distintos) es 400.
  • .dockerignore con la semántica de Docker en Template.build: anclado en la raíz, ** como cero o más directorios (así **/.env, **/.git y el resto de los valores de docker init excluyen también los de la raíz, que antes acababan en la imagen), un directorio excluido excluye lo de dentro y la última coincidencia gana. Cambio: * y ? ya no cruzan / (*.pyc sólo excluye los de la raíz; usa **/*.pyc). Un UserWarning (con las rutas, nunca el contenido) avisa si el contexto va a empaquetar .env*, .git/, .aws/, .ssh/, *.pem o *.key.
  • Template.build no sigue enlaces simbólicos dentro de un directorio copiado (ni a fichero ni a directorio), como Docker y el SDK de TypeScript (el zip de rayito image zip/publish los rechaza, ver más abajo): un enlace a un fichero de tu máquina ya no acaba en el artefacto de S3 ni en la imagen. Cada fichero del contexto se vuelve a comprobar dentro del contexto y se abre con O_NOFOLLOW justo antes de leerlo.
  • Credenciales de git: clone/push/pull con username/password restauran siempre la URL sin credenciales (también si vence la orden o la restauración), conservan el error de la operación y avisan por el logger rayito.git, sin la URL, si el token puede seguir en .git/config. Esas órdenes corren sin hooks ni credential helpers y se niegan (GitAuthException) si la configuración de git reescribe URLs (url.*.insteadOf). La documentación dice ahora que el token queda al alcance del código del sandbox: usa tokens de vida corta y de un solo repositorio.
  • Errores de AWS saneados en más caminos: los stacks opcionales encadenan el resumen saneado y no el ClientError crudo; Template.build traduce un rechazo de AWS a BuildException(reason="aws_error") (antes subía el ClientError); el manejador de errores de la CLI, rayito doctor y rayito prune redactan el mensaje de AWS (un error de firma repite la cadena canónica con el token de sesión).
  • Salida acotada en memoria: cada descriptor de un comando o una PTY guarda como mucho 64 MiB (COMMAND_OUTPUT_MAX_BYTES); se conserva el final y CommandResult.truncated/CommandExitException.truncated lo indican. commands.run/connect aceptan max_output_bytes (0 no guarda nada; los callbacks reciben siempre todo) y rayito sandbox exec ya no guarda la salida que imprime.
  • La CLI neutraliza secuencias de escape: hacia una terminal, los caracteres de control de logs de CloudWatch, logs de build y mensajes de AWS salen como \xNN visibles. rayito sandbox logs sólo usa un stream que no es el esperado si tiene exactamente la forma YYYY/MM/DD[<versión>]<id> y un día no anterior al arranque, y avisa cuando lo hace.
  • Access token mínimo: un token propio (access_token=, RAYITO_ACCESS_TOKEN, --token-file) tiene que decodificar a al menos 16 bytes (ACCESS_TOKEN_MIN_BYTES). Cambio: uno más corto, aceptado antes, ahora es InvalidArgumentException. Los generados (32 bytes) no cambian.
  • ProxyToken y el contexto de rayito doctor ya no muestran el JWE en repr.
  • Sandbox.create(persist=) y AsyncSandbox.create(persist=) ligan el sandbox a su prefijo de persistencia (C-07): el runHookPayload lleva el bucket y la base prefix del S3Prefix (nunca prefix/name), y un rayd con la corrección rechaza con permission_denied cualquier checkpoint o restore fuera de esa base. Con un prefix por inquilino, el token de un sandbox ya no alcanza el HOME persistido de otro aunque compartan execution role. Cambia un uso: un restore o checkpoint explícito hacia otra base desde un sandbox creado con persist= ahora falla con permission_denied. No añade llamadas a AWS.
  • La wheel y el sdist se hashean justo después de uv build, antes de que check_wheel.py o twine ejecuten nada, y se vuelven a comprobar al final del job: el sha256sum -c del job que publica ahora prueba que lo publicado es lo que produjo uv build. twine y su grafo se instalan desde requisitos con --hash (.github/release/requirements-twine.txt), uv va fijado por versión y checksum, y la caché de uv está apagada en la release.
  • La release comprueba que dist/ es exactamente la wheel y el sdist del tag antes de subirlo y antes de publicarlo: sha256sum -c no detecta un fichero que no esté en SHA256SUMS, y la acción de publicación sube todo lo que haya en el directorio.
  • rayito image zip y make image-publish rechazan enlaces simbólicos en el árbol de la imagen y en el sidecar (salvo dentro de los directorios que nunca viajan, como .venv): un enlace metía en todas las imágenes el contenido de un fichero de la máquina que construye, legible por el usuario del sandbox.

Deprecated

  • EventsOperatorPolicy (salida OperatorPolicyArn de events-webhooks): sigue siendo la unión de las tres políticas nuevas durante esta versión. Vincula a cada identidad la suya (EventsLauncherPolicy, EventsReaderPolicy o EventsWebhookAdminPolicy).

0.6.1 - 2026-10-04

Fixed

  • Redesplegar una pila opcional ya no deshace su configuración (OptionalStacks.deploy()/AsyncOptionalStacks.deploy() y rayito stack deploy): los valores por defecto del catálogo sólo se aplican al crear la pila; al actualizarla, cada parámetro que no vuelves a pasar y la pila ya tiene se manda con UsePreviousValue. Antes, por ejemplo, redesplegar s3-mounts sin repetir Prefixes volvía a '*' (todo el bucket), metadata-index sin TableName reemplazaba (y borraba) la tabla y secrets-access sin KmsKeyArn quitaba kms:Decrypt. Nuevo parameter_changes() para ver qué cambiaría sin desplegar; rayito stack deploy lo imprime antes de pedir confirmación.
  • Guardián SSRF de los webhooks (pila events-webhooks): una dirección IPv6 que encapsula una IPv4 (::ffff:100.64.0.1, de un registro AAAA) se clasifica como esa IPv4, así que el rango CGNAT queda bloqueado también por esa vía. Se regenera el zip de la Lambda que el SDK sube.
  • volumes=/domain= lanzan UnimplementedError con "todavía no disponible" en vez de "llega en 0.6"; rayito domain (pendiente) ya no aparece en rayito --help; rayito template build|status|logs, rayito stack y rayito events tienen texto de ayuda.
  • reincarnate() reaplica todas las secciones de ConfigureSandbox (m15-reincarnate-configure-replay, sync y async): hasta ahora sólo reenviaba gateways= y un sucesor perdía mounts=, events= y telemetry=. create() guarda sus opciones 0.6 en LaunchOptions.features (relaunch_features: todas menos size, que ya va en el ARN de la imagen) y reincarnate() las reenvía campo a campo (feature_kwargs) al create() del sucesor, que las aplica por el mismo camino en su único Configure: events= deriva k_sbx del nuevo sandbox_id, mounts= espera otra vez a mounted y telemetry= usa la imagen y la memoria del sucesor. LaunchOptions.gateways pasa a LaunchOptions.features.gateways (atributo interno).

Documentation

  • Bloques "Coste y activación" completos para mounts/S3Mount y size/el catálogo de tamaños, y filas de las seis funciones 0.6 en la tabla de funciones opcionales; la referencia de la CLI, la paridad con E2B, la guía de migración, limits.md y las notas de 0.6.0 ya no describen como pendientes funciones publicadas en 0.6.0.
  • Metadatos del paquete: la URL Documentation apunta al sitio de documentación y se añade Issues; el README del paquete termina con la licencia, el NOTICE incluido y la nota de marcas (proyecto independiente, no afiliado a E2B). El NOTICE conserva además el copyright de E2B en la atribución del código git adaptado (Apache-2.0 §4(c)).

0.6.0 - 2026-10-03

Added

  • Convenio OptionalStack y ConfigureSandbox (v06-foundations, M15 foundations, ADR-015/ADR-016, opcional y apagado por defecto): el servicio OptionalStacks (deploy/status/destroy/components) y la CLI rayito stack sobre un catálogo de nueve componentes (metadata-index y secrets-access, migrados sin cambios de comportamiento; los otros siete son stubs hasta su propia función); las siete opciones 0.6 de Sandbox.create() (mounts, volumes, size, events, telemetry, gateways, domain) existen ya en la firma y lanzan UnimplementedError nombrando el cambio que las trae mientras sigan siendo un stub, antes de run-microvm; en cuanto una deja de serlo (mounts=, ver más abajo), create()/take() ejecutan sus configure_sections justo tras el primer Health (_configure_base: require_capabilities, build_configure_request, check_configure_response), dentro del mismo camino que ya termina el sandbox ante cualquier fallo anterior a agent_ready. Fila de compatibilidad "0.6" en rayito.cli._compat.COMPATIBILITY. Sin ninguna opción nueva, el comportamiento es byte a byte el de 0.5.x (traza de oro en tests/unit/fixtures/zero_cost_0_5_trace.json).
  • mounts= (m15-s3-mounts, ADR-017, experimental, apagado por defecto): Sandbox.create(mounts=)/AsyncSandbox.create(mounts=) monta uno o más buckets S3 (S3Mount(bucket=, prefix=, read_only=True, allow_overwrite=False, allow_delete=False), exportado desde rayito) en el guest con mount-s3/FUSE, sólo sobre rayito-base-caps (require_caps_for lo exige antes de run-microvm cuando la imagen se nombra directamente; sobre un ARN opaco la decisión se difiere a Health.features tras /run, que termina el sandbox si falta la capacidad). create() no vuelve hasta que cada montaje está montado: sondea ConfigureStatus (como mucho 15 s) y, si uno falla o no se asienta, termina el sandbox y lanza MountException. sbx.mounts (propiedad) da el estado en vivo de cada montaje ("pending"/"mounted"/"failed", ConfigureStatus en cada lectura); una sección rechazada o un montaje fallido lanza MountException con un code cerrado (network, iam_denied, not_found, not_allowed, invalid_path, helper_missing, timeout). rayd lanza mount-s3 como el usuario dedicado rayito-mount (uid 990) con credenciales resueltas por su propio acceso a IMDS, nunca en argv ni en entorno; un daemon caído se relanza solo, con backoff. Política IAM RayitoS3MountAccess del componente OptionalStack s3-mounts (infra/s3-mounts.yaml, desplegable con rayito stack deploy s3-mounts --param BucketName=... --param Prefixes=..., pide CAPABILITY_IAM), acotada al bucket y a sus prefijos (hasta 4) también para leer, escribir y borrar objetos. El bucket debe estar en RAYITO_ALLOWED_MOUNT_BUCKETS de la imagen (rayito image publish --env, de m15-sizes-catalog).
  • Catálogo de tamaños, aceptación en AWS real (m15-sizes-catalog): rayito image publish --env/--sizes vuelve a reutilizar una versión ya construida con la misma configuración: list-microvm-image-versions no devuelve environmentVariables (Q118), así que antes toda imagen con variables (cada imagen con sufijo de tamaño) se reconstruía en cada publicación; ahora se confirman con GetMicrovmImageVersion (gratuita), sólo cuando la publicación lleva variables. rayito image sizes acepta --image-name, como publish, para listar una familia publicada con nombre propio.
  • Catálogo de tamaños (m15-sizes-catalog, ADR-019, opcional y apagado por defecto): Sandbox.create(size="4gb")/SizeRequest(memory_mib=...) resuelve, enteramente en cliente y sin ningún RPC, al primer tamaño del catálogo cerrado (512mb/1gb/2gb/4gb/8gb, Q87) que cubra lo pedido (redondea siempre hacia arriba, avisa con RayitoCompatWarning si no encaja exacto) y antepone el sufijo de imagen (rayito-base-4gb) antes de resolver el ARN; size= con un template dado por ARN, o por encima del máximo publicado, es InvalidArgumentException antes de cualquier llamada a AWS. rayito image publish --sizes 512mb,4gb publica, desde el mismo artefacto, una imagen adicional por tamaño (en oleadas de hasta 10 construcciones simultáneas), horneando RAYITO_BASELINE_MEMORY_MIB en la imagen (nunca un interruptor de activación); --env KEY=VALUE añade cualquier otra variable de imagen. get_info() confirma, con una única llamada cacheada a GetMicrovmImageVersion por versión de imagen, SandboxInfo.baseline_memory_mib y baseline_cpu (vCPU medido exactamente para los cinco tamaños del catálogo); cpu_count/memory_mb siguen siendo lo que el guest reporta de verdad. El shim rayito.e2b reporta ese mismo baseline en SandboxInfo.cpu_count/memory_mb cuando size= se usó (como E2B reporta lo declarado por el template), la vista real del guest si no. rayito image sizes [--variant] lista, por tamaño del catálogo cerrado, qué imagen ya se publicó (list-microvm- images) y si comparte artefacto con el baseline (sameArtifact, una GetMicrovmImageVersion sin cuota propia por imagen ya publicada, nunca lanza ningún sandbox). Guardarraíles de coste opcional rayito stack deploy sizes-guard (RayitoRunAllowedSizes: un Deny de lambda:RunMicrovm fuera de los ARN de imagen permitidos, efectivo aunque la identidad ya tenga el microvm-image:* de la CallerPolicy estándar). Sin size=, el comportamiento sigue siendo exactamente el de 0.5.x.
  • LifecycleEvents/AsyncLifecycleEvents (m15-events-webhooks, opcional y apagado por defecto): despliega infra/events-webhooks.yaml (deploy/status/destroy, componente events-webhooks de OptionalStacks), registra webhooks compatibles con E2B (register_webhook/list_webhooks/delete_webhook) y lee el historial (get_events, 1–100 filas, filtrado por tipo en DynamoDB). CLI rayito events deploy|status|destroy|list y rayito events webhook add|list|remove, con el mismo bloque de coste y confirmación que rayito stack. Los errores de AWS llegan como WebhookException con sólo el código (aws_code). events= en Sandbox.create()/ AsyncSandbox.create() valida el tipo y que logging llegue a CloudWatch antes de lanzar y, tras run-microvm, manda la clave del sandbox (k_sbx, derivada de la clave del stack y el sandbox_id; un GetSecretValue por instancia de LifecycleEvents) en el mismo ConfigureSandbox que mounts=/gateways=/telemetry= (LifecycleEventsSectionFactory, ADR-020); sin la pila desplegada, sin lifecycle_events en el agente o con la sección rechazada, termina el sandbox (salvo keep_on_failure). Sin events=, ni un cliente DynamoDB/Secrets Manager nuevo ni una llamada ConfigureSandbox. rayito events deploy --tag K=V (repetible) para cuentas cuya organización exige etiquetas al crear recursos. Aceptado en AWS real (AWS_API_NOTES.md Q105–Q108): el forwarder exige que el log stream termine en ]<sandbox_id> (formato medido) y el forwarder y el reconciliador dejan una línea JSON por invocación con lo que aceptaron, rechazaron (por motivo) o sintetizaron.
  • telemetry=: exportación OTLP de rayd a CloudWatch (m15-rayd-otlp, ADR-021, opcional y apagado por defecto): TelemetryExport/OtlpAuth nuevos (OtlpAuth.execution_role(), exige rayito-base-caps; OtlpAuth.bearer(secret_name=...), experimental, funciona en rayito-base; el nombre se resuelve bajo rayito/ por la misma SecretCache que secrets=). Se envía como una sección de ConfigureSandbox tras /run; una imagen cuyo rayd no exporta termina el sandbox (salvo keep_on_failure=True) y lanza UnimplementedError. Con tracer_provider=, cada RPC del handle lleva además el traceparent del span de esa misma llamada hacia rayd (lo calcula un interceptor del canal en el hilo o la tarea que llama, no el AuthMetadataPlugin, que grpc ejecuta en un hilo suyo sin el contexto OpenTelemetry del llamante); sin él, ninguna cabecera nueva. El token de OtlpAuth.bearer(...) es una API key de CloudWatch Metrics. sbx.get_telemetry_status() (síncrono y AsyncSandbox) lee ConfigureStatus, como una llamada explícita aparte de get_health(). Sin telemetry=, ningún comportamiento cambia frente a 0.5.x (traza de oro sin tocar).
  • Templates declarativos (m15-templates, ADR-022, opcional y apagado por defecto): Template/AsyncTemplate compilan un DSL (igual al Template de E2B v2) a un Dockerfile y un zip deterministas sobre una imagen rayito-base/rayito-base-caps ya publicada; Template.build()/build_in_background()/get_build_status()/ exists() suben el artefacto por hash de contenido y llaman a create/update-microvm-image, reutilizando una versión idéntica en vez de reconstruir. Un build fallido se explica con BuildException (step/command/exit_code/log_tail del log de BuildKit, o reason="ready_client_error"|"ready_server_error" si falló el ready_cmd), sin repetir nada. from_image/from_template/ from_dockerfile/from_gcp_registry/apt_install lanzan UnimplementedError (documentados en ADR-022). El shim rayito.e2b.Template/AsyncTemplate ya construye de verdad, con la firma de E2B (alias, skip_cache, memory_mb redondeado con RayitoCompatWarning, cpu_count con aviso) y E2B(bucket=...); TemplateException/BuildException del shim pasan a ser las clases nativas. set_start_cmd() necesita una imagen base con rayd 0.6. La imagen compuesta hereda la configuración de la base (additionalOsCapabilities incluida); skip_cache() equivale a force=True; la cuota de builds de AWS llega como reason="build_quota". El núcleo de build de imágenes pasa a rayito._images, compartido con rayito image publish (sin cambio de comportamiento). Sin llamar a Template.build(), el SDK no crea ningún cliente nuevo. infra/templates.yaml (rayito stack deploy templates): sólo la política IAM RayitoTemplateBuilder, $0 en reposo, que no puede sobrescribir las imágenes base publicadas. Aceptación en AWS real: pip_install() compila a python3 -m pip install --no-cache-dir --break-system-packages (rayito-base no tiene pip en el PATH); la versión gestionada que se hereda se envía como 1, no como el eco 1.0 que create-microvm-image rechaza; la política concede CreateMicrovmImage sobre * (AWS no la autoriza por ARN) y lambda:PassNetworkConnector sobre los conectores gestionados, y su Deny cubre UpdateMicrovmImage sobre las bases (AWS_API_NOTES.md Q114-Q116).
  • gateways= — pasarela de secretos en loopback (m15-secrets-gateway, M15, ADR-023, opcional y apagado por defecto): Sandbox.create(gateways= {"nombre": SecretGateway(upstream=..., headers=..., allow=..., ...)}) abre, dentro del agente, un listener de loopback por ruta que reenvía sólo lo que su allow cubre, dentro de su límite de peticiones por minuto, inyectando cada cabecera vaultada (resuelta de Secrets Manager con la misma SecretCache que secrets=, nunca antes de Configure) y eliminando primero cualquier cabecera del mismo nombre que el sandbox intente poner. sbx.gateways["nombre"].url da la URL de loopback; refresh()/arefresh() rotan el secreto sin recrear el sandbox (releen Secrets Manager aunque la SecretCache no haya vencido, conservan el puerto y lanzan si rayd rechaza la sección). También SandboxPool.take(gateways=)/AsyncSandboxPool.take(gateways=). Cualquier fallo al configurarla tras run-microvm (imagen anterior a 0.6.0, flag ausente, secreto que falta, sección rechazada) termina el VM salvo keep_on_failure. Sin gateways=, ningún cliente secretsmanager nuevo se construye y no se manda ningún ConfigureSandbox.

Fixed

  • rayito stack deploy events-webhooks --artifact-bucket B (y OptionalStacks.deploy("events-webhooks", artifact_bucket=B)) ya no exige repetir --param ArtifactBucket=B: StackArtifact.bucket_parameter_key pasa a la plantilla el mismo bucket al que se sube el código, y un valor distinto es InvalidArgumentException antes de subir nada (aceptación 0.6 en AWS real).

0.5.1 - 2026-10-01

Fixed

  • Los mensajes de error y los docstrings del SDK nombran la versión de la imagen o del SDK que hace falta (por ejemplo, "necesita una imagen 0.3.0 o posterior"), en lugar del hito interno (M9, M12…).

Documentation

  • Sitio de documentación reorganizado por tareas: primeros pasos, guías por función con ejemplos en Python y TypeScript, funciones opcionales con su coste, migración desde E2B y referencia (Python, TypeScript, CLI, errores y variables de entorno). Los ejemplos se comprueban en CI.

0.5.0 - 2026-10-01

Added

  • Spans OpenTelemetry del lado del SDK (m13-otel-sdk, opcional y apagado por defecto, ADR-014): tracer_provider= en Sandbox.create() (también con pool=), connect() (ambas formas) y las variantes de clase kill/pause/resume; instrumenta commands.run, run_code, files.* y kill/pause/resume de instancia con spans rayito.* de SpanKind.CLIENT. Sin tracer_provider= no se importa opentelemetry (extra otel, pip install rayito[otel]) ni se crea ningún span (NOOP.span() devuelve siempre el mismo nullcontext; sólo queda el diccionario de atributos trivial de cada llamada). Lista cerrada de atributos (nunca texto de comandos, código, rutas, envs, secretos ni metadata); un error del span lleva el nombre de la clase de la excepción, nunca su mensaje. $0 de AWS; el coste (si lo hay) es el del backend de exportación del llamante. Fuera de alcance: propagación traceparent/tracestate hacia rayd, telemetría del sandbox y el shim de E2B.

  • Índice de metadatos sobre DynamoDB (m14-metadata-index, opcional y apagado por defecto, ADR-014): DynamoDbIndex(table_name, region=, session=, on_write_failure="terminate", ttl_margin_seconds=3600) y la opción index= en Sandbox.create(), Sandbox.list(), Sandbox.paginate() (y sus gemelos asíncronos) y PoolConfig. create() escribe una fila inmutable (PutItem condicional) tras run-microvm; con metadata= e index=, list()/paginate() unen list-microvms con BatchGetItem por página y filtran también sandboxes SUSPENDED sin ninguna sonda de Health, token ni get-microvm. Nuevas excepciones SandboxIndexException e IndexWriteException. Coste: ~1 WRU por sandbox creado y 0,5 RRU por candidato listado (DynamoDB on-demand); la tabla la despliegas tú con infra/metadata-index.yaml. reincarnate() conserva index=: el sucesor escribe su propia fila.

  • Shim de E2B: Sandbox.list(query=SandboxQuery(metadata=..., state=[PAUSED]), index=...) y E2B(index=...) filtran sandboxes en pausa por metadatos con el índice.
  • CLI: rayito sandbox list --metadata K=V --state suspended --index-table TABLA (y --state sola); --index-table sin --metadata es un error de uso (salida 2); sin --index-table, sin cambios.
  • Secretos sobre AWS Secrets Manager (m13-secrets, opcional y apagado por defecto, ADR-014): SecretStore(region=, session=, prefix="rayito/", kms_key_id=) con create/update/get_info/ exists/list/destroy (versión entera en ClientRequestToken, metadatos en Description, borrado sin ventana de recuperación, reintento acotado de create tras un borrado reciente) y SecretCache(ttl_seconds=300) (clave por región, sesión, secreto y versión; una sola lectura en vuelo por clave; los aciertos no llaman a AWS; refresh()/invalidate(); nunca muestra valores). Coste: $0,40/secreto-mes hasta destroy + $0,05/10 000 llamadas.
  • secrets= / secret_cache= en Sandbox.create() (también con pool=), connect(), SandboxPool.take() y, por llamada, en commands.run, pty.create, run_code (contextos Python) y create_code_context, síncrono y asíncrono: entregan el valor como variable de entorno por los envs que ya viajan a rayd (sin RPC nueva, vale con imágenes 0.4.0), nunca en el runHookPayload, metadata, logs ni errores. Se resuelven antes de run-microvm. La primera vez avisan con RayitoCompatWarning: el valor es visible para el código del sandbox.
  • Shim de E2B: Secret/AsyncSecret, SecretInfo, SecretPaginator/ AsyncSecretPaginator, SecretException/SecretNotFoundException sobre SecretStore, con los nombres de e2b 2.51.0; E2B(...).Secret usa su región y su sesión. fill() devuelve el placeholder, que nada resuelve; iam_token sigue en UnimplementedError.

Changed

  • El motivo de UnimplementedError de list(state=PAUSED, query.metadata) en el shim de E2B nombra ahora la opción index=DynamoDbIndex(...).
  • RayitoCompatWarning vive ahora en rayito.exceptions (y se re-exporta desde rayito.e2b.exceptions: es la misma clase).
  • rayito.e2b.Secret/AsyncSecret y E2B(...).Secret ya no lanzan UnimplementedError: llaman a Secrets Manager cuando se usan.

  • rayito sandbox proxy ID --port N (m12-sizes-proxy): expone un puerto del guest en http://<bind>:<local-port> (--local-port, por defecto igual a --port; --bind, por defecto 127.0.0.1; --allow-remote para escuchar fuera de loopback) sin coste de AWS más allá de GetMicrovm + CreateMicrovmAuthToken (gratuitos). Reutiliza el TokenRefresher/TokenStore del SDK (mismo refresco a los 45 min); siempre acuña PortSpec.single(N), nunca allPorts; rechaza el puerto 9000 (hooks, ADR-006) antes de llamar a AWS. Quita cualquier cabecera x-aws-proxy-* del cliente, fija Host/X-aws-proxy-auth/ X-aws-proxy-port y fuerza Connection: close salvo en peticiones de upgrade (Upgrade + Connection: upgrade; el paso de WebSocket está implementado, no medido contra AWS). Sin JWE vigente o sin conexión al sandbox responde 502 (motivo en stderr, sin JWE ni ruta); lectura de cabecera y conexión al sandbox con tiempo máximo de 30 s. Nunca registra el JWE, las cabeceras, los cuerpos ni las rutas. El parseo de la cabecera del cliente es HTTP/1.1 estricto: rechaza (400 y cierra) cualquier CR/LF suelto fuera de un \r\n (contrabando de cabeceras que podía colar un x-aws-proxy-* falso), obs-fold y nombres fuera de los tchar de RFC 9110. --port y --local-port se validan y el socket local se reserva antes de GetMicrovm/ CreateMicrovmAuthToken, así que un puerto inválido u ocupado falla limpio (mensaje con el puerto, salida 1, sin traceback) sin gastar ninguna llamada. Sin dependencias nuevas.

Fixed

  • SecretStore.destroy() (y Secret.destroy/AsyncSecret.destroy del shim de E2B) devolvía True para un nombre que nunca existió, porque AWS acepta el DeleteSecret forzado de un nombre inexistente sin ResourceNotFoundException (aceptación de 0.5.0 en AWS real). Ahora hace DescribeSecret antes y devuelve False sin borrar si no existe (o ya estaba programado para borrarse, o lo borró otro a la vez), como documenta y como E2B. Usa secretsmanager:DescribeSecret, ya incluido en RayitoSecretsAdmin.
  • SecretStore.create() sobre un nombre recién borrado agotaba su presupuesto de reintentos de 30 s: AWS liberó el nombre tras 19–28 s y el backoff no llegaba. El presupuesto es ahora de 60 s (CREATE_RETRY_BUDGET_SECONDS) con ±25 % de jitter.
  • El e2e de secretos (SEC-10) ponía el logger raíz a DEBUG y capturaba los cuerpos de botocore con el valor del secreto; ahora captura sólo el logger rayito. docs/site/docs/secrets.md avisa de que el DEBUG de botocore/urllib3 imprime los valores (Rayito nunca lo hace) y de que list() es eventualmente consistente (~3–5 s).

0.4.0 - 2026-09-30

Fixed

  • rayito doctor tiene la fila 0.4 de la tabla de compatibilidad (SDK 0.4 exige rayd 0.4.0); sin ella la comprobación compatibility daba FAIL.

  • files.read() de un fichero grande enrutado por S3: si el plazo (request_timeout/stream_idle_timeout) vencía justo cuando el get_object seguía en vuelo, el SDK seguía lanzando ese get_object tras una cancelación ya vista, aunque el resultado tardío se descartaba y el llamante siempre recibía TimeoutException. ObjectFetch.run comprueba ahora la cancelación antes de abrir el cuerpo, así que no sale ningún get_object de más una vez vista la cancelación.

Cambios que rompen

  • LifecycleUnsupportedException pasa a ser subclase de UnimplementedError (ya no de InvalidArgumentException/SandboxException), y el kernel ausente en run_code/create_code_context (y cualquier RPC que el agente no implemente, vía la tabla unaria genérica de _transport.py) deja de ser InvalidArgumentException/SandboxException: ahora es UnimplementedError en los tres casos, un único tipo para "esta feature no está disponible en este sandbox". Migración: cambia except InvalidArgumentException/except SandboxException alrededor de esas llamadas por except rayito.UnimplementedError (o except LifecycleUnsupportedException, que ahora es subclase suya); MetricsHistoryUnavailable sigue siendo UnimplementedError para quien la captura, pero su __cause__ cambia: ya no es la RpcError de gRPC directamente, sino el UnimplementedError genérico de la tabla unaria (cuya propia __cause__ sí es la RpcError; __cause__.__cause__ para llegar a ella). rayito.e2b no cambia de superficie: seguía lanzando UnimplementedError en los tres casos con el mismo feature, reason y doc; sólo se simplifica su traducción interna. La pista "publica una imagen con una versión actual de rayd" de unimplemented_rpc_error sólo sale para un RPC de verdad ausente: un kernel ausente en run_code/create_code_context ya nombra rayito-base-poly en el propio detalle de rayd, así que ya no repite un consejo que no aplica (la imagen ya está actualizada).

Changed

  • sbx.kill(), sbx.pause() y sbx.connect() (rayito.e2b, llamadas de instancia) avisan ahora con RayitoCompatWarning cuando se les pasa headers=, proxy= o retries= (y sbx.kill()/sbx.pause() también con request_timeout=), en vez de perderlos en silencio: esta llamada opera sobre el canal y el plano ya construidos de este sandbox y no puede reconstruirlos; usa Sandbox.<kill|pause|connect>(sandbox_id, ...) si necesitas aplicarlos.

0.3.3 - 2026-09-29

Security

  • .github/workflows/release.yml: el job python se parte en python-build (sin environment ni id-token, el único que hace checkout y corre uv build) y python-publish (con el token OIDC de PyPI, sin checkout y sin uv, que verifica con sha256sum -c que el dist/ descargado es el que produjo python-build antes de publicar) — cierra C-10/H-02 residuo (docs/SECURITY_AUDIT.md). Sin cambio de comportamiento en lo publicado ni en el Trusted Publisher de PyPI.

Changed

  • Shim E2B: native_call_kwargs (rayito.e2b._compat) ya no lee ConnectionConfig.current_integration() ni construye un plano de control compartido (shared_control_plane) — la construcción con AWS se mueve al nuevo bind_control_plane (rayito.e2b._connection), el único punto de I/O; native_call_kwargs recibe integration como parámetro y sólo describe, en NativeCall.plane_settings, si hace falta un plano nuevo. snapshot_config ya no escribe el atributo privado de ConnectionConfig desde fuera de la clase; ahora pasa por el nuevo ConnectionConfig._snapshot. Sin cambio de comportamiento observable ni de ninguna firma pública (ConnectionConfig, Sandbox, AsyncSandbox, E2B); ver openspec/changes/m10-python-shim.

0.3.2 - 2026-09-29

Fixed

  • Reconexión tras un reset de stream del proxy de AWS (RST_STREAM antes del plazo real): grpcio lo presenta como CANCELLED "Stream removed" (AWS_API_NOTES.md #33), que is_stream_reset no clasificaba como corte reconectable (sólo miraba UNAVAILABLE/INTERNAL), así que commands.connect/pty.connect, watch_dir y el reenganche de run_code fallaban en vez de reconectar. Ahora CANCELLED con esa marca (u otra de STREAM_RESET_MARKERS) reconecta igual que un Socket closed; un CANCELLED sin la marca (el propio call.cancel(), p. ej. CommandHandle.disconnect()) nunca reconecta, y el chequeo del handle desconectado sigue yendo antes que la clasificación, así que un corte que llegara disfrazado de reset justo después de disconnect() tampoco reconecta (_transport.py).
  • rayito-mcp y python -m rayito.mcp sin el extra rayito[mcp]: antes fallaban con un ModuleNotFoundError sin capturar (traza completa) porque rayito.mcp/__init__.py importaba todo el paquete de forma ansiosa, así que cualquier import de rayito.mcp (incluido rayito.mcp.__main__) disparaba el fallo antes de que su propio try/except pudiera atraparlo. Ahora rayito.mcp resuelve sus cuatro nombres con __getattr__ perezoso (PEP 562) y rayito.mcp.__main__ imprime un aviso de una línea con el comando de instalación y sale con 2, sin traza, igual que la CLI rayito con rayito[cli].

Changed

  • tests/e2e/test_m3_filesystem.py: UPLOAD_BUDGET_SECONDS (20 s fijos para 8 MB) fallaba en uplinks lentos (0,25-0,44 MB/s medidos). El presupuesto ahora se deriva de una línea base medida en el propio run (una escritura de prueba de 300 KB) escalada a los 8 MB con un margen de 3x, acotada entre 5 s y 120 s: el test comprueba una regresión de throughput, no la red de quien lo corre.

0.3.1 - 2026-09-28

Versión de mantenimiento, sin cambios de API; se publica para mantener las versiones de los tres componentes sincronizadas.

Changed

  • boto3/botocore 1.43.103 en el lockfile de desarrollo y rango de uv-build ampliado a <0.13.

0.3.0 - 2026-09-24

Rayito 0.3.0 (M9, paridad con E2B 2.x). Notas completas en docs/RELEASE_NOTES_0.3.0.md; todo lo de M9 exige una imagen publicada con el rayd de M9.

Cambios que rompen

  • rayito.e2b pasa de E2B 1.x a 2.x y exige una imagen M9: create() siempre pide un plazo lógico a rayd y, contra una imagen anterior, termina el VM y lanza UnimplementedError("lifecycle"). timeout deja de ser la vida inmutable del MicroVM (ahora es el plazo lógico; el tope es max_lifetime). Detalle en "Changed (shim)".
  • rayito.e2b: sbx.pause() devuelve bool (antes el id del sandbox); files.watch_dir(path, on_event) y pty.create(size, on_data) toman el callback por nombre (el segundo posicional es user, como en E2B 2.x).
  • rayito.e2b: set_timeout, upload_url/download_url, get_metrics(start=, end=), list(next_token=) y allow_internet_access=False ya no lanzan UnimplementedError, y mcp=, network= y lifecycle= ya no son TypeError: el código que capturaba esos errores para degradar cambia de camino.

Added

  • Transferencias por S3 prefirmado (m9-file-transfer, ADR-010): S3Staging(bucket, prefix="rayito-transfer", region=None, max_expires_in=86400, threshold_bytes=8 MiB, multipart_threshold_bytes=5 GiB) (o RAYITO_TRANSFER_BUCKET/RAYITO_TRANSFER_PREFIX/RAYITO_TRANSFER_REGION; sin bucket por defecto), create(transfer=), connect(transfer=) y sbx.transfer. files.upload_url(path, user=, expires_in=3600, max_bytes=, form=) devuelve un UploadTicket (un str con la URL, más headers, fields, expires_at, wait(), status(), cancel(); de un solo uso, con la importación ya armada en el sandbox) y files.download_url(path, user=, expires_in=3600, filename=) un DownloadLink (str con size, sha256 y expires_at; una foto del fichero al llamar, multiparte desde multipart_threshold_bytes). Sandbox.upload_url/download_url(path, user, use_signature_expiration) con la forma de E2B. El SDK firma con tus credenciales (SigV4, host virtual regional, vida min(expires_in, max_expires_in, 604800)); rayd no guarda ninguna. repr nunca muestra la URL y pickle la rechaza. Nuevos TransferStatus, AsyncUploadTicket, TransferException(code, reason), FileUploadException y UnimplementedError(feature, reason, doc=None) nativo (sin staging: "configura transfer=S3Staging(...) o RAYITO_TRANSFER_BUCKET"; con una imagen anterior: "actualiza la imagen").
  • Ficheros grandes por S3: con transfer, files.write/write_files suben lo que mide al menos threshold_bytes (y todo stream binario no buscable) directamente a S3 y rayd lo importa verificando su sha256; files.read exporta y descarga verificando el sha256. Sin transfer, el camino gRPC no cambia. request_timeout (o 60 s + 1 s por MB) acota también la pata S3 y stream_idle_timeout rige entre trozos de la descarga en format="bytes"/"text": una subida o descarga colgada falla con TimeoutException sin esperar a los reintentos de botocore. create(pool=..., transfer=...) firma con la sesión boto3 del pool.
  • files.read(gzip=, stream_idle_timeout=), files.write/write_files(gzip=, metadata=, use_octet_stream=) y EntryInfo.metadata (mapa de sólo lectura, claves en minúsculas). gzip y metadata en una escritura exigen una imagen M9 (UnimplementedError antes de mover bytes); use_octet_stream se acepta sin efecto. Nuevos códigos de StreamError: resource_exhausted (DiskFullException o RateLimitException) y failed_precondition (InvalidArgumentException).

  • Plazo del servidor (m9-server-timeout, ADR-011; exige una imagen M9): create(max_lifetime=, on_timeout="kill" | "pause"). Con cualquiera de los dos, timeout es un plazo lógico que rayd hace cumplir aunque el cliente muera y max_lifetime (120–28 800 s, por defecto timeout + 60) es maximumDurationInSeconds. set_timeout(timeout) (instancia y clase, con el access token) lo fija con SetTimeout EXACT (puede acortarlo; más allá del tope, InvalidArgumentException) y connect(timeout=) (instancia y clase) lo alarga con AT_LEAST. on_timeout="pause" suspende al vencer y idle.auto_resume reanuda con un plazo nuevo de max(timeout, 300 s). Nuevos SandboxLifecycle (en get_health().lifecycle y get_info().lifecycle) y LifecycleUnsupportedException (una imagen anterior a M9 con un ciclo de vida pedido: el VM se termina salvo keep_on_failure). Sin max_lifetime ni on_timeout el cable y la semántica son los de siempre. TimeoutException también cuando una llamada choca con el plazo (sandbox_timeout).

  • Historial de métricas y listado reanudable (m9-sandbox-observability): get_metrics_history(start=, end=, max_points=) (instancia y clase con el access token; una muestra cada 5 s, anillo de 8 h, hueco mientras está suspendido; UnimplementedError en una imagen anterior, como las transferencias y el plazo, con el UNIMPLEMENTED de gRPC en __cause__), SandboxMetrics.mem_cache_bytes, SandboxHealth.cpu_count y memory_total_bytes, SandboxInfo.agent_version/cpu_count/memory_mb en get_info(). Sandbox.paginate(limit=, next_token=, order=, started_after=, states=, metadata=) → SandboxListPaginator (next_items(), has_next, next_token opaco) y list(order=, started_after=).
  • Política de egress (m9-egress-policy, ADR-012): create(network=, allow_internet_access=), update_network() (instancia y clase), get_network(), NetworkPolicy, NetworkOptions, EgressProxy, NetworkState, EgressEnforcement, ALL_TRAFFIC y SandboxHealth.egress_enforcement. Se aplica en el guest de rayito-base-caps; en cualquier otra imagen el SDK termina el VM (también con keep_on_failure) y lanza UnimplementedError.
  • Kernels JavaScript y TypeScript (m9-deno-kernels): language acepta typescript (alias ts) además de javascript (js), servidos por el kernel de Deno de rayito-base-poly.
  • Git (m9-e2b-v2-surface): sbx.git (Git/AsyncGit) con la API git de E2B sobre commands.run (clone, status, commit, push, pull, dangerously_authenticate, ...), GitStatus, GitBranches, GitFileStatus, GitAuthException y GitUpstreamException; las credenciales nunca se registran y se redactan de la salida.
  • CLI (rayito[cli]): rayito sandbox create [--detach --token-file], connect, exec -- CMD y metrics [--follow], con terminal interactiva (POSIX y consola de Windows) y el token sólo por fichero o RAYITO_ACCESS_TOKEN, nunca por argv.
  • create()/connect() aceptan logger= (los logs de ese sandbox van al logging.Logger dado), is_running(request_timeout=) y CommandHandle.wait(on_pty=, on_stdout=, on_stderr=).
  • Shim rayito.e2b 2.x (m9-e2b-v2-surface, contrato e2b 2.51.0 y e2b-code-interpreter 2.10.0): set_timeout, connect(timeout) y la instancia sbx.connect(), lifecycle, beta_create(auto_pause=True), upload_url/download_url, get_metrics(start, end) y la forma de clase con el access token, list(limit, next_token, order) con SandboxQuery.state/started_after/template, allow_internet_access=False y network (en rayito-base-caps), update_network, git, run_code(language="typescript"), E2B, ConnectionConfig, sbx.connection_config, proxy=, headers=, retries=, logger=, traffic_access_token, envd_api_url y las excepciones FileNotFoundException, SandboxNotFoundException, ServiceBusyException, FileUploadException, BuildException. UnimplementedError explícito (nunca TypeError ni AttributeError) para fork, snapshots, pause(keep_memory=False), network.rules, mask_request_host, allow_public_traffic=True, mcp, iam, volúmenes, secretos y templates.

Changed (shim)

  • rayito.e2b exige una imagen M9: create() siempre pide un plazo lógico; contra una imagen anterior termina el VM y lanza UnimplementedError("lifecycle"). timeout es ahora el plazo lógico y maximumDurationInSeconds es max_lifetime (por defecto max(3600, min(timeout + 60, 28800))).
  • rayito.e2b.Sandbox.set_timeout deja de lanzar UnimplementedError (lo que decía la entrada de 0.2.0): mueve el plazo lógico hasta max_lifetime.
  • sbx.pause() devuelve bool (antes el id); files.watch_dir y pty.create toman el callback por nombre (el segundo posicional es user); proxy= se honra; NotEnoughSpaceException es DiskFullException y se lanza; rayito.e2b.UnimplementedError es la misma clase que rayito.UnimplementedError.

Fixed

  • Auto-resume tras una pausa por el plazo con el cliente vivo: en modo on_timeout="pause" con auto_resume, si este cliente suspendió el sandbox al vencer y la suspensión real duró menos de 2 s (el vigilante de rayd no la reconoce como congelación), el sandbox volvía expired y la siguiente llamada fallaba con sandbox_timeout. Ahora el SDK aplica la regla de E2B (max(timeout, 300 s), acotada al tope menos 5 s) con un SetTimeout en el primer sandbox_timeout tras reanudar, una vez por suspensión. La marca de un solo uso sólo se consume cuando la resume_generation ya avanzó y el SetTimeout respondió (un sandbox_timeout que llega antes de la congelación ya no la gasta), y los callers concurrentes comparten una sola reapertura (un lock en Sandbox, una tarea compartida en AsyncSandbox): un único SetTimeout y todos reintentan.
  • rayito doctor: la tabla de compatibilidad gana la fila 0.3 (rayd mínimo 0.3.0); sin ella la comprobación compatibility daba FAIL con el SDK 0.3.0.
  • Las URLs y la pata S3 de las transferencias se firman con la sesión del plano de control (LambdaMicrovmsControlPlane.session) cuando no se pasa session=, también en un sandbox tomado de un pool (pool.session); antes caían a la cadena por defecto de boto3 y podían firmar con otro principal.
  • get_info() devuelve los hechos del guest (agent_version, cpu_count, memory_mb) en el valor que retorna sin guardarlos en sbx.info.
  • UnimplementedError de una política de egress en una imagen sin CAP_NET_ADMIN nombra allow_internet_access=False sólo cuando ese flag, y no network=, es lo que pide la política (la misma regla que TS).
  • CLI: al salir de rayito sandbox connect/create el hilo que lee la terminal local se detiene (antes podía seguir consumiendo la entrada y, en macOS, bloquear el cierre del descriptor de la PTY).
  • Result.extra se lee del proto con las claves ordenadas: el orden de un mapa protobuf no está garantizado.
  • La readiness de create(), connect() y resume() exige además un Health con sandbox_id: el proxy deja pasar Health antes de que rayd reciba /run, y ese Health (kernel del snapshot sin rotar) daba por listo un sandbox cuya rotación de /run ponía kernel_ready=false justo después (AWS_API_NOTES.md Q78, visto en la regresión de M9 con create() en 2,35 s).

Security

  • repr() de LaunchPlan ya no muestra el access token ni el de LaunchRequest el runHookPayload (que lleva los envs de create()): ambos campos quedan fuera de repr (field(repr=False)).
  • JsonFilePoolBackend escribe por un temporal exclusivo (auditoría interna H-01…H-06, fila H-03): el fichero del pool se escribía por <path>.tmp, un nombre fijo que otro usuario del sistema podía crear o apuntar con un enlace antes que el SDK; ahora se crea con tempfile.mkstemp() (O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW) y fchmod sobre el descriptor antes de escribir nada, y se renombra al destino. Sin esto la promesa de 0600 de T14 no era cierta.
  • rayito-mcp --http pasa siempre TransportSecuritySettings (fila H-05): el SDK mcp sólo auto-activa la protección anti-DNS-rebinding para tres cadenas de host exactas, así que --host 127.0.0.2 servía sin mirar Host ni Origin. Ahora el servidor exige que ambos sean el host:puerto con el que arrancó, y un --host comodín (0.0.0.0, ::) se rechaza con error de uso (exit 2).
  • Aviso único cuando RAYITO_ACCESS_TOKEN se hereda del entorno (fila C-08): create() también lee la variable, así que exportarla convierte el secreto por sandbox en uno de toda la flota del proceso. El SDK lo avisa una vez por proceso (logger.warning, nunca el valor).
  • Los errores de AWS ya no encadenan el ClientError crudo: botocore no cuelga la petición firmada de un ClientError, pero lo que AWS devuelve repite la firma: un SignatureDoesNotMatch de S3 trae AWSAccessKeyId y CanonicalRequest (con el valor de x-amz-security-token) en response["Error"], y un InvalidSignatureException/SignatureDoesNotMatch de lambda-microvms o STS mete la cadena canónica en el mensaje, que acababa en el mensaje de la excepción traducida y, por raise ... from exc, en cualquier traceback (también el logger.debug(..., exc_info=True) de la reconexión). Ahora el plano de control y las transferencias por S3 lanzan fuera del except y desde un AwsErrorSummary (rayito._aws_sanitize.sanitize_aws_error) con sólo name, code, el mensaje redactado, status_code, request_id, extended_request_id y attempts (S3 sin mensaje: un EndpointConnectionError nombra el bucket); ni __cause__ ni __context__ guardan el error de botocore. El mensaje traducido pierde la cadena canónica, las cabeceras de firma, los parámetros X-Amz-* y los ids de clave. status_code, aws_code, retry_after y quota_code no cambian.

0.2.0 - 2026-09-17

Segunda release: los siete cambios de M7 (MILESTONES.md), aceptados contra AWS real sobre rayito-base con rayd 0.2.0. Los SDKs y rayd suben MAJOR.MINOR en lockstep; rayito doctor exige agent_version ≥ 0.2.0 para el SDK 0.2 (docs/site/docs/limits.md).

Added

  • Persistencia del HOME en S3 (m7-s3-persistence, ADR-009): S3Prefix(bucket, prefix="rayito", name=None, region=None), Sandbox.create(persist=, persist_timeout=600) (exige execution_role_arn; con name restaura el checkpoint existente en sbx.last_restore, sin name lo fija al sandbox_id), connect(persist=), sbx.persist, checkpoint_files(target=, exclude=, timeout=, on_progress=) → CheckpointResult, restore_files(source=, timeout=, on_progress=) → RestoreResult (NotFoundException sin checkpoint) y reincarnate(exclude=, persist_timeout=) (checkpoint → create(persist=) con las mismas opciones → kill(); el viejo sigue vivo si el create falla). Nuevos CheckpointProgress, RestoreProgress, LaunchOptions y PersistenceException(code) (permission_denied, internal, failed_precondition, unimplemented, interrupted, resource_exhausted); create(pool=) rechaza persist=. Misma superficie en AsyncSandbox. rayito.e2b.Sandbox.set_timeout sigue siendo UnimplementedError y su mensaje nombra reincarnate().
  • run_code(code, language=...) y create_code_context(language=...) aceptan python, bash y javascript (alias js, sin distinguir mayúsculas; m7-poly-kernels): language selecciona el contexto por defecto de ese kernel (default-bash), que el agente crea en la primera celda, y es excluyente con context (InvalidArgumentException). El kernel bash sólo lo trae la variante de imagen rayito-base-poly; javascript es un nombre reservado que hoy ninguna imagen trae (AWS_API_NOTES.md Q57). Un kernel que la imagen no trae es InvalidArgumentException con grpc_code UNIMPLEMENTED y rayito-base-poly en el mensaje; envs por ejecución sólo en contextos Python. El shim rayito.e2b reenvía bash, javascript y js al core y mantiene UnimplementedError para r, java y el resto (docs/site/docs/kernels.md).
  • Pool de sandboxes suspendidos (SandboxPool, AsyncSandboxPool, PoolConfig, PoolStats, PoolSlotInfo, PoolBackend, InMemoryPoolBackend, JsonFilePoolBackend, PoolClosedException; ADR-008, m7-suspended-pool): N MicroVMs calentados con create(), asentados con una celda trivial, aparcados con pause(wait=True) y entregados por take(wait=...) con resume_microvm explícito y el token de la plaza (sin get_microvm en la toma); fallback a create() sin plaza; relleno en un hilo del proceso con backoff 1 s → 60 s por los token buckets compartidos; reciclado antes del muro de 8 h y reconciliación con list_microvms; recuperación desde el backend JSON (rayito.pool/1, modo 0600, el mismo fichero que lee TypeScript); custodia del secreto por plaza hasta take() (SECURITY.md T14); Sandbox.create(pool=...) / AsyncSandbox.create(pool=...) como azúcar que rechaza todo kwarg de lanzamiento. Documentación en docs/site/docs/pool.md.
  • Servidor MCP (rayito.mcp, extra rayito[mcp], script rayito-mcp): seis herramientas (run_code, run_command, read_file, write_file, list_files, list_sandboxes), stdio y streamable HTTP, un sandbox por proceso (creado en la primera llamada, suspendido por AWS al quedar ocioso, terminado al cerrar el servidor); configuración sólo por entorno (RAYITO_TEMPLATE, RAYITO_MCP_TIMEOUT_SECONDS, RAYITO_MCP_IDLE_SECONDS, RAYITO_MCP_LOG_LEVEL). Documentación en docs/site/docs/mcp.md.
  • CLI rayito como extra rayito[cli] (typer) con el entry point rayito y python -m rayito.cli: rayito image publish|list|prune|zip, rayito sandbox list|kill|info|logs (stream de CloudWatch YYYY/MM/DD[<versión>]<id> resuelto desde get-microvm) y rayito doctor (diez comprobaciones OK|WARN|FAIL|SKIP: credenciales y región, imágenes gestionadas, Service Quotas frente a los defaults, iam:SimulatePrincipalPolicy orientativa, bucket, gate de tres estados de la imagen, MicroVMs RUNNING, token, Health del agente y la tabla de compatibilidad SDK ↔ rayd de rayito.cli._compat (la versión de imagen, contador de builds por imagen y cuenta, sólo se informa); --launch crea y mata un sandbox de 300 s; --json en todos los comandos). Ninguna salida contiene un JWE, un access token ni un runHookPayload.
  • probe_health / probe_health_async (un Health sin access token sobre un canal dedicado) como base de probe_metadata; superficie pública sin cambios.

Fixed

  • rayito image zip / copy_sidecar.py: la lista de exclusión se evalúa antes de is_file(), así que un symlink de Linux dentro de un .venv excluido (creado por una sesión Docker) ya no rompe la copia en Windows (WinError 1920).

Changed

  • scripts/publish_image.py, image_prune.py, image_zip.py y copy_sidecar.py son shims de rayito.cli (_publish, _prune, _artifact) con los mismos argumentos; publish exige --bucket o RAYITO_BUCKET (la biblioteca ya no trae el bucket del mantenedor) y el Makefile lo pasa con la variable BUCKET corriendo los dos shims de AWS con uv run --project clients/python.

  • Licencia MIT → Apache-2.0 (PEP 639: license = "Apache-2.0", license-files = ["LICENSE", "NOTICE"], sin clasificador License ::; la wheel lleva License-Expression: Apache-2.0 y NOTICE en .dist-info/licenses/, comprobado por scripts/check_wheel.py).

0.1.0 - 2026-09-16

Primera versión publicable. Reúne la superficie aceptada contra AWS real en los hitos M1-M6 (MILESTONES.md).

Added

  • Ciclo de vida (M1): Sandbox.create (run-microvm + token del proxy + sondeo de Health hasta agent_ready y kernel_ready), connect, kill, list, get_info, is_running, get_host(port) con las cabeceras del proxy, get_health; IdlePolicy (auto-suspensión a los 300 s por defecto); timeout como vida máxima del MicroVM (tope 8 h).
  • Comandos (M2): sbx.commands.run en foreground y background, on_stdout/on_stderr, stdin, list, kill, connect(pid, from_seq), send_stdin, close_stdin; CommandHandle con wait, iteración y reconexión; get_metrics() (instantánea procfs).
  • Ficheros (M3): sbx.files.read (text/bytes/stream), write, write_files en un solo stream, list(depth), exists, get_info, remove, rename, make_dir, watch_dir con WatchHandle.
  • Código (M4): sbx.run_code sobre kernels Jupyter con estado, Execution con results (mime bundles y charts de E2B), logs, error como dato; contextos (create_code_context, list_code_contexts, remove_code_context, restart_code_context).
  • PTY y suspend/resume (M5): sbx.pty (create, connect, send_input, resize, kill; PtyHandle), pause()/resume() con procesos, PTYs, watches y kernels vivos al otro lado, el contrato de reconexión (Connect(from_seq), Pty.Connect, WatchDir, Reattach) y reconnect_timeout.
  • Metadatos por sandbox (M6): Sandbox.create(metadata=...) viaja en el runHookPayload (4096 caracteres junto a envs) y rayd lo devuelve en Health: sbx.metadata, SandboxHealth.metadata, SandboxInfo.metadata (None = no leído del agente), Sandbox.get_info(sandbox_id) con la sonda de Health sobre sandboxes RUNNING y Sandbox.list(metadata=...), un filtro en cliente O(n) (un get-microvm + un JWE + un Health por sandbox RUNNING) que nunca despierta un sandbox suspendido.
  • Shim de E2B (M6): rayito.e2b con Sandbox, AsyncSandbox y los nombres del SDK de E2B 1.x (Execution, Result, CommandHandle, SandboxInfo, SandboxQuery, SandboxPaginator, PtySize(rows, cols), las excepciones de e2b.exceptions, los charts...). Mapea template, timeout (300 s), metadata, envs, request_timeout y sandbox_id; ignora con RayitoCompatWarning api_key, domain, debug, proxy y secure=False; lanza UnimplementedError para set_timeout, upload_url/download_url, rangos de get_metrics, connection_config, kernels no Python, list(next_token=), beta_create(auto_pause=...), templates/fork y allow_internet_access=False (medido: un MicroVM sin conector de egress hereda el de la imagen y sigue saliendo a internet, AWS_API_NOTES.md Q44).
  • AsyncSandbox con la misma superficie que Sandbox sobre grpc.aio, incluidos metadatos y el shim.
  • Empaquetado: clasificadores, URLs, py.typed, wheel comprobada (scripts/check_wheel.py, twine check), workflow release.yml con Trusted Publishing de PyPI y sitio de documentación (docs/site).

Requisitos de imagen

  • run_code necesita rayito-base ≥ 7.0 (sidecar de kernels); pty y la reconexión tras pause() necesitan ≥ 10.0 (M5); los metadatos necesitan la imagen de M6 (un rayd que devuelva HealthResponse.metadata; la aceptación de 0.1.0 corrió sobre rayito-base-e2b 1.0, construida del mismo árbol que la rayito-base de M6). Sobre una imagen anterior el SDK funciona y metadata se lee vacío.

Limitaciones conocidas

  • La vida de un sandbox no se puede extender (set_timeout de E2B es UnimplementedError: no existe UpdateMicrovm); tope 8 h running + suspended.
  • Ancho de banda del endpoint de un MicroVM de 2 GB: ≈ 0,65 MB/s de escritura y ≈ 6,71 MB/s de lectura (medidos); 8 conexiones concurrentes.
  • list(metadata=) es O(n) sobre los sandboxes RUNNING y pospone la auto-suspensión de cada uno una ventana de idle.
  • Sólo kernels Python; sin URLs firmadas; sin historial de métricas.
  • Las URLs del proyecto apuntan a https://github.com/alejandro-cedeno-10/rayito hasta que el repositorio sea público (placeholder documentado).

Publicación

Pasos manuales, fuera de CI, antes del primer tag (pasos canónicos en docs/RELEASING.md):

  1. Registrar el Trusted Publisher en PyPI: proyecto rayito, repositorio alejandro-cedeno-10/rayito, workflow release.yml, environment pypi.
  2. Crear el environment pypi en GitHub (Settings → Environments).
  3. git tag python-v0.1.0 && git push origin python-v0.1.0: el job build construye y comprueba la wheel (contenido y twine check) y verifica que el tag coincide con pyproject.toml; el job publish sube a PyPI sin token. workflow_dispatch sólo ensaya el build.

[0.0.1] - [0.0.5]

Builds internos de los hitos M1-M5, nunca publicados.

Unreleased

Corregido

  • rayito setup pone al día la pila de IAM de una versión anterior. Antes reutilizaba rayito-m0-iam si su ArtifactBucket coincidía, aunque se hubiera desplegado con un infra/iam.yaml más antiguo (en 0.17.0 la pila de 0.16 se quedaba sin los permisos de la receta de la flota). La plantilla lleva ahora el parámetro TemplateFingerprint (el sha256 de la plantilla empaquetada, el mismo en los dos SDK) y su salida; setup lo manda al crear o actualizar y planifica update si la pila no lo tiene o lleva otro, con el texto «actualizar a la plantilla infra/iam.yaml de este SDK» y previousBucket: null en --json cuando el bucket no cambia. Una pila desplegada a mano (sin el parámetro) se actualiza una vez; los demás parámetros conservan su valor (UsePreviousValue). Una pila que desplegó un Rayito más nuevo (su etiqueta rayito:sdk-version) nunca vuelve a la plantilla de este SDK: con el mismo bucket se reutiliza («… más nuevo que este SDK: se conserva», stackSdkVersion en --json) y, si el plan le cambia el bucket, falla con el error nuevo stack_newer (salida 2). Al actualizar la pila, setup vuelve a mandar las etiquetas que ya tenía, con las de --tag por encima, porque UpdateStack sustituye la lista entera. StackStatus gana tags (las de DescribeStacks). Una pila creada con la plantilla de 0.6.1 o anterior se actualiza sin revertirse: la plantilla gana el parámetro LegacyCallerPolicy, que conserva la descripción original de CallerPolicy (IAM no la cambia y CloudFormation sustituía la política, que chocaba con su propio nombre fijo y dejaba la pila en UPDATE_ROLLBACK_COMPLETE), y setup lo pone a true en una pila sin la salida SandboxLauncherPolicyArn.
  • Un --token-file que dejó un rayito agent deploy fallido ya no bloquea el reintento. El fichero se escribe antes del build; si el despliegue falla antes de que exista la flota (build, arranque del host, una confirmación rechazada o cualquier otro error), se borra el fichero que creó esa misma ejecución. Con --keep-on-failure se conserva, porque es el único acceso al sandbox que se queda, y un fichero de una ejecución anterior nunca se toca.
  • rayito setup --json informa la versión de cada imagen publicada en su paso. Tras apply, los pasos baseImage y capsImage con publish llevan en imageVersion la versión recién publicada (antes null, aunque result sí la tenía). En --dry-run y si la preparación falla, siguen en null.
  • El host del agente ya no deja salir el DEBUG del SDK de AWS para Python con parámetros y cuerpos de AWS (los assets del host se generan desde el SDK Python): sube botocore, boto3, s3transfer y urllib3 a WARNING, como httpx y httpcore en 0.17.0, y descarta los registros por debajo de INFO de botocore.endpoint, botocore.parsers, botocore.auth, botocore.regions, boto3.resources.action, boto3.resources.collection, boto3.dynamodb.table, s3transfer.tasks y s3transfer.futures aunque el código del agente vuelva a bajar el nivel. El SDK TypeScript no pone ningún logger en sus clientes del AWS SDK v3.
  • Una flota dedicada de un solo VM vuelve a su sesión. Con sessions: "dedicated" y maxVms: 1, el id de sesión no lleva asa y la flota no recordaba la sesión que asignó el host: la segunda llamada con ese id esperaba un VM libre y acababa en CapacityError tras el plazo de la cola. Ahora cada sesión asignada por el host se recuerda con su VM dentro del proceso, como un id elegido por el llamante.
  • El plazo de los VMs sobrantes de una flota (las tres limitaciones conocidas de 0.17.0):
  • Una invocación que se suelta ya no acorta el plazo de otra que empezó entre medias en el mismo VM: los setTimeout de cada VM van en cola y el acortamiento sólo se manda si el VM sigue libre.
  • Una invocación que falla (un 429 hasta agotar la cola, un error del host o de transporte) devuelve el VM sobrante a scaleInAfterMs; antes se quedaba con el plazo de invocación hasta requestTimeoutMs.
  • Un VM acortado que pasa a ser base recupera toda su vida aunque la flota no conozca su tope: pide el máximo de la plataforma y rayd, que lo rechaza informando su tope, recibe ese tope en el reintento. Antes no se mandaba nada y el VM base podía terminar por el plazo corto. Si no queda nada que devolverle, deja de contar como acortado (restoreMs, vectores restoreSeconds en testdata/agent-fleet/lifetime.json).

Seguridad

  • vitest 4.1.12 (dependencia de desarrollo; @vitest/mocker GHSA-qxg6-89mg-rqv8 y GHSA-xp26-3454-mjgp). No cambia el paquete publicado.

0.17.0 - 2026-10-23

Añadido

  • rayito setup --caps-image publica también rayito-base-caps, la base que usa rayito agent deploy en MicroVM y que antes había que publicar a mano. Es un cuarto paso del plan (capsImage) que sale del mismo rayito-image.zip (una sola descarga) con additionalOsCapabilities=["ALL"]; el plan y --dry-run muestran el paso y su coste (otra versión de imagen, ≈ $0,04 por semana, $0,08 por GB-mes). Va apagado por defecto porque cuesta: sólo --caps-image (SetupRequest.capsImage: true) lo añade; sin la opción el paso sale como skip y no se lee ni se publica esa imagen. Si rayito agent deploy en MicroVM no encuentra rayito-base-caps, el error pide rayito setup --caps-image. El documento de --json gana capsImageArn y capsImageVersion en result.
  • AgentProject.gatewayPresets: pasarela -> preset, tal como lo declara [gateways.<nombre>] preset. Lo rellenan la lectura del fichero y la detección; no entra en el documento canónico del proyecto.
  • Receta de la flota (ADR-034): deploy_agent() y rayito agent deploy guardan, tras arrancar la flota de MicroVMs, lo que usaron (el proyecto tras las banderas, la versión de la imagen, los presets de las pasarelas, el escalado) en un parámetro SecureString estándar de Parameter Store (/rayito/agents/<nombre>/recipe), con nombres de secretos y nunca valores. AgentFleet.connect(nombre) sin project la lee y puede crear VMs iguales; useRecipe: false la ignora; @aws-sdk/client-ssm es un peer opcional y, sin él, conectar por nombre sigue como en 0.16. Es la única función encendida por defecto (no cuesta en el nivel estándar): saveRecipe: false / --no-save-recipe la apagan. Un fallo al guardarla es un aviso (recipe_access_denied, recipe_not_saved, recipe_too_large), nunca un despliegue fallido. Nueva sentencia AgentRecipe: CallerPolicy la guarda, la lee y la borra; SandboxLauncherPolicy sólo la lee y la borra (escribirla equivale a poder desplegar) e ImagePublisherPolicy sólo la guarda (AgentRecipeWrite).
  • FleetStatus.scalingSource (explicit, project, recipe o default).
  • AgentFleet.stop({ keepRecipe }) y rayito agent stop --keep-recipe: por defecto stop() borra la receta que leyó o guardó.
  • AgentDeployOptions.scaleInAfterMs y rayito agent deploy --scale-in-after SECONDS.
  • Renovación opcional de la clave de corta duración de Bedrock (renewGatewayKeys en AgentFleet, AgentFleet.connect y AgentDeployOptions; --renew-gateway-keys), apagada por defecto, con los peers opcionales @aws/bedrock-token-generator 1.1.0 y @aws-sdk/client-secrets-manager.
  • rayito agent invoke <flota> --renew-gateway-keys: la CLI pide la renovación al reconectar la flota por su nombre (la receta no la activa). Con el id de un sandbox sale con 2, y con --target agentcore, option_not_supported.
  • AgentDeployment.refreshGateways() y applyGateways en el pool.

Cambiado

  • Incompatible con 0.16: rayito agent deploy --env rechaza un nombre que parece un secreto, también *_SECRET y SECRET, los que tienen un segmento PASS, PASSWD, PAT, CREDENTIAL o CREDENTIALS, y los que llevan AUTH o BEARER como último segmento o junto a TOKEN, KEY o HEADER (AUTH, X_AUTH, AUTH_HEADER; sin distinguir mayúsculas; antes pasaban). AUTH_MODE, AUTH_URL o AUTHOR_NAME siguen pasando. Se rechaza antes de cualquier llamada a AWS y con los dos destinos. Migración: pasa el valor por --secret-env NOMBRE=<secreto> o --secret-env-from <secreto>. La regla es sólo de la bandera: los valores de env del proyecto ([env] de rayito-agent.toml o la biblioteca) se guardan tal cual en la receta, así que no pongas en env nada secreto.
  • rayito setup --caps-image publica rayito-base-caps: una versión de imagen más que factura almacenamiento de snapshot (≈ $0,04 por semana). Apagado por defecto (ADR-034 D9).

Corregido

  • Un pool sólo recupera las plazas de su imagen: SandboxPool adoptaba al arrancar todas las plazas del backend. Con el fichero de estado por defecto de rayito agent deploy --warm (.rayito/agent-pool.json), desplegar una segunda flota desde el mismo directorio reciclaba (terminaba) las plazas aparcadas de la primera, o las contaba como suyas. Ahora una plaza de otra imagen no se adopta, recicla, termina ni entrega, y se queda en el backend para el pool de su imagen. Visto en la regresión en AWS real.
  • La reducción de la flota (scaleInAfterMs) por fin ocurre: desde 0.14 los VMs se creaban sin maxLifetimeMs/onTimeout, así que rayd rechazaba el setTimeout que termina un VM sobrante y la flota se tragaba el error. Una flota que puede crecer (maxVms > minVms) crea ahora todos sus VMs y plazas calientes con onTimeout: "kill" y maxLifetimeMs = serve.timeoutMs + 60 000; antes de cada invocación el plazo de un VM sobrante sube al de la petición y, al soltar la última invocación en curso, vuelve a scaleInAfterMs (mientras quede otra no se acorta), siempre por debajo del tope que impone rayd (maxLifetimeMs - 60 s desde el arranque) menos 5 s. Si rayd rechaza un plazo por pasar de su tope, la flota adopta el tope que informa y reintenta una vez, sin marcar el VM. Una flota de tamaño fijo se crea byte a byte como en 0.16; un VM creado sin plazo lógico avisa una vez por el logger y no se reintenta. PoolConfig gana maxLifetimeMs y onTimeout opcionales (sólo "kill"). Vectores en testdata/agent-fleet/lifetime.json.

  • El host del agente ya no escribe URLs con credenciales en sus logs (el host viaja en los recursos del paquete, host-assets.gen.ts): los loggers httpx y httpcore, que a nivel INFO escriben la URL de cada petición con su consulta, arrancan en WARNING en serve (QUIET_HTTP_LOGGERS). El código del agente puede volver a bajarlos. Ver Seguridad.

  • El log de peticiones del host del agente ya no lanza AttributeError tras responder 400 a una línea de petición mal formada; las invocaciones siguientes nunca se vieron afectadas.
  • --create-secret busca la clave por el preset de la pasarela: una pasarela [gateways.model] preset = "bedrock" lee AWS_BEARER_TOKEN_BEDROCK en vez de buscar una variable por el nombre model. Con preset = "custom" sigue mirando el nombre de la pasarela, como en 0.16.
  • --create-secret ya no duplica el prefijo rayito/: la detección escribía el secreto de la pasarela como rayito/agents/<proyecto>/<proveedor> y, como la pasarela lo lee bajo rayito/, el secreto acababa en rayito/rayito/agents/.... Ahora escribe agents/<proyecto>/<proveedor> y el secreto queda en rayito/agents/<proyecto>/<proveedor>, el nombre que lee la pasarela. Si sólo existe el secreto de 0.16, el despliegue lo reutiliza y no crea otro; un rayito-agent.toml con un nombre propio no cambia.
  • OpenCode sin fichero de proyecto usa AGENTS.md y los permisos de opencode.json (ADR-034 D8). Antes sólo rayito-agent.toml los aplicaba. Ahora la detección lleva el AGENTS.md de la raíz a agent.instructions_file, y el bloque permission de opencode.json u opencode.jsonc (sin comentarios ni comas finales) a agent.permissions. ask se aplica como deny, porque la ejecución es desatendida, y una clave que no se puede aplicar se ignora. Los dos casos avisan con opencode_permission_ignored. Lo que dice rayito-agent.toml gana, y el informe sólo lleva la evidencia (file:AGENTS.md, config:permission), nunca el texto de los ficheros.
  • Una flota reconectada en otro proceso abría una plaza caliente sin las pasarelas del proyecto (el agente arrancaba sin la pasarela del modelo): la flota las abre ahora con el mismo camino que SandboxPool.take({ gateways }) y sustituye la plaza si no puede.
  • rayito agent status <flota> sin proyecto mostraba un escalado inventado (min 1, max 1); ahora imprime el de la receta o escalado desconocido: sin proyecto ni receta.
  • AgentFleet.connect(nombre) sin project ya no falla si Parameter Store no responde (sin endpoint de VPC para SSM, limitación de tasa, un error de KMS): es el aviso recipe_unreadable y la flota de 0.16, y rayito agent stop <nombre> termina sus VMs.
  • La receta ya no activa la renovación de claves de Bedrock: la clave se firma con las credenciales de quien conecta, así que AgentFleet.connect(nombre) sólo renueva con renewGatewayKeys: true explícito. Si la receta lo pedía y no lo pasas, la flota no renueva y avisa recipe_renewal_ignored (sólo el código). Listar, consultar o terminar la flota nunca necesita el peer @aws/bedrock-token-generator: el renovador se carga en la primera renovación, y las renovaciones simultáneas se serializan con una promesa en curso (los VMs que arrancan en paralelo escriben una sola clave).
  • renewGatewayKeys no renueva un secreto compartido: si el secreto de la pasarela bedrock lo lee también una pasarela cuyo upstream no es Bedrock, la opción es InvalidArgumentError (nombra las pasarelas, nunca el secreto). Sólo cuenta como Bedrock un host bedrock-runtime.<región>.amazonaws.com con una región de AWS válida; un host como bedrock-runtime.evil.s3.amazonaws.com ya no se toma por Bedrock.
  • Un nombre de flota ., .. o vacío ya no da una ruta de receta (recipe_not_saved).
  • rayito setup informa artifactUploaded también cuando sólo publica rayito-base-caps (una cuenta que viene de 0.16).
  • La ayuda de --scale-in-after sale de la constante del SDK y dice que, sin la bandera, manda el [scaling] del proyecto.
  • rayito agent deploy con --min-vms 0 imprimía sandbox_id=undefined (y --json perdía la clave sandboxId): una flota sin VMs con servicio no imprime sandbox_id ni url y --json los da como null, como en Python.

Pruebas

  • Los e2e de despliegue agnóstico ya no repiten nombre de secreto entre AgentCore y MicroVM en una misma ejecución (antes chocaban con ResourceExistsException mientras el anterior seguía pendiente de borrado): secretName(scope, kind) devuelve rayito-e2e/<run>/<scope>-<kind>-<sufijo aleatorio>, y cada prueba borra al terminar sólo los secretos que creó.
  • Los e2e de la receta ya no inventan ids de sesión: el host sólo acepta los suyos y respondía invalid_session_id.

0.16.0 - 2026-10-22

Añadido

  • El id del modelo es opcional cuando no es Bedrock (ADR-033 D2): un grafo que elige su modelo por configuración (new ChatOpenAI({ model: process.env.LLM_MODEL, configuration: { baseURL } })) se detecta y se despliega sin escribir el id. [model] acepta los nombres de variable id_env, base_url_env y api_key_env (idEnv, baseUrlEnv, apiKeyEnv en AgentModelOptions; nunca valores), y en MicroVMs Rayito los apunta a la pasarela con el marcador como clave. La detección ve base_url= en el constructor del cliente (openai pasa a openai-compatible) y los nombres de variable del modelo, y avisa con model_id_from_code. El id sigue siendo obligatorio con las pasarelas bedrock y gemini y con los runtimes que construyen el modelo; con un entrypoint, ctx.model es undefined y la telemetría omite gen_ai.request.model.
  • --model-id, --model-provider y --model-base-url en rayito agent detect y deploy (DetectOverrides.modelId, modelProvider, modelBaseUrl).
  • --external-model en AgentCore (AgentCoreOptions.externalModel, prop externalModel de RayitoAgent): admite un modelo fuera de Bedrock sin dar al rol permisos de Bedrock que no pidas; la URL base va al entorno del runtime y la clave nunca. El plan lo dice con la línea externalModel.
  • Entrada estructurada y selección de grafo (ADR-033 D3): un grafo de LangGraph cuyo esquema de entrada no es de mensajes (por ejemplo, InputState con ticket_id) se invoca con invoke({ input }) y stream({ input }) en AgentDeployment, AgentFleet y AgentCoreFleet (sobrecargas: el prompt de siempre o { input }; tipos nuevos AgentInputRequest, AgentInvocation y JsonObject), y devuelve AgentResult.output y Done.output. El host lee el esquema con get_input_jsonschema() (getInputJsonSchema en LangGraph.js), comprueba el input y pasa la sesión como thread_id; un grafo de mensajes sigue igual. Fallos protocol_error con input_required, invalid_input u output_not_serializable, con su propio mensaje, y InvalidArgumentError con una pista de redespliegue si el host desplegado es anterior. Con varios grafos en langgraph.json, --graph, [runtime] graph y DetectOverrides.graph eligen uno; sin elegir y sin uno llamado agent, el error lista los nombres. rayito agent invoke acepta --input y --input-file (- lee stdin) en lugar de PROMPT. Límite nuevo AGENT_SERVICE_MAX_OUTPUT_BYTES.
  • Secretos de Secrets Manager como variables de entorno del agente (ADR-033 D4): [secret_env] (NOMBRE = "<secreto>" o { secret, key, version_stage }) y [[secret_env_from]] (secret, prefix, keys) en el fichero de proyecto, new AgentProject({ secretEnv, secretEnvFrom }) con SecretEnvRef y SecretEnvSource, y --secret-env NOMBRE=<secreto>[#clave] y --secret-env-from <secreto>[,prefix=P][,keys=A|B] en rayito agent deploy. En AgentCore el runtime sólo recibe RAYITO_SECRET_ENV (nombres y ARNs), el host lee cada secreto con el rol al arrancar la sesión, antes de cargar el código, y falla en cerrado con secret_env_*; los secretos se suman a ReadableSecretArns (la plantilla no cambia) y el plan lo dice con la línea secretEnv y secretEnvNames. En MicroVMs el SDK los lee con tus credenciales y los pasa sólo al proceso del host. Sin estas claves, el plan, el export, la imagen y las llamadas no cambian.
  • --create-secrets-from FICHERO.env (AgentDeployOptions.createSecretsFrom) crea o actualiza el secreto JSON rayito/agents/<nombre>/env con los valores de un .env y lo usa como secret_env_from; enseña sólo los nombres y pide --yes (cuesta $0,40 al mes). --check-env (checkEnv) avisa (env_missing) de las variables de .env.example que no reciben valor.
  • Protocolo langgraph-api (experimental) para servir un langgraph.json con langgraph dev: new HttpApp({ protocol: "langgraph-api", assistantId }) (y [runtime.app] protocol = "langgraph-api" con assistant_id) hace que el host traduzca cada invocación a POST /threads/{thread_id}/runs/wait del LangGraph Platform API, con el hilo derivado de la sesión (la misma sesión sigue el mismo hilo), salud en /ok, el texto de response_field o del último mensaje y el uso de su usage_metadata. Un interrupt del grafo es AgentError(protocol_error) con detail_code graph_interrupted. rayito agent detect y deploy con --protocol langgraph-api sirven así un proyecto con langgraph.json (kind = "custom", el launcher de langgraph dev y el grafo elegido como assistant_id), y sin la opción la detección no cambia. langgraph dev es un servidor de desarrollo en memoria; Rayito no instala langgraph-cli[inmem] (el paquete langgraph-api usa la licencia Elastic-2.0). Sin streaming ni langgraph up en esta versión. HttpApp.servedHealthPath y servedInvokePath dicen las rutas a las que llama el host. Con invoke({ input }) el objeto llega tal cual como entrada del grafo y, si el estado final no tiene mensajes, vuelve como AgentResult.output; --graph elige el assistantId.

Cambiado

  • AgentModel.id pasa de string a string | undefined (AgentModel<Id>: un modelo construido con id sigue siendo string), y con él AgentSpec.effectiveSmallModel; sólo cambia el tipo que se lee, ninguna llamada existente. Un proyecto con id se comporta igual, pero con strict tsc avisa (TS2322/TS18048) donde el código lee project.spec.model.id como string; estréchalo (if (id !== undefined)).
  • model_required se lanza por fin: AgentCore con un proveedor bedrock sin [model] id ni --bedrock-model (antes reservado); su texto dice «fija [model] id o pásalo con --bedrock-model».
  • Textos de error (los códigos, las rutas y el código de salida no cambian): model_required ya no lleva la pista «(bedrock_models en el SDK)», y un langgraph.json con varios grafos y ninguno llamado agent da el texto de ambiguous_graph (los nombres de los grafos y --graph) en vez del de ambiguous_entrypoint, con el mismo código ambiguous en runtime.entrypoint.
  • La detección sólo convierte openai en openai-compatible por un baseURL propio en el constructor del cliente (también dentro de configuration; configuration: { organization } ya no cuenta); con la URL oficial de OpenAI, o si el código fija el id del modelo con un literal y no pasas --model-base-url, el proyecto se detecta como en 0.15 (en el segundo caso, con el aviso custom_base_url).
  • Un prompt a un grafo con esquema de entrada propio sin claves required sigue el camino de mensajes, como en 0.15; input_required queda para los grafos que declaran claves obligatorias.

  • Un 400 a una petición con input se explica con el cuerpo del host: el invalid_request de un host anterior pide volver a desplegar; el de un host con entrada estructurada (que añade "accepts_input": true) es una petición que no acepta; cualquier otro cuerpo da un mensaje que sólo sugiere redesplegar. Con input, el SDK mide el cuerpo entero de la petición contra AGENT_SERVICE_MAX_REQUEST_BYTES, no sólo el input, y sólo acepta objetos planos (un Date, un Map o una instancia de clase son InvalidArgumentError, como un datetime en Python); con un prompt no cambia nada.

  • new AgentProject(...) con un runtime que construye el modelo (opencode, hermes, deepagents o strands sin entrypoint) y sin AgentModel.id falla al construirse, antes de cualquier llamada o de crear el secreto de --create-secrets-from.
  • rayito agent deploy --create-secrets-from pregunta en una terminal (sin --json) en vez de exigir siempre --yes, como la CLI de Python.
  • Con langgraph-api, healthPath: "/ok" (el de la detección) ya no da la pista de rutas ocultas. El puente de Node valida el input sólo con claves propias (toString o hasOwnProperty no cuentan como claves) y guarda en la sesión los messages de un input a un grafo de mensajes sin checkpointer.

Seguridad

  • Con --external-model el agente llama a un modelo fuera de Bedrock por Internet con una clave que obtiene su código; la clave nunca va a las variables de entorno del runtime (SECURITY.md, T36).
  • T38: los valores de secret_env nunca van al proyecto, al plan, a la imagen, a los logs ni a los errores; el rol del runtime sólo lee esos secretos (GetSecretValue, sin kms:Decrypt), y un test falla si se amplía. El agente y lo que lance pueden leer su entorno: la guía, security.md y SECURITY.md lo dicen.
  • El fichero de --create-secrets-from se excluye del código si está dentro, así que sus valores nunca llegan a la imagen ni a la plantilla; un nombre reservado en él es un error que nombra la variable, nunca su valor. Un --secret-env sin = o con un nombre que no es de variable da el error en secret_env, sin repetir lo escrito. En MicroVMs el host recibe también RAYITO_SECRET_ENV_NAMES (sólo nombres) y redacta los valores de secret_env en el diagnóstico de arranque igual que en AgentCore.
  • Una clave __proto__ de un secreto JSON o de secretEnv es una variable más, como en Python, en vez de perderse.
  • El host lee los valores de secret_env por una tubería propia, nunca por la salida estándar de la herramienta, y el diagnóstico de arranque redacta un valor con CRLF (un PEM) o con caracteres de control.

0.15.0 - 2026-10-21

Añadido

  • Despliegue en Amazon Bedrock AgentCore Runtime: deployAgent(ruta, { target: "agentcore" }), planDeploy() y rayito agent deploy ./mi-agente --target agentcore despliegan un agente de cero configuración sin Docker local: la imagen arm64 se construye en CodeBuild a partir del export de agentcore, se sube a ECR (repositorio, proyecto y roles en la pila rayito-agentcore-<nombre>) y el runtime se crea o se actualiza sin duplicarlo, con MMDSv2 siempre exigido. rayito agent deploy --target agentcore muestra el plan, la región, el coste aproximado y la seguridad, y pide confirmación (--yes, --dry-run, --json, --tag K=V para las organizaciones que exigen etiquetas). AgentCoreFleet (connect(), invoke(), stream(), status(), stopSession(), delete()) tiene la misma API y las mismas sesiones que AgentFleet; rayito agent invoke|status|stop --target agentcore y las órdenes nuevas rayito agent list y rayito agent delete lo usan. Las opciones de MicroVMs que AgentCore no admite (min_vms, warm_vms, --memory...) dan option_not_supported en vez de aproximarse. AgentCore no tiene la pasarela de secretos de Rayito: el agente lee las credenciales del rol del runtime, que sólo puede invocar los modelos de agentcore.bedrockModels y leer los secretos de agentcore.readableSecrets. Cuatro clientes de AWS nuevos como peers opcionales (@aws-sdk/client-bedrock-agentcore, -bedrock-agentcore-control, -codebuild y -ecr); el destino por defecto sigue siendo la flota de MicroVMs y no los carga.
  • El export agentcore lleva el argumento de build AGENT_BASE_IMAGE y, para Vercel AI SDK, el ayudante que lee las credenciales del rol del runtime (botocore fijado por hash); el Dockerfile y el README del export docker no cambian.
  • OpenCode obtiene el rol del runtime con un perfil credential_process: el export agentcore de un agente OpenCode añade el ayudante de botocore, un AWS_CONFIG_FILE con un perfil cuyo credential_process lo ejecuta (con Expiration, para que el SDK renueve) y AWS_CONFIG_FILE y AWS_PROFILE en la imagen; sin ellos OpenCode no arranca su proveedor de Bedrock. MicroVM, el export docker y los demás frameworks no cambian.
  • El host del agente retiene hasta 25 s la primera petición de una sesión nueva de AgentCore esperando el calentamiento, en vez de contestar 503 (un calentamiento fallido sigue contestando 503 al instante).
  • rayito setup: prepara una cuenta y una región de AWS para Rayito en una orden idempotente, como cdk bootstrap. Lee la cuenta, muestra el plan y el coste aproximado, pide confirmación (--yes sin terminal o con --json; --dry-run sólo lee) y termina con rayito doctor. Crea o actualiza la pila de IAM rayito-m0-iam (sólo ArtifactBucket), reutiliza el bucket de --bucket, RAYITO_BUCKET, la pila o el bootstrap de CDK y publica rayito-base desde el rayito-image.zip de la release rayd-v<versión>, comprobado contra su SHA256SUMS, o desde --artifact. Un build en curso se espera en vez de lanzar otro. Mismas opciones, textos, documento JSON y códigos de salida que la CLI de Python.
  • rayito setup crea el bucket de artefactos con la pila rayito-m0-artifacts (infra/artifact-bucket.yaml: cifrado, privado, sólo TLS, zips caducando a los 7 días, conservado al borrar la pila) cuando no hay ninguno que reutilizar; un --bucket o RAYITO_BUCKET que no existe se crea con ese nombre.
  • rayito agent deploy y deployAgent() encuentran el bucket que creó rayito setup (la salida BucketName de rayito-m0-artifacts, sólo lectura) cuando no hay RAYITO_BUCKET ni bucket del bootstrap de CDK.
  • Constructos de AWS CDK en rayito/cdk (peers opcionales aws-cdk-lib ^2.261.0 y constructs ^10.5.0). RayitoAgent despliega con cdk deploy el mismo agente que deployAgent(ruta, { target: "agentcore" }), con la misma plantilla infra/agentcore-runtime.yaml (IAM idéntico): la imagen se construye en CodeBuild por defecto (AgentImageBuild.codeBuild(), sin Docker local) o con tu Docker (AgentImageBuild.localDocker()), y Custom::RayitoRuntimeMmds exige MMDSv2. RayitoMicrovmImage publica una imagen de Lambda MicroVMs con rayd (y sus tamaños) desde await MicrovmImageArtifact.downloadRelease() (comprobado contra SHA256SUMS, con caché para síntesis sin red) o fromFile(), con su propio rol de build y la misma configuración que rayito image publish. cdk synth no llama a AWS; la región del stack debe ser concreta. Errores propios en RayitoCdkError; los del plan siguen siendo AgentCoreError. Ningún otro punto de entrada (rayito, rayito/e2b, la CLI, el servidor MCP) importa aws-cdk-lib. Guía: guias/desplegar-con-cdk.md.
  • La CLI respeta lo que gestiona CloudFormation: un runtime con la marca rayito.agentcore/1+cdk se lista, se consulta y se invoca como cualquier otro, pero rayito agent deploy, rayito agent delete y deployAgent(ruta, { target: "agentcore" }) lo rechazan con name_conflict, y rayito image publish rechaza una imagen con la etiqueta rayito:managed-by=rayito-cdk. Los runtimes con la marca rayito.agentcore/1 no cambian.

Cambiado

  • Una vez, el hash de contenido de las imágenes de agentes de MicroVMs: el host del agente (agent_host) y, para los frameworks de Node (Strands TS, LangGraph JS, Deep Agents JS, AI SDK de Vercel, Strands harness TS), el puente node_bridge.mjs llevan ahora el soporte de AgentCore (id de sesión y credenciales del rol). Como los dos forman parte del contexto de build, el primer deploy_agent tras actualizar construye una versión de imagen nueva (unos $0,011 y al menos una semana de almacenamiento de su snapshot) aunque tu código no haya cambiado; los siguientes la reutilizan. El Dockerfile y el README del export docker no cambian; sus ficheros de agent_host/ y, en Node, node_bridge.mjs sí.
  • Los errores de Sandbox.create() (ResourceNotFoundException de RunMicrovm) y de deployAgent() por falta de bucket o de imagen base terminan con la pista "si la cuenta o la región aún no están preparadas, ejecuta rayito setup". La clase, el código y la condición no cambian.

Seguridad

  • Pines de los venvs de agente con los parches de seguridad publicados: certifi 2026.10.19 (retira raíces de emSign con claves intermedias comprometidas) en Strands, el harness de Strands, Deep Agents y Hermes; mcp 1.30.0 en Strands y el harness (strands-agents 1.61.1 exige mcp <2.2, así que es el parche de la rama 1.x); y, en Hermes, PyJWT 2.15.1, Pillow 13.0.0, aiohttp 3.14.4, urllib3 2.8.0, multidict 6.9.1 y yarl 1.25.1 sobre el lock de Hermes v0.22.2. pip-audit no encuentra ninguna vulnerabilidad conocida en ellos. browser-harness (el motor del Browser Use CLI de Hermes, que fija pillow==12.3.0) sale del venv de Hermes: la plantilla no activa el navegador y Hermes usa sus herramientas propias si falta. Como los ficheros de pines forman parte del contexto de build, el primer deployAgent() o build de AgentTemplate de esos frameworks tras actualizar construye una versión de imagen nueva.

0.14.0 - 2026-10-17

Añadido

  • AgentProject.detect() une la detección con deployAgent: devuelve [proyecto, informe] a partir de una carpeta, sin ejecutar su código, con el proveedor y el modelo que encuentra (también en los ficheros de configuración de Hermes y OpenCode), la pasarela del modelo y los plugins de Node de LangGraph, DeepAgents y Vercel AI SDK como custom con su adaptador. Strands instala con uv cuando el proyecto lo usa, y el host recibe queue_max y queue_timeout_seconds en host.json. La detección de una app HTTP que lee su puerto de PORT ahora lo declara (port_env), de modo que el host se lo pasa y la app escucha donde él la espera.
  • Opciones tipadas para servir un agente en varios VMs (agent-zero-config): ServeOptions suma queueMax y queueTimeoutMs (la cola del host; con 0, el valor por defecto, la llamada que no cabe recibe 429 como hasta ahora) y maxConcurrency queda acotada por las conexiones que admite el MicroVM de memoryMib. AgentProject suma scaling (ScalingOptions: minVms, maxVms, warmVms, sesiones shared o dedicated y scaleInAfterMs), env (sin secretos), network con allowOut y denyOut, region, bucket, baseImageVersion, language, framework y versions (RuntimeVersions). El fichero rayito-agent.toml admite las tablas [scaling], [env] y [deploy], las claves allow_out, deny_out, queue_max y queue_timeout_seconds, y en [runtime] las de language, framework, python_version y node_version. Todo es aditivo y los valores por defecto son los de un solo VM. Los errores de lectura suman los códigos ambiguous, not_detected y unsupported. Además, la detección estática de un proyecto de agente (lenguaje, framework, punto de entrada, app HTTP, plan de instalación, versiones y proveedor, sin ejecutar su código) y las reglas que comparten los dos SDK (agent-detection.json) quedan listas para deploy_agent.
  • Host y adaptadores de instalación del agente: el host del agente sirve una cola FIFO acotada (queue_max y queue_timeout_seconds de host.json; sin cola por defecto: la llamada que no cabe recibe 429, como hasta ahora) y supervisa las apps de larga vida (http-app y Hermes): una app que muere se relanza con espera creciente, /ping es HealthyBusy y las invocaciones reciben 503 con Retry-After mientras tanto, y agotado el presupuesto el marcador de fallo lleva app_crashloop con la cola saneada de su salida de error. new HttpApp({ protocol: "json" }) traduce /invocations al contrato propio de una app (requestField y responseField) y reenvía sus demás rutas; agentcore sigue siendo el valor por defecto de un HttpApp explícito. Un único puente de Node (node_bridge.mjs, driver node-callable) ejecuta agentes de Strands (el antiguo strands-ts, que sigue como alias), LangGraph.js y deepagents-js (adapter: "langgraph"), el AI SDK de Vercel (adapter: "vercel-ai") y una función propia (adapter: "callable") con new CustomRuntime({ entrypoint: "ruta.ts#exportacion", packageManager }). Y CustomRuntime y StrandsRuntime({ language: "typescript" }) aceptan packageManager (pip, uv, npm, pnpm, yarn), pythonVersion y nodeVersion: el plan de instalación (install-plan.ts) baja CPython y Node con sha256 fijado en limits.json y hace la instalación congelada del gestor. Cuando el modelo va por la pasarela, el puente fuerza HTTP/1.1 en el AWS SDK de JavaScript, que de otro modo habla HTTP/2 con Bedrock. Nada cambia para los runtimes y los ficheros de proyecto de 0.13.
  • deployAgent() y AgentFleet: despliega el agente que ya tienes con una llamada (agent-zero-config): deployAgent(ruta) (y deploy_agent_async()) detecta el proyecto sin ejecutar su código, resuelve la región, la imagen base, el secreto del modelo, el bucket del artefacto (el de RAYITO_BUCKET o el de activos del bootstrap de CDK; nunca crea un bucket) y el access token, construye la imagen sólo si cambió y devuelve un AgentFleet: uno o varios MicroVMs que sirven el agente. ScalingOptions fija minVms, maxVms, las plazas calientes (warmVms, del pool de plazas aparcadas) y si las sesiones se comparten o cada una tiene su VM (dedicated). Con más de un VM posible, el sessionId del resultado es un handle rs1.<sandbox>.<sesión> que enruta derecho a su VM sin servidor propio; una flota llena espera en una cola cliente y, al agotarse, lanza CapacityError con code: "agent_capacity"; un VM que desaparece es SandboxError con code: "agent_vm_lost". Los VMs por encima de minVms se terminan solos (setTimeout), un VM que deja de contestar se sustituye y AgentFleet.connect() reconstruye la flota en otro proceso. planDeploy() calcula el plan y el coste de lista sin llamar a AWS. AgentDeployOptions, DetectOverrides y PoolConfig.accessToken son aditivos: AgentProject.deploy() no cambia. Nada que cueste se crea sin pedirlo: un secreto sólo con createSecretFromEnv: true, la imagen base sólo con un publicador.

  • rayito agent detect y rayito agent deploy [RUTA]: la CLI detecta el proyecto y lo despliega como una flota con --min-vms, --max-vms, --warm, --sessions, --max-sessions, --memory, --idle, --queue, --env, --allow-out, --bucket, --secret proveedor=nombre, --create-secret, --base-artifact con --yes, --build/--no-build, --dry-run (sólo plan y coste, sin AWS) y --verbose. El token se guarda en .rayito/agent-token (modo 0600) si no se pasa --token-file ni RAYITO_ACCESS_TOKEN, y los logs del build salen resumidos. invoke, status y stop aceptan el nombre de la flota además del id de un sandbox; de los VMs de esa imagen sólo cuentan los que son de un agente (los RUNNING con las etiquetas de un despliegue de Rayito, sondeados con un Health sin token), y si la imagen tiene un RUNNING que no lo es, tampoco se cuentan los suspendidos, que no se pueden sondear sin reanudarlos: stop nunca termina un sandbox ajeno. La salida de 0.13 de agent deploy se conserva (mismas claves, con otras añadidas) y --file sigue valiendo; lo que cambia está en "Cambiado".

  • Strands, Strands harness, Hermes, LangGraph y agentes propios como runtimes de primera clase (agent-runtimes-byo): StrandsRuntime (SDK y harness), HermesRuntime y CustomRuntime con HttpApp suman runtimes a AgentTemplate, Agent y AgentPool, y el tipo de proyecto langgraph cubre un grafo propio. Cada runtime instala sus dependencias con hashes fijados, el modelo sale por la pasarela de secretos (el agente nunca lee la credencial) y todo lo que sale a Internet queda apagado por defecto. Los puertos nuevos InstallableRuntime y ServableRuntime (con HostConfig, GatewayWiring/DirectWiring e InstallTarget) describen cómo se instala y cómo se sirve un runtime sin tocar el puerto AgentRuntime, así que los runtimes de 0.13 siguen funcionando. AgentTemplate acepta nombres del registro u objetos runtime.

  • AgentProject y AgentDeployment: del fichero rayito-agent.toml a un agente desplegado o exportado: AgentProject construye la imagen, ejecuta una vez, despliega un servicio (rayito.agent-service/1, el contrato HTTP de AgentCore: GET /ping y POST /invocations, con SSE) o exporta un contexto Docker/AgentCore (arm64, puerto 8080) sin llamar a AWS. AgentDeployment invoca, consulta DeploymentStatus y detiene el servicio. Es una función con coste (build, run y deploy crean imágenes y MicroVMs), apagada por defecto: nada corre sin llamar al método.

  • Peer opcional smol-toml: se carga de forma perezosa sólo al leer un rayito-agent.toml; sin él, el resto del SDK no cambia.

  • AgentProject y rayito agent: describe un agente en un fichero y construye, ejecuta, despliega o exporta (agent-runtimes-byo): la CLI suma rayito agent init, build, run, deploy, export, invoke, status y stop, conchas sobre AgentProject y AgentDeployment con --json, los valores por defecto del SDK y salida 2 para un fichero de proyecto inválido. init escribe rayito-agent.toml y un andamiaje por runtime (Strands, Strands harness, Hermes, LangGraph, deepagents, OpenCode o propio) sin sobrescribir nada sin --force; export y init no llaman a AWS, y deploy nunca imprime las cabeceras de acceso. El andamiaje de init se comparte con la CLI de Python en testdata/cli/agent-scaffold/. Guía nueva, Despliega tu agente, y un adaptador de Strands Sandbox sobre Rayito en docs/examples/strands_rayito_sandbox.py.

  • AgentProject.deploy() acepta el access token del sandbox y rayito agent deploy|invoke|status|stop se pueden encadenar entre procesos: deploy({ accessToken }) lo pasa a Sandbox.create(), y la CLI guarda el token en --token-file (o usa RAYITO_ACCESS_TOKEN) antes de crear el sandbox, igual que sandbox create --detach. Sin ninguno de los dos, deploy sale con 2 sin crear nada: antes dejaba un MicroVM que ni invoke, ni status ni stop podían volver a abrir. invoke y status leen el token con --token-file y sin él salen con 2; stop ya no lo necesita (terminate-microvm por id).

  • AgentDeployment.pause(), resume() y accessToken, y deploy({ keepOnFailure }) (agent-runtimes-dx): AgentDeployment suspende y reanuda el sandbox del despliegue (Sandbox.pause() y resume()) y expone el access token con el que se creó, también el que se generó cuando no se pasó accessToken, para reconectarse con AgentDeployment.connect(sandboxId, { accessToken }). AgentProject.deploy({ keepOnFailure: true }) deja vivo el sandbox de un despliegue que falla (apagado por defecto: sigue facturando hasta que lo termines), exige un accessToken que el llamador conozca y anota el id del sandbox en el error. rayito agent deploy --keep-on-failure hace lo mismo.

  • AgentResult.usageReported y Done.usageReported: false cuando el agente no informó de tokens (una HttpApp cuya respuesta no lleva usage, un comando con protocol: "text" o una función que no lo devuelve). usage vale entonces cero porque se desconoce, no porque no se gastara nada, y la CLI imprime tokens=desconocido y usageReported en --json. Una HttpApp informa de usage si su respuesta lo trae (input/output, input_tokens/output_tokens o prompt_tokens/completion_tokens).

  • HttpApp.hidesOwnRoutes y el aviso de rayito agent deploy: con protocol: "agentcore" u "openai-chat" el despliegue sólo expone /ping y /invocations; si tu healthPath o invokePath son otros, esas rutas no se alcanzan desde fuera (404). rayito agent deploy lo avisa y apunta a protocol: "raw"; la guía explica cuándo usar cada protocolo.

  • rayito image publish --base-image-version latest: resuelve la versión más reciente que lista list-managed-microvm-image-versions y la imprime (base_image_version=...), así no hay que buscarla a mano y el cambio de la base nunca pasa en silencio; la opción sigue siendo obligatoria y un número explícito sigue fijándola para una build reproducible.

Cambiado

  • rayito agent deploy ahora despliega una flota, con más llamadas a AWS (agent-zero-config): el mismo comando con un --file de 0.13 ya no crea un VM suelto sin más: comprueba la imagen base (GetMicrovmImage) y el secreto del modelo (DescribeSecret), resuelve el bucket (HeadBucket y GetCallerIdentity, y el de activos del bootstrap de CDK si no pasas --bucket ni RAYITO_BUCKET), construye la imagen del proyecto si su contenido cambió (excluye por defecto .git, .env*, .venv, venv, node_modules, __pycache__, *.pyc y .rayito, así que un proyecto que los subía a la imagen la reconstruye) y devuelve una flota. Las credenciales necesitan por tanto sts:GetCallerIdentity, s3:ListBucket sobre el bucket, lambda-microvms:GetMicrovmImage y secretsmanager:DescribeSecret, además de las de 0.13. Con --no-build y sin bucket reutiliza la imagen existente sin construir nada.

  • ServeOptions({ maxConcurrency }) queda acotada por la memoria de la VM: un MicroVM de memoryMib admite un número limitado de conexiones (7 con los 2048 MiB por defecto, 15 con 4096 y 31 con 8192); 0.13 aceptaba cualquier valor desde 1. Un proyecto de 0.13 con un valor mayor falla al cargarse con InvalidArgumentError: baja maxConcurrency o sube memoryMib.

  • AgentTemplate.runtimes y AgentTemplateOptions.runtimes pasan de readonly AgentTemplateRuntime[] a readonly (string | AgentRuntime)[]: quien sólo lee la propiedad como string debe tratar también objetos.

  • CustomRuntime corre tu app y tu comando desde el código copiado, con su venv primero en el PATH (agent-runtimes-dx): el directorio de trabajo de una HttpApp es el del source copiado (/opt/agents/app) y va el primero en su PYTHONPATH, así que python -m paquete.modulo funciona; con requirements, python y pip son los del venv, y sin él python es el intérprete de la imagen (que sólo trae python3). Los command de un CustomRuntime reciben el mismo PATH y PYTHONPATH, y el directorio de trabajo de un command no cambia. Hace falta reconstruir la imagen para que una ya construida lo use.

  • El uso de un agente que no informa de tokens es desconocido, no cero: el host marca usage_reported en POST /invocations y deja usage sin valor en el step_finished de un comando con protocol: "text" o de una función sin usage. Un cliente de 0.13 que no conoce el campo sigue leyendo usage.

  • Un arranque fallido del host cuenta qué falló: el marcador de fallo lleva el detail_code (app_exited, app_not_ready, la clase de la excepción) y, en una HttpApp, el final de su salida de error (acotado a AGENT_STARTUP_DIAGNOSTICS_MAX_BYTES, sin valores de entorno ni credenciales); deploy() lo incluye en la SandboxException en vez del nombre de clase opaco AgentFailure.

  • rayito agent init --runtime custom escribe una app HTTP y langgraph un grafo de LangGraph a secas: custom escribe [runtime.app] (con protocol = "raw", sus rutas /health e /invoke), requirements, install y un serve.py mínimo; langgraph, un create_react_agent en vez de create_deep_agent. El runtime langgraph traduce además el nodo agent de create_react_agent (antes sólo model), así que un grafo de LangGraph a secas devuelve texto, pasos, herramientas y uso.

Corregido

  • Exportar a Docker: el contexto crea los directorios de configuración del agente con permisos de lectura para el usuario sin privilegios (BuildKit los dejaba en 0600 con --chmod), la imagen de OpenCode instala curl antes de descargar los binarios y los módulos y fábricas de un grafo se resuelven como punto de entrada.
  • El host relanzado mata antes lo que dejó el hijo muerto en su grupo: un envoltorio como npm run start que muere dejaba al servidor huérfano ocupando el puerto y el relanzado nunca quedaba listo.
  • exclude de un AgentProject/AgentTemplate({ appExclude }) filtra antes de leer: un .venv excluido se leía entero y luego se descartaba (y su cacert.pem disparaba el aviso de ficheros con secretos). Ahora los patrones de exclude se suman a los de .dockerignore y filtran antes de leer ningún fichero.

Obsoleto

  • AgentTemplateRuntime: usa string | AgentRuntime. Sigue exportado y se retira en una versión posterior.

0.13.0 - 2026-10-16

Añadido

  • Documentación de la CLI, el servidor MCP y el panel en TypeScript (ADR-027, fase 9): cli.md, mcp.md, las páginas del panel, el proxy local, la instalación y optional-features.md llevan pestañas Python y TypeScript, y optional-features.md suma una fila para rayito-mcp. La API de TypeScript sigue siendo sólo asíncrona (ADR-027, D8).

  • CLI rayito en TypeScript, fases 2, 3 y 5 (ADR-027): doctor (las diez comprobaciones y --efs-vpc-id, con @aws-sdk/client-iam como peer opcional para la simulación de IAM), template build|status|logs, agent template build, events deploy|status|destroy|list y events webhook add|list|remove, domain deploy|status|destroy, dashboard cloudwatch e image publish|prune|zip, con la misma superficie, salida y códigos de salida que la CLI de Python. La tabla de compatibilidad SDK a rayd, el contrato de doctor, los dashboards de CloudWatch, los contratos puros de image (nombres, configuración deseada, planes de poda, entradas del zip) y los casos de órdenes por pila se comparten con Python en testdata/.

  • CLI rayito en TypeScript, fase 4 (ADR-027): sandbox connect y sandbox create sin --detach abren una terminal real (PTY del sandbox en modo raw, Ctrl-C como byte, cambio de tamaño reenviado, EOF en tuberías; salen con el código del shell y create termina el sandbox pase lo que pase), y sandbox proxy expone un puerto del guest en 127.0.0.1 con las mismas garantías que la CLI de Python: Host (421) y Origin (403) validados, un solo mensaje por conexión, delimitación ambigua o cabecera con CR/LF suelto en 400, tope de conexiones (503) y sin JWE en ningún informe. Los vectores de contrabando de cabeceras, delimitación del cuerpo y Host/Origin (testdata/cli/proxy-vectors.json) los ejecutan las dos CLI.

  • Servidor MCP rayito-mcp en TypeScript (ADR-027, fase 6): el paquete declara el ejecutable rayito-mcp (npx rayito-mcp; dist/mcp.mjs, sólo ESM, sin tocar exports ni importarse desde index) con las mismas seis herramientas que el de Python (run_code, run_command, read_file, write_file, list_files, list_sandboxes), las mismas anotaciones, las mismas variables RAYITO_TEMPLATE y RAYITO_MCP_* y un sandbox por proceso creado en la primera herramienta que lo necesita y terminado al salir. stdio por defecto y --http para streamable HTTP en loopback con Host y Origin exactos. @modelcontextprotocol/server (2.x) y zod son peers opcionales: sin ellos rayito sigue funcionando y el ejecutable explica la orden npx -y -p rayito -p @modelcontextprotocol/server -p zod rayito-mcp. Los nombres, descripciones, anotaciones y esquemas de entrada de las herramientas (testdata/mcp/tools.json), los casos de configuración (testdata/mcp/settings-cases.json) y las autoridades de --http (testdata/mcp/host-vectors.json) los ejecutan pytest y vitest.

  • CLI rayito en TypeScript, fase 1 (ADR-027): el paquete declara el ejecutable rayito (npx rayito ...; dist/cli.mjs, sólo ESM, sin tocar exports ni importarse desde index) con las opciones globales --profile, --region, --json, --verbose y --version (rayito 0.x (node)), los mismos códigos de salida que la CLI de Python y estas órdenes: sandbox list | info | kill | logs | metrics | exec | create --detach, stack list | status | deploy | destroy e image list | sizes. Salida y documentos --json idénticos a los de Python (tabla de rich incluida), comprobados con vectores y casos de testdata/cli/ que ejecutan las dos CLI. Las demás órdenes (doctor, template, events, domain, dashboard, sandbox connect|proxy, create sin --detach e image publish|prune|zip) se describen en las entradas siguientes y entran en el mismo cambio.

  • Panel local en TypeScript, fases 7 y 8 (ADR-027): npx rayito dashboard sirve en 127.0.0.1 la misma SPA y el mismo contrato /api/v1 que rayito dashboard de Python, con las mismas defensas (Host/Origin, token de arranque de un solo uso, cookie HttpOnly, Sec-Fetch-Site, CSRF, sólo lectura por defecto, CSP sin unsafe-inline, nada se registra) y las mismas opciones (--port, --allow-actions, --cost-explorer, --events-stack, --log-group, --theme, --logo, --account, --external-id, --open). Todo lo que cuesta sigue apagado hasta pedirlo. La SPA tiene una sola fuente (la del paquete de Python): el build la copia a dist/dashboard-static/ y pack:check falla si el tarball difiere byte a byte. Nuevos vectores compartidos con Python en testdata/dashboard/ (api-v1-cases.json, guard-vectors.json, security-headers.json, theme-vectors.json). Dependencia de desarrollo nueva: ajv (valida las respuestas contra api-v1.schema.json).

  • Pruebas locales de la CLI, el servidor MCP y el panel: los escenarios de tests/shared corren tanto contra AWS real como en el e2e local (Docker + Floci), y make local-e2e construye rayito-mcp para probarlo por stdio.

Corregido

  • rayito doctor: números de cuota como en Python: el resumen humano de quotas imprime los valores con format(value, "g") de Python (1e+06 en vez de 1000000), con vectores generalText compartidos en testdata/cli/format-vectors.json.

  • El reuso de una versión de imagen comparaba objetos por el orden de sus claves: AWS devuelve hooks con otro orden que el que se envía, así que Template.build() reconstruía una imagen idéntica. Ahora la igualdad es estructural y las variables de entorno (que la lista no devuelve) se confirman con GetMicrovmImageVersion, como en Python.

  • La CLI escribe los float de Python como Python: rayito --json sandbox metrics (cpu_used_pct), el JSON de doctor (el valor de una cuota y su mínimo) y las duraciones de image publish|prune|zip llevan su parte decimal (0.0, 10.0) en vez de 0 y 10, igual que la CLI de Python y que la tabla.

0.12.0 - 2026-10-15

Añadido

  • Cobertura e2e de la superficie del SDK: nuevos tests contra AWS real (transport, processes, filesystem, code y hardening, espejos de los bloques M1 a M4 de Python) y ampliaciones de m9-e2b, poly, m9-transfer, m9-timeout y m9-egress. Sólo tests: no cambia el SDK ni su API. Sin RAYITO_E2E=1 se omiten sin llamar a AWS.

  • Panel: parámetro AuthMode del componente dashboard (aws-iam pone la API tras una Function URL con firma SigV4; por defecto cognito-jwt), espejo del de Python con los mismos textos de coste.

  • e2e de sólo lectura de Quotas, UsageCost (el coste facturado sólo con RAYITO_E2E_BILLED_COST=1) y Fleet.

  • Coste a la vista en el panel (ADR-026): cada clase, método y opción del panel dice si es gratuito o de pago, y los textos de coste de los componentes dashboard, dashboard-cloudwatch, dashboard-read-role y quota-alerts son idénticos a los de Python (comprobado con un fichero de referencia común), con las cifras de limits.ts.

  • Panel, fase 3 (ADR-026): componentes de OptionalStacks dashboard-cloudwatch, dashboard-read-role y quota-alerts (deploy("quota-alerts", { artifactBucket })), espejo de los de Python: las mismas plantillas, el mismo zip y los mismos parámetros. Apagados hasta que los despliegas. Constantes nuevas en limits.ts. El generador del dashboard y la vista multicuenta son de la CLI de Python.

  • Pila dashboard (el panel de gestión dentro de tu cuenta, ADR-026, fase 2): componente de OptionalStacks (deploy("dashboard", { artifactBucket })), espejo del de Python: la misma plantilla, el mismo zip y los mismos parámetros (AllowActions, EnableCostExplorer, ThemeJson, ThemeParameterName...). De sólo lectura y sin Cost Explorer por defecto. Constantes nuevas en limits.ts (DASHBOARD_STACK_*). El coste facturado tiene tope aunque lo pida cualquier usuario con sesión (ventanas de meses completos, regiones conocidas y un presupuesto diario de consultas de pago para toda la pila) y las acciones se comprueban en vivo en Cognito antes de ejecutarse.

  • Servicios del panel de gestión (dashboard, ADR-026, fase 1): Quotas (cuotas aplicadas de Lambda MicroVMs, memoria asignada contra la cuota de la región y avisos al 80 % y al 95 %), UsageCost (coste facturado por Cost Explorer por tipo de uso, con caché local de 24 h, y coste estimado por sandbox, siempre rotulados) y Fleet (sandboxes con la memoria de su imagen, imágenes y versiones, historial de eventos y acciones masivas con una cola que respeta los TPS). Apagados por defecto: construirlos no llama a AWS, Cost Explorer ($0,01 por petición) sólo se toca con UsageCost.billed() y las acciones exigen allowActions: true. Nueva excepción DashboardError y constantes nuevas en limits.json (catálogo de cuotas, umbrales, TTL y precios de cómputo). Los clientes de Service Quotas y Cost Explorer son peers opcionales (@aws-sdk/client-service-quotas, @aws-sdk/client-cost-explorer). Cada clase, método y opción dice si es gratuito o de pago (billed() y events() son de pago; resume vuelve a facturar cómputo) con el mismo texto en ambos SDK. Guía: Panel de gestión.

  • Template.dnfInstall() y Template.npmInstall(packages?, { g }). Atajos del DSL para instalar paquetes del sistema con dnf (sin paquetes débiles) y paquetes Node con npm; los nombres se escapan para el shell y se rechazan las listas vacías y los nombres que empiezan por -. dnfInstall y npmInstall({ g: true }) corren como root aunque un setUser() previo dejara otro usuario. npmInstall falla con el código 127 y un mensaje claro si la imagen no tiene Node.js. aptInstall sigue lanzando UnimplementedError, ahora apuntando a dnfInstall.
  • SandboxHealth.imdsBlocked: true cuando rayd instaló y verificó el bloqueo de IMDS para los uids del sandbox (imagen con capabilities), como Health.imds_blocked de Python; false en la imagen por defecto y en un agente anterior a M6.
  • sbx.metadata: los metadata de create({ metadata }) tal como los devolvió el último Health (copia congelada), como Sandbox.metadata de Python.
  • rayito/e2b exporta los tipos de gráficos (Chart, LineChart, ScatterChart, BarChart, PieChart, BoxAndWhiskerChart, SuperChart, PointData, BarData, PieData, BoxAndWhiskerData), ChartType, ScaleType, ProcessInfo, WriteEntry y WatchHandle, los mismos nombres que rayito.e2b en Python.

Cambiado

  • La variante de imagen se reconoce con un catálogo cerrado (parity-image-variants): resolveImageVariant sólo interpreta rayito-<variante>[-<talla>] con la variante en base, base-caps, base-caps-efs, base-poly y base-slim. Una imagen propia que empieza por rayito- (p. ej. rayito-mi-app-caps-efs) ya no se interpreta como una variante conocida que no es caps ni se rechaza antes de lanzar: la decisión pasa a Health.features, como ya ocurría con un ARN. volumes exige rayito-base-caps-efs cuando la variante se conoce.

  • Componente dashboard-cloudwatch: DashboardName vacío (el valor por defecto) usa el nombre de la pila en lugar del fijo rayito; una pila ya desplegada conserva su nombre.

Corregido

  • Componente dashboard-cloudwatch: con IncludeOtlp o IncludeQuota los widgets añadidos se creaban con la región literal ${AWS::Region}; la plantilla (la misma que usa Python) resuelve ahora la región con Fn::Join y Ref AWS::Region.

0.11.0 - 2026-10-13

Cambiado

  • AgentTemplate arranca antes (ai-agent-first-start). Dos cambios en la imagen que construye, medidos en AWS real (AWS_API_NOTES.md Q155 y Q156): kernelWarmup: false por defecto (opción nueva) escribe el marcador slim del sidecar, así que el kernel de runCode ya no importa numpy, pandas, matplotlib, scipy y scikit-learn en cada arranque, y el demonio de prefetch lee una vez los binarios de OpenCode y ripgrep antes del snapshot del build, con un ready_cmd que lo espera, para que el snapshot de memoria ya los lleve. create() vuelve antes (6,4 s frente a 19,1 s de mediana en la misma tanda) y el snapshot de memoria pesa 62 MB menos; el primer token mejora poco y con mucha dispersión (20,9 s frente a 23,7 s de mediana). Las imágenes ya construidas no cambian.
  • Cambio de comportamiento: AgentTemplate ya no precalienta el kernel de runCode (kernelWarmup: false por defecto). En una imagen construida con 0.11, la primera celda de runCode que importe numpy, pandas, matplotlib, scipy o scikit-learn paga esa importación. Para volver al comportamiento de 0.10, construye la imagen con new AgentTemplate({ ..., kernelWarmup: true }).

Corregido

  • pool.take() y pool.close() ya no dejan que Node salga mientras esperan. Todos los temporizadores del pool, y los de los calentamientos en vuelo, iban sin referencia (unref()): con el pool vacío o con un relleno en marcha, un script cuyo único trabajo pendiente era await pool.take({ waitMs }) o await pool.close() salía con la promesa sin resolver (aviso unsettled top-level await, código 13) y dejaba vivos los MicroVM que se estaban calentando. Ahora take() y close() retienen el proceso con un intervalo con referencia mientras corren; los temporizadores del barrido y del backoff siguen sin ella, así que un pool ocioso sigue sin mantener vivo el proceso. Encontrado en la aceptación de 0.11.0 en AWS real.

0.10.0 - 2026-10-08

Añadido

  • Más proveedores para el modelo del agente (ai-agent-providers). AgentModel admite provider "openai" (Responses API, también xAI), "google" (Gemini API) y "azure" (Azure OpenAI v1), y hay nueve presets de pasarela con clave de API estática: openaiGateway, geminiGateway({ models }), azureOpenaiGateway({ resource }), openrouterGateway, groqGateway, mistralGateway, deepseekGateway, xaiGateway y litellmGateway({ upstream, basePath: "/v1" }). OpenCode usa los proveedores nativos openai, google y azure a través de la pasarela; deepagents construye ChatOpenAI(use_responses_api=True), ChatGoogleGenerativeAI o AzureChatOpenAI (Responses API, clave en api-key). El opencode.json declara el paquete del AI SDK de cada proveedor (@ai-sdk/openai, @ai-sdk/google, @ai-sdk/azure, todos dentro del binario fijado), Azure apunta a /openai/v1 y OPENCODE_EXPERIMENTAL_WEBSOCKETS queda apagado; la prueba de humo de AgentTemplate importa también langchain_openai y langchain_google_genai. gen_ai.provider.name vale gcp.gemini y azure.ai.openai para los nuevos. En las APIs al estilo de OpenAI la pasarela no puede limitar el modelo: fija el tope de gasto en el proveedor (SECURITY.md T29). Las suscripciones de consumo (plan de ChatGPT, Claude Pro/Max, Copilot, SuperGrok) no se admiten; la guía "Proveedores del agente" explica por qué. Construir un preset no llama a AWS.

Fixed

  • El timeout y abort() de sbx.agent paran también lo que lanzó la herramienta de shell del agente. OpenCode y deepagents corren cada orden en una sesión propia (setsid), fuera del grupo del runtime, y el timeout sólo mataba ese grupo: un sleep o un servidor lanzado por el agente sobrevivía y el siguiente run() encontraba el agente busy. Cada ejecución arranca ahora con killTree: true y rayd congela y mata todo su árbol (también lo demonizado); el stream acaba con reason: "timeout" cuando ya no queda nada. abort() y los límites del SDK sólo matan el handle: desaparece la orden pgrep que corría antes como segundo comando. commands.run() acepta killTree para lo mismo en cualquier comando. Necesita el rayd de esta versión: con uno anterior el campo se ignora y el timeout y abort() sólo alcanzan al grupo del runtime; reconstruye la imagen.

  • deepagents cuenta los tokens de los modelos compatibles con OpenAI. Con "openai-compatible", ChatOpenAI no pedía el uso en streaming (stream_options.include_usage): cada paso llegaba con 0 tokens, result.usage quedaba a cero y AgentLimits.maxTotalTokens no cortaba nunca. El runner crea ahora ChatOpenAI(stream_usage=True). Medido de verdad, con el egress cerrado, contra la Chat Completions compatible con OpenAI de Amazon Bedrock (bedrock-runtime /openai/v1 y bedrock-mantle) y un proxy de LiteLLM; la Responses API ("openai", "azure") ya mandaba el uso.

  • deepagents con modelos que no son de Bedrock: el venv de AgentTemplate (y del guest local) lleva socksio, el extra socks de httpx. rayd exporta ALL_PROXY=socks5h://... en el sandbox y, sin él, httpx fallaba al crear el cliente (AgentFailed con ImportError) con OpenAI, xAI, Azure OpenAI, Gemini, Anthropic y las APIs compatibles con OpenAI, aunque el modelo se llame por la pasarela en loopback. Cambia el sha256 de requirements-deepagents.txt: reconstruye la plantilla de agente.

0.9.1 - 2026-10-07

Fixed

  • Sandbox.create({ timeoutMs: 120_000 }) sin idle ya no lanza InvalidArgumentError. Desde 0.3.0, cualquier timeoutMs (o maxLifetimeMs con onTimeout) de 300 s o menos fallaba porque el SDK validaba su propia política de idle por defecto contra él. Ahora, sin idle, la auto-suspensión por defecto se desactiva si no cabe (el sandbox termina en su plazo, como en E2B) y, con onTimeout: "pause", baja a 60 s (PAUSE_DEFAULT_IDLE_FALLBACK_SECONDS). Un idle que pasas tú se valida igual que antes.

Documentation

  • Los e2e de s3-mounts y rayd-otlp documentan como precondición que la política gestionada de su pila (PolicyArn) esté adjunta al rol de ejecución; rayito stack deploy sólo la crea.

0.9.0 - 2026-10-07

Cambios que rompen

  • Se retira la opción D del arranque rápido del agente (pool con opencode serve residente), sin periodo de obsolescencia: apenas ganaba unas décimas a la opción C (AWS_API_NOTES.md Q154) y costaba más memoria y más por plaza. Desaparecen la opción serve de agentPoolWarmup() y de sbx.agent.prepare(), attach de AgentRunOptions (sbx.agent.run()/stream()) y RunRequest.attach; el script de OpenCode ya no busca ni se engancha a un servidor (--attach) ni relee la vuelta de él, y el span rayito.agent.run deja de llevar rayito.agent.attached. La interfaz AgentRuntime pierde abortCommand (ningún runtime lo usaba ya) y warmupSteps() no lleva argumentos; OPENCODE_SERVE_PORT desaparece. Migración: quita serve: true y attach; para un primer mensaje rápido usa agentPoolWarmup(runtime) (opción C) o el arranque normal (Sandbox.create(...) y sbx.agent.run(...)). Un runtime propio borra abortCommand y el parámetro de warmupSteps.
  • Se eliminan GatewayError y GatewayErrorOptions (no estarán en 0.9.0). GatewayError nunca se lanzaba: lo que la pasarela de secretos rechaza por petición llega al proceso del sandbox como respuesta HTTP (403, 429, 502, 504), y una configuración que rayd rechaza es SandboxError. Migración: quita el import y cualquier instanceof GatewayError; si capturabas fallos de la pasarela al crear o refrescar, usa instanceof SandboxError.

Changed

  • TSDoc alineado con la documentación (barrido de docs de 0.8): los errores sin TSDoc lo tienen, los códigos de MountError, AgentError (runtime_version_mismatch y output_limit, reservados) dicen lo que pasa de verdad, y reincarnate() lista todo lo que reaplica.

Fixed

  • Sandbox.connect() recupera sbx.gateways: otro proceso ya puede usar sbx.agent sobre un sandbox creado con gateways sin recrearlo. connect() lee una vez el ConfigureStatus de rayd (nombre, puerto y último error de cada ruta; nunca el upstream ni las cabeceras) si el agente anuncia secret_gateway y el handle no aplicó gateways él mismo. En un handle recuperado, refresh() sólo relee el estado: rotar la clave sigue siendo cosa del proceso que creó la pasarela. Si rayd rechaza el token en esa lectura, connect() no falla: se salta la recuperación y el AuthenticationError sale en la primera llamada autenticada, como hasta ahora.

0.8.0 - 2026-10-07

Cambios que rompen

  • registerWebhook (eventos de ciclo de vida) rechaza con InvalidArgumentError, antes de cualquier llamada a AWS, una URL cuyo host es localhost (o *.localhost) o una IP literal no pública (loopback, privada, link-local, CGNAT, reservada…). Hasta 0.7.1 se guardaba y el deliverer la descartaba en cada entrega, así que nunca se entregó nada. Migración: registra una URL HTTPS con un nombre público (detalle en "Security").

Added

  • Agente de IA, adaptador de deepagents (ai-agent-deepagents): runtime: "deepagents" o runtime: new DeepAgents({ entrypoint: "pkg.mod:build" }) corre un grafo de deepagents/LangGraph con el mismo contrato que OpenCode (eventos cerrados, límites del SDK, credenciales sólo por la pasarela). Configuración, script y traducción del protocolo JSONL v1 idénticos byte a byte a los de Python (testdata/agent/); el código del runner va en src/agent/assets/deepagents-runner.ts, generado por scripts/gen_agent_assets.py. mcp, rawConfig y attach: true fallan con InvalidArgumentError antes de cualquier RPC.
  • Agente de IA, arranque rápido (ai-agent-fast-start): AgentTemplate (y rayito agent template build en la CLI de Python) construye la imagen del agente con OpenCode y ripgrep fijados por sha256 y el venv de deepagents con --require-hashes (salvo runtimes: ["opencode"]), todo de root, el manifiesto rayito.agent-template/1 y, salvo prefetch: false, un demonio que precarga los binarios tras cada restauración del snapshot. PoolConfig gana warmup (pasos que cada plaza corre antes de aparcarse; un paso fallido es un calentamiento fallido), allowInternetAccess y network; agentPoolWarmup() da los pasos del agente y, con serve: true, deja un opencode serve residente con contraseña propia de cada VM al que agent.run se engancha tras el take(). Coste sólo si construyes la plantilla o pasas warmup (bloques "Coste y activación").
  • Agente de IA, adaptador de OpenCode (ai-agent-core): runtime="opencode" ya resuelve a un adaptador (src/agent/opencode.ts) que escribe opencode.json sin credenciales, lanza opencode run con el prompt por stdin, un cerrojo por sandbox y --attach a un opencode serve residente si responde, y traduce su JSONL a eventos. Configuración y script idénticos byte a byte entre Python y TypeScript (testdata/agent/).
  • Agente de IA, dominio (ai-agent-core, ADR-025): los tipos con los que sbx.agent describirá una ejecución, validados al construirlos y sin ninguna llamada a AWS: AgentSpec, AgentModel (Bedrock, Anthropic o una API compatible con OpenAI, siempre a través de una pasarela de sbx.gateways), AgentPermissions (sólo allow/deny; question, webfetch y websearch denegadas por defecto), SubAgent, McpLocal, McpRemote y AgentLimits (50 pasos, 600 s, 16 MiB y 1 000 000 de tokens por defecto); los eventos (unión discriminada por type; agentFailed() construye el de fallo), TokenUsage y AgentResult; AgentError con un reason de una lista cerrada y un mensaje fijo que nunca lleva texto del proveedor; y las pasarelas ya hechas bedrockGateway, anthropicGateway y openaiCompatibleGateway, cuyo allow cubre sólo los modelos elegidos. sbx.agent llega en un cambio posterior.
  • Agente de IA, API pública (ai-agent-core, design.md §4 y §7): el campo sbx.agent (run, stream, prepare). run() corre el agente hasta el final y lanza AgentError si falla; stream() devuelve un AgentStream (AsyncIterable<AgentEvent>) que nunca lanza por un fallo del agente —el último evento es Done o AgentFailed— y expone .abort(), .sessionId y .result(); un AbortSignal en stream() dispara abort(). prepare() dispara los pasos de calentamiento del runtime en segundo plano y vuelve enseguida. Aplica la configuración del runtime con files.writeFiles (una sola vez por sha de configuración), lanza el script del runtime con commands.run({ background: true, stdin: true }) y trocea su stdout en eventos; AgentLimits.maxSteps y maxTotalTokens los impone el propio SDK sobre esos eventos, no el runtime. sbx.agent no manda ningún RPC hasta el primer run()/ stream(). Con tracerProvider, cada ejecución abre un span rayito.agent.run con atributos gen_ai.* y rayito.agent.* (nunca el prompt, el texto de la respuesta ni argumentos de herramienta). Un AgentRuntime concreto (el adaptador de OpenCode) llega en un cambio posterior; mientras tanto, runtime admite cualquier objeto que implemente la interfaz AgentRuntime.

Fixed

  • sbx.agent con un opencode serve residente (agentPoolWarmup("opencode", { serve: true }), opción D): opencode run --attach (1.18.34, igual en 1.18.35) sale en cuanto el servidor contesta el prompt, sin esperar a sus propios eventos, y el SDK veía un agent_failed con reason: "protocol_error" o un Done sin texto aunque el servidor completaba la respuesta. Ahora el script crea la sesión en el servidor, y al salir run relee de él los mensajes de esa vuelta y emite las partes que faltaban (sin duplicar las ya emitidas), incluido el error del modelo. Un sessionId que no tiene forma de id de OpenCode es InvalidArgumentError.
  • AgentTemplate con prefetch: el demonio de precarga leía los binarios nada más restaurar el snapshot y su E/S competía con el arranque, así que create() tardaba ≈ 8 s más (Q146). Ahora espera a que el guest lleve 1 s sin E/S en curso (como mucho 60 s) antes de leer.
  • AgentTemplate: instala el runner de deepagents en /opt/agents/rayito/deepagents_runner.py (root, 0755) y pone su sha256 en runner_sha256 del manifiesto. Sin él, runtime="deepagents" fallaba siempre en una imagen construida con AgentTemplate (aceptación en AWS, AWS_API_NOTES.md Q150).
  • sbx.agent: abort() y los límites maxSteps/maxTotalTokens paran también lo que lanzó la herramienta de shell del agente (OpenCode y deepagents la corren en una sesión propia, fuera del grupo que mata rayd); antes un sleep o un servidor lanzado por el agente sobrevivía. Un límite del SDK, además, ya no deja el runtime trabajando (y gastando tokens) en segundo plano, y el siguiente run() no lo encuentra busy.
  • sbx.agent: un timeout de AgentLimits terminaba con reason="runtime_error" en vez de "timeout".
  • runtime: "deepagents": las herramientas de ficheros escribían una ruta absoluta dentro del workdir (/home/user/x acababa en /home/user/home/user/x); ahora usan la ruta tal cual, como el shell.
  • sbx.agent.prepare() arranca los pasos en segundo plano sin plazo: con serve: true el servidor residente moría al agotar timeoutMs.

Changed

  • Montajes S3: el MountError de un bucket que no está en el allowlist de la imagen (code: "not_allowed") ya no dice sólo que la sección se rechazó: explica que falta en RAYITO_ALLOWED_MOUNT_BUCKETS y cómo publicar la imagen con él (--env RAYITO_ALLOWED_MOUNT_BUCKETS=<bucket> o make image-publish-caps MOUNT_BUCKETS=<bucket>), sin nombrar el bucket. El texto vive en MOUNT_ERROR_HINTS, igual en los dos SDKs (testdata/s3-mounts/error-hints.json).

Security

  • Eventos de ciclo de vida (webhook-url-public-address): registerWebhook rechaza también, con InvalidArgumentError y antes de cualquier llamada a AWS, un host localhost (o bajo .localhost) y una IP literal que el guardián SSRF del deliverer bloquearía: loopback, privada, link-local (incluida la de metadatos), multicast, reservada, sin especificar o CGNAT, también como IPv6 con una IPv4 dentro (::ffff:127.0.0.1) o en las formas numéricas de IPv4 (0x7f.1, 2130706433). Antes se guardaba y el deliverer la descartaba en cada entrega. isBlockedWebhookAddress aplica la misma regla que el deliverer y los tres pasan testdata/lifecycle-events/ssrf-address-vectors.json. Un nombre DNS sigue sin resolverse al registrarlo: el DNS rebinding lo para el deliverer.

0.7.1 - 2026-10-06

Changed

  • Sin cambios en el SDK TypeScript: sube a 0.7.1 por las versiones enlazadas, con rayd 0.7.1 (avisos de licencia de terceros en el zip, la imagen y la release).

0.7.0 - 2026-10-05

Added

  • Dominio propio (m15-custom-domain, ADR-024, experimental, apagado por defecto): la clase CustomDomain despliega una distribución CloudFront, una CloudFront Function de enrutado (cloudfront-js-2.0) y un KeyValueStore (infra/custom-domain.yaml), y register/unregister/ refresh/hostFor gestionan las rutas {puerto}-{alias}.<tu-dominio> (hostname HTTPS normal, sin las cabeceras x-aws-proxy-*). register() exige trafficToken salvo public: true explícito. deploy({ alternateDomainNames }) sustituye el alias comodín por hostnames exactos. Nuevos peers opcionales @aws-sdk/client-cloudfront-keyvaluestore y @aws-sdk/signature-v4a (el plano de datos del KeyValueStore firma con SigV4A); sin instanciar CustomDomain no se importa ninguno. domain en Sandbox.create() sigue lanzando UnimplementedError: la integración con getHost()/expose() llega en un cambio posterior. Experimental porque no se ha verificado de punta a punta en AWS real: la Function compila y enruta en el runtime real (TestFunction), y la pila crea el KeyValueStore y la Function, pero la cuenta de pruebas deniega cloudfront:CreateDistribution por una SCP, así que nunca se ha servido tráfico por una distribución desplegada con esta plantilla (AWS_API_NOTES.md Q121, Q140, Q141). La API puede cambiar en una minor.
  • rayito-base-caps-efs (y sus sufijos de tamaño) cuenta como variante caps para mounts, volumes y telemetry (requireCapsFor, CAPS_VARIANTS, EFS_CAPS_VARIANT).
  • Volúmenes EFS (m15-efs-volumes, ADR-018, experimental, apagado por defecto): VolumeStore (CRUD real de access points EFS: CreateAccessPointCommand/DescribeAccessPointsCommand/ DeleteAccessPointCommand, con @aws-sdk/client-efs como peer opcional cargado sólo en el primer uso) y Sandbox.create({ volumes }), que valida la petición (tipos, rutas, variante caps y un único conector propio en egress: sin él, con INTERNET_EGRESS o con dos conectores es InvalidArgumentError, porque un MicroVM sólo admite un conector de egress, AWS_API_NOTES.md §16 Q131), como mucho 4 volúmenes y executionRoleArn antes de cualquier llamada a AWS; resuelve la IP de mount target que falte con un DescribeMountTargetsCommand por sistema de ficheros antes de run-microvm, manda la sección efs_volumes en el único ConfigureSandbox de create() (con plazo de 65 s), sólo vuelve con todos los volúmenes mounted y, si uno falla, termina el sandbox (salvo keepOnFailure) y lanza VolumeMountError (code); sobre una imagen sin amazon-efs-utils, UnimplementedError. reincarnate() la vuelve a mandar y sbx.volumes() da el estado en vivo. EfsVolume (valida mountTargetIp como IPv4), VolumeStatus y los errores VolumeError/VolumeMountError/VolumeNotFoundError/VolumePathNotFoundError. El shim de E2B (Volume) hace CRUD real sobre new E2B({ volumeStore }) (volumeId es el nombre del volumen, el mismo que reciben connect/getInfo/destroy); sus operaciones de contenido (UnimplementedError("volume.content")) siguen sin plano de datos, y Sandbox.create({ volumeMounts }) monta con new E2B({ volumeStore, volumeConnectorArn }): ese sandbox sale sólo por el conector del volumen y allowInternetAccess: true explícito es InvalidArgumentError. Componente rayito stack {deploy,status,destroy} efs-volumes (infra/efs-volumes.yaml: sistema de ficheros EFS cifrado, un mount target por subred de SubnetIds, grupos de seguridad NFS nuevos y conector de egress dedicado, sólo dentro de una VPC que ya existe). EfsVolumes: check({ vpcId, subnetIds }) comprueba la VPC sin crear nada (sólo Describe* de EC2 con el peer opcional @aws-sdk/client-ec2; cuenta las subredes con ruta por defecto a un NAT y a otra puerta, como un transit gateway), deploy() se niega si algún hallazgo es FAIL, volumeStore() da un VolumeStore sobre la pila, y destroy({ deleteFileSystem: true })/ deleteFileSystem(id) borran el sistema de ficheros conservado (sólo uno con la etiqueta rayito=efs-volumes). deploy({ readOnlyAccessPointArns }) (ReadOnlyAccessPointArns) le deniega ClientWrite a los access points de sólo lectura: la opción ro del montaje no basta, porque el usuario del sandbox alcanza el puerto local de efs-proxy (Q133). El hallazgo internet-egress de check() explica que un sandbox con volumen sólo tiene internet por la VPC. list/get son eventualmente consistentes, como DescribeAccessPoints (medido en AWS real, AWS_API_NOTES.md §16 Q125: hasta 11 s en listar un access point nuevo y 8 s en dejar de listar uno borrado): create de un nombre que ya existe reintenta get hasta 30 s y destroy de un access point que el listado aún mostraba pero ya no existe devuelve false.

Security

  • Eventos de ciclo de vida (events-webhooks): una línea maliciosa ya no tira los eventos legítimos de su lote. El forwarder verifica el MAC con la clave del sandbox que nombra el log stream antes de leer el contenido, parsea de forma estricta y nunca falla la invocación por una línea: la cuenta (malformed_line, mac_invalid, stale_event, internal_error) y sigue. Si falla una escritura en la tabla, el resto del lote se escribe igual y el lote va a la nueva cola ForwarderFailuresQueue (destino OnFailure de su invocación asíncrona) en vez de perderse. El nombre del stream deja de presentarse como prueba de identidad: sólo el MAC autentica (SECURITY.md T22).
  • Eventos: sólo lo que emite rayd, y reciente. Tras el MAC, el evento debe ser del mismo sandbox que el stream, con un event_id de 32 hex (nunca un id del reconciliador), kill_reason sólo en killed y sólo request, un ARN de microvm-image y un occurred_at_ms de menos de 24 h (y no más de 5 min en el futuro): una línea capturada y repetida cuando su fila de deduplicación ya caducó se rechaza. La deduplicación de entregas pasa a DELIVERY#<sandbox_id>#<event_id>, así que un sandbox no puede marcar como entregado el evento de otro (las entregas en curso al actualizar la pila pueden repetirse una vez).
  • Webhooks: uno roto o lento ya no bloquea a los demás. El deliverer trata una URL que no puede parsear como un fallo permanente de ese webhook (invalid_url), aísla cualquier error inesperado a su webhook (internal_error) y el plazo de cada intento cubre la resolución DNS y la respuesta entera. registerWebhook rechaza con InvalidArgumentError una URL que el deliverer no podría alcanzar (puerto fuera de 1-65535, host inválido o no codificable en IDNA, espacios o barras invertidas), con el mismo criterio en los dos SDKs.
  • Código de las pilas opcionales: nunca se fía de un objeto que ya exista. OptionalStacks.deploy() sube el zip a rayito/stacks/<componente>/<sha256>.zip (dentro del espacio rayito/ que protegen las plantillas de IAM, ya no en la raíz del bucket), manda ExpectedBucketOwner con la cuenta del llamante y, si el objeto ya existe, compara su contenido antes de reutilizarlo (si no coincide, lo sobrescribe con ChecksumSHA256 y avisa por el logger).
  • infra/iam.yaml: el lanzador ya no puede publicar imágenes. Nuevas políticas SandboxLauncherPolicy (sólo lanzar y manejar sandboxes: la del rol de un servicio en producción) e ImagePublisherPolicy (sólo publicar); CallerPolicy sigue igual, como su unión. El Deny del execution role sobre los artefactos de imagen existe ya siempre, no sólo con PersistenceBucket.
  • infra/templates.yaml: RayitoTemplateBuilder sólo lee rayito/* del bucket de artefactos o de la imagen base, nunca el bucket entero (que puede guardar los HOME persistidos y las transferencias).
  • Secretos de firma de webhooks fuera de secrets=. RayitoSecretsReader niega rayito/webhooks/* y el SDK se niega a leer esos secretos por secrets/SecretCache (InvalidArgumentError). SecretPrefix de infra/secrets-access.yaml debe terminar en /.
  • Mínimo privilegio en events-webhooks: cada Lambda escribe sólo sus tipos de fila (dynamodb:LeadingKeys) y sólo en su propio log group (la pila crea uno por función y lo borra con ella), y el rol del Scheduler exige aws:SourceAccount.
  • Eventos: el código del sandbox ya no puede inundar la pila con paused/resumed. Esos dos eventos los emite rayd cuando corren sus hooks de suspensión y reanudación, que el código sin privilegios del sandbox puede invocar. El forwarder sólo admite ahora eventos que hagan avanzar el ciclo de vida de su sandbox (nada tras killed, ningún created repetido, ninguna posición repetida o anterior) y limita paused/resumed con un cubo por sandbox (20 seguidos, después uno cada 30 s); lo rechazado se cuenta como invalid_transition o rate_limited y nunca se guarda ni se entrega. El reconciliador consulta un índice disperso de sandboxes abiertos en vez de escanear la tabla entera, y el deliverer sólo se invoca por eventos nuevos. paused/resumed quedan documentados como orientativos (SECURITY.md T22). Al actualizar la pila, un sandbox que ya estaba abierto entra en el índice con su siguiente evento.
  • Webhooks: segunda firma con marca de tiempo. Cada entrega lleva además rayito-signature: t=<segundos>,v1=<HMAC-SHA256> sobre "<t>.<webhook_id>." y el cuerpo; la guía de eventos explica cómo verificarla con una ventana de 5 minutos. e2b-signature no cambia.
  • Webhooks: un secreto rotado deja de usarse en 5 minutos. El deliverer guardaba cada secreto en caché mientras viviera su contenedor; ahora lo relee cada 5 minutos y, si el receptor responde 401/403, lo relee en el acto y reintenta una vez con el nuevo.
  • Políticas del llamante por tarea en events-webhooks. La pila emite EventsLauncherPolicy (events=: sólo la clave del stack), EventsReaderPolicy (getEvents: sólo filas de eventos; ya no puede registrar un webhook ni leer la clave del stack) y EventsWebhookAdminPolicy (registerWebhook/listar/borrar: sólo filas WEBHOOK). La guía de eventos tiene la tabla de qué llamada necesita cuál y avisa de que los webhooks son de toda la pila.
  • sizes-guard: RayitoRunAllowedSizes niega también publicar imágenes. Sin ello, una identidad con la CallerPolicy estándar podía reconstruir una imagen permitida a un tamaño mayor y lanzarla sin salir de la lista. La política ya no dice que valga "sin importar lo demás": una versión antigua y mayor bajo un nombre permitido sigue siendo lanzable con imageVersion (SECURITY.md T27).
  • SECURITY.md: filas T20, T23, T26 y T27, que los cambios de M15 planearon y nunca se aplicaron (infra/otlp-export.yaml ya citaba T23).
  • infra/ci-oidc-role.yaml y infra/README.md piden una cuenta dedicada a e2e (o imágenes de test propias): el rol puede acuñar tokens y terminar cualquier sandbox de las imágenes de TestImageArns.
  • SecretGateway rechaza una ruta de allow que rayd nunca dejaría pasar (sec-rayd-agent-hardening): las rutas de allow siguen ahora la misma lista de permitidos que rayd aplica a cada petición (caracteres de ruta de RFC 3986 salvo ;, sin segmentos vacíos salvo el último, y cada segmento decodificado una sola vez UTF-8 válido, sin /, \, %, ; ni bytes de control y distinto de ./..), y una que no la cumpla lanza InvalidArgumentError antes de cualquier llamada, con los mismos vectores compartidos que rayd (testdata/secret-gateway/request-paths.json).
  • .dockerignore con la semántica de Docker en Template.build: anclado en la raíz, ** como cero o más directorios (así **/.env, **/.git y el resto de los valores de docker init excluyen también los de la raíz, que antes acababan en la imagen), un directorio excluido excluye lo de dentro y la última coincidencia gana; mismos vectores que el SDK de Python. Cambio: * y ? ya no cruzan / (*.pyc sólo excluye los de la raíz; usa **/*.pyc). Un process.emitWarning de tipo RayitoContextWarning (con las rutas, nunca el contenido) avisa si el contexto va a empaquetar .env*, .git/, .aws/, .ssh/, *.pem o *.key. Cada fichero del contexto se vuelve a comprobar dentro del contexto y se abre con O_NOFOLLOW justo antes de leerlo.
  • Template.build(): un rechazo de AWS distinto de la cuota es ahora BuildError({ reason: "aws_error" }) con el resumen saneado como mensaje y cause, y el adaptador sanea cada llamada al SDK de AWS (el error crudo de smithy lleva $response, la petición firmada; un error de firma lleva la cadena canónica con el token de sesión). El name del error se conserva. StackError usa también el resumen saneado como cause.
  • Credenciales de git: clone/push/pull con username/password corren sin hooks ni credential helpers, se niegan (GitAuthError) si la configuración de git reescribe URLs (url.*.insteadOf), y un clone que falla sin exit de git intenta igualmente quitar las credenciales de origin; si una restauración falla, el logger del sandbox avisa sin la URL. La documentación dice ahora que el token queda al alcance del código del sandbox.
  • Salida acotada en memoria: cada descriptor de un comando o una PTY guarda como mucho 64 MiB (COMMAND_OUTPUT_MAX_BYTES); se conserva el final y CommandResult.truncated/CommandExitError.truncated lo indican. commands.run/connect aceptan maxOutputBytes (0 no guarda nada; los callbacks reciben siempre todo).
  • Access token mínimo: un token propio (accessToken, RAYITO_ACCESS_TOKEN) tiene que decodificar a al menos 16 bytes (ACCESS_TOKEN_MIN_BYTES). Cambio: uno más corto, aceptado antes, ahora es InvalidArgumentError. Los generados (32 bytes) no cambian.
  • ProxyToken.jwe ya no es enumerable: console.log/util.inspect y JSON.stringify no lo muestran.
  • Tests que fijan que Template.build() no sigue enlaces simbólicos dentro de un directorio copiado (misma regla que el SDK de Python).
  • Sandbox.create({ persist }) ligan el sandbox a su prefijo de persistencia (C-07): el runHookPayload lleva el bucket y la base prefix del S3Prefix (nunca prefix/name), y un rayd con la corrección rechaza con permission_denied cualquier checkpoint o restore fuera de esa base. Con un prefix por inquilino, el token de un sandbox ya no alcanza el HOME persistido de otro aunque compartan execution role. Cambia un uso: un restore o checkpoint explícito hacia otra base desde un sandbox creado con persist= ahora falla con permission_denied. No añade llamadas a AWS.
  • Recortes y troceo de cadenas en tiempo lineal (CodeQL js/polynomial-redos): git.clone() con credenciales, files.download() sin filename, el access token y las líneas de .dockerignore de un template usaban expresiones regulares que retrocedían en tiempo cuadrático (50 000 caracteres bloqueaban el bucle de eventos más de un segundo). La semántica no cambia: una URL con un fin de línea tras la autoridad sigue sin reconocerse como http(s).

Deprecated

  • EventsOperatorPolicy (salida OperatorPolicyArn de events-webhooks): sigue siendo la unión de las tres políticas nuevas durante esta versión. Vincula a cada identidad la suya (EventsLauncherPolicy, EventsReaderPolicy o EventsWebhookAdminPolicy).

0.6.1 - 2026-10-04

Fixed

  • Redesplegar una pila opcional ya no deshace su configuración (OptionalStacks.deploy()): los valores por defecto del catálogo sólo se aplican al crear la pila; al actualizarla, cada parámetro que no vuelves a pasar y la pila ya tiene se manda con UsePreviousValue. Antes, por ejemplo, redesplegar s3-mounts sin repetir Prefixes volvía a '*' (todo el bucket), metadata-index sin TableName reemplazaba (y borraba) la tabla y secrets-access sin KmsKeyArn quitaba kms:Decrypt. Nuevo parameterChanges() para ver qué cambiaría sin desplegar.
  • Guardián SSRF de los webhooks (pila events-webhooks): una dirección IPv6 que encapsula una IPv4 (::ffff:100.64.0.1) se clasifica como esa IPv4, así que el rango CGNAT queda bloqueado también por esa vía. Se regenera el artefacto de la Lambda que el SDK sube.
  • volumes/domain lanzan UnimplementedError con "todavía no disponible" en vez de "llega en 0.6".
  • Se exporta WorkdirStep desde rayito (paridad con Python: ya era miembro de la unión WireStep).
  • reincarnate() reaplica todas las secciones de ConfigureSandbox (m15-reincarnate-configure-replay): hasta ahora sólo reenviaba gateways y un sucesor perdía mounts, events y telemetry. create() guarda sus opciones 0.6 en LaunchOptions.features (relaunchFeatures: todas menos size) y reincarnate() las reenvía (relaunchCreateOptions) al create() del sucesor, que las aplica por el mismo camino en su único Configure: events deriva k_sbx del nuevo sandboxId, mounts espera otra vez a mounted y telemetry usa la imagen y la memoria del sucesor. De paso, reincarnate() de un sandbox creado con size ya no reenvía el tamaño resuelto junto al ARN de la imagen (que ya lo lleva), combinación que create() rechaza.
  • Tipos correctos para consumidores CommonJS (exports de package.json): "rayito" y "rayito/e2b" declaraban un types hermano de import/require, que TypeScript elige antes que ambos, así que un proyecto CommonJS con moduleResolution: node16/nodenext recibía los .d.mts (ESM) para un require que carga el .cjs ("Masquerading as ESM" de arethetypeswrong). Ahora cada condición lleva su types (import → .d.mts, require → .d.cts), el types de nivel superior apunta al .d.cts de main, se exporta "./package.json" y pnpm pack:check rechaza un mapa que vuelva a la forma anterior. Mismos ficheros en tiempo de ejecución.

Documentation

  • Bloques "Coste y activación" completos para mounts/S3Mount y size/el catálogo de tamaños, y filas de las seis funciones 0.6 en la tabla de funciones opcionales; la referencia de la CLI, la paridad con E2B, la guía de migración, limits.md y las notas de 0.6.0 ya no describen como pendientes funciones publicadas en 0.6.0.
  • DynamoDbIndex enseña su bloque "Coste y activación" en el hover del IDE: vivía en el comentario de módulo, que tsdown descarta; ahora está en el TSDoc de la clase y check-dts-cost-blocks lo exige.
  • Metadatos del paquete: package.json declara homepage (el sitio de documentación) y bugs; el README del paquete termina con la licencia, el NOTICE incluido y la nota de marcas (proyecto independiente, no afiliado a E2B). El NOTICE conserva además el copyright de E2B en la atribución del código git adaptado (Apache-2.0 §4(c)).

0.6.0 - 2026-10-03

Added

  • Convenio OptionalStack y ConfigureSandbox (v06-foundations, M15 foundations, ADR-015/ADR-016, opcional y apagado por defecto): la clase OptionalStacks (deploy/status/destroy/components) sobre un catálogo de nueve componentes (metadata-index y secrets-access, migrados sin cambios de comportamiento; los otros siete son stubs hasta su propia función), con @aws-sdk/client-cloudformation como peer opcional; las siete opciones 0.6 de Sandbox.create() (mounts, volumes, size, events, telemetry, gateways, domain) existen ya en SandboxCreateOptions y lanzan UnimplementedError nombrando el cambio que las trae mientras sigan siendo un stub, antes de run-microvm; en cuanto una deja de serlo (mounts, ver más abajo), create() ejecuta sus configureSections justo tras el primer Health (configure-base.ts: requireCapabilities, buildConfigureRequest, checkConfigureResponse), dentro del mismo camino que ya termina el sandbox ante cualquier fallo anterior a agentReady. Sin ninguna opción nueva, el comportamiento es byte a byte el de 0.5.x.
  • mounts (m15-s3-mounts, ADR-017, experimental, apagado por defecto): Sandbox.create({ mounts }) monta uno o más buckets S3 (new S3Mount({ bucket, prefix, readOnly: true, allowOverwrite: false, allowDelete: false }), exportado desde rayito) en el guest con mount-s3/FUSE, sólo sobre rayito-base-caps (requireCapsFor lo exige antes de run-microvm cuando la imagen se nombra directamente; sobre un ARN opaco la decisión se difiere a Health.features tras /run, que termina el sandbox si falta la capacidad). mounts acepta un objeto literal (como volumes) o un Map. create() no vuelve hasta que cada montaje está montado: sondea ConfigureStatus (como mucho 15 s) y, si uno falla o no se asienta, termina el sandbox y lanza MountError. sbx.mounts() da el estado en vivo de cada montaje ("pending"/"mounted"/ "failed", ConfigureStatus en cada llamada); una sección rechazada o un montaje fallido lanza MountError con un code cerrado (network, iam_denied, not_found, not_allowed, invalid_path, helper_missing, timeout). rayd lanza mount-s3 como el usuario dedicado rayito-mount (uid 990) con credenciales resueltas por su propio acceso a IMDS, nunca en argv ni en entorno; un daemon caído se relanza solo, con backoff. Política IAM RayitoS3MountAccess del componente OptionalStack s3-mounts (infra/s3-mounts.yaml, pide CAPABILITY_IAM; su plantilla ya va empaquetada en el SDK), acotada al bucket y a sus prefijos (hasta 4) también para leer, escribir y borrar objetos. El bucket debe estar en RAYITO_ALLOWED_MOUNT_BUCKETS de la imagen.
  • Catálogo de tamaños (m15-sizes-catalog, ADR-019, opcional y apagado por defecto): Sandbox.create({ size: "4gb" })/ { memoryMib: ... } resuelve, enteramente en cliente y sin ningún RPC, al primer tamaño del catálogo cerrado (512mb/1gb/2gb/4gb/8gb, Q87) que cubra lo pedido (redondea siempre hacia arriba, avisa con process.emitWarning(..., { type: "RayitoCompatWarning" }) si no encaja exacto) y antepone el sufijo de imagen (rayito-base-4gb) antes de resolver el ARN; size con un template dado por ARN, o por encima del máximo publicado, es InvalidArgumentError antes de cualquier llamada a AWS. getInfo() confirma, con una única llamada cacheada a GetMicrovmImageVersion por versión de imagen, SandboxInfo.baselineMemoryMib y baselineCpu (vCPU medido exactamente para los cinco tamaños del catálogo); cpuCount/memoryMb siguen siendo lo que el guest reporta de verdad. El shim rayito/e2b reporta ese mismo baseline en SandboxInfo.cpuCount/memoryMB cuando size se usó (como E2B reporta lo declarado por el template), la vista real del guest si no. Guardarraíles de coste opcional sizes-guard (RayitoRunAllowedSizes: un Deny de lambda:RunMicrovm fuera de los ARN de imagen permitidos, efectivo aunque la identidad ya tenga el microvm-image:* de la CallerPolicy estándar; rayito image publish --sizes/--env y rayito image sizes son sólo CLI Python). Sin size, el comportamiento sigue siendo exactamente el de 0.5.x.
  • LifecycleEvents (m15-events-webhooks, opcional y apagado por defecto): despliega infra/events-webhooks.yaml (deploy/status/ destroy, componente events-webhooks de OptionalStacks), registra webhooks compatibles con E2B (registerWebhook/listWebhooks/ deleteWebhook, paginado) y lee el historial (getEvents, 1–100 filas, filtrado por tipo en DynamoDB), con @aws-sdk/client-dynamodb y @aws-sdk/client-secrets-manager como peers opcionales. Los errores de AWS llegan como WebhookError con sólo el código (awsCode). events en Sandbox.create() valida el tipo y que logging llegue a CloudWatch antes de lanzar y, tras run-microvm, manda la clave del sandbox (k_sbx, derivada de la clave del stack y el sandboxId; un GetSecretValue por instancia de LifecycleEvents) en el mismo ConfigureSandbox que mounts/gateways/telemetry (LifecycleEventsSectionFactory); sin la pila desplegada, sin lifecycleEvents en el agente o con la sección rechazada, termina el sandbox (salvo keepOnFailure). events pasa a tiparse LifecycleEvents en SandboxCreateOptions. Aceptado en AWS real (AWS_API_NOTES.md Q105–Q108); el .gen.ts lleva el código Lambda actualizado (comprobación exacta del log stream y una línea JSON por invocación del forwarder y del reconciliador).
  • telemetry: exportación OTLP de rayd a CloudWatch (m15-rayd-otlp, ADR-021, opcional y apagado por defecto): TelemetryExport/OtlpAuth nuevos (OtlpAuth.executionRole(), exige rayito-base-caps; OtlpAuth.bearer(secretName), experimental, funciona en rayito-base; el nombre se resuelve bajo rayito/ por la misma SecretCache que secrets). Se envía como una sección de ConfigureSandbox tras /run; una imagen cuyo rayd no exporta termina el sandbox (salvo keepOnFailure) y lanza UnimplementedError. Con tracerProvider, cada RPC del handle lleva además el traceparent del span de esa misma llamada hacia rayd; sin él, ninguna cabecera nueva. El token de OtlpAuth.bearer(...) es una API key de CloudWatch Metrics. sbx.getTelemetryStatus() lee ConfigureStatus, como una llamada explícita aparte de getHealth(). Sin telemetry, ningún comportamiento cambia frente a 0.5.x.
  • Templates declarativos (m15-templates, ADR-022, opcional y apagado por defecto): Template compila un DSL (igual al Template de E2B v2) a un Dockerfile y un zip deterministas sobre una imagen rayito-base/ rayito-base-caps ya publicada; Template.build()/buildInBackground()/ getBuildStatus()/exists() suben el artefacto por hash de contenido y llaman a create/update-microvm-image, reutilizando una versión idéntica en vez de reconstruir. Un build fallido se explica con BuildError (step/command/exitCode/logTail del log de BuildKit, o reason: "ready_client_error"|"ready_server_error" si falló el readyCmd), sin repetir nada. fromImage/fromTemplate/ fromDockerfile/fromGcpRegistry/aptInstall lanzan UnimplementedError (documentados en ADR-022). El shim rayito/e2b's Template ya construye de verdad, con la firma de E2B ({ alias, skipCache, memoryMB, cpuCount }) y new E2B({ bucket }); BuildError/TemplateError del shim pasan a ser alias de las clases nativas (igual patrón que NotEnoughSpaceError/FileUploadError). setStartCmd() necesita una imagen base con rayd 0.6. La imagen compuesta hereda la configuración de la base (additionalOsCapabilities incluida); skipCache() equivale a force: true; la cuota de builds de AWS llega como reason: "build_quota". El núcleo de build vive en src/images/gateway.ts. Sin llamar a Template.build(), el SDK no importa estáticamente @aws-sdk/client-s3 ni carga @aws-sdk/client-cloudwatch-logs (peer opcional nuevo, sólo para explicar un build fallido). infra/templates.yaml (rayito stack deploy templates): sólo la política IAM RayitoTemplateBuilder, $0 en reposo, que no puede sobrescribir las imágenes base publicadas. Aceptación en AWS real: pipInstall() compila a python3 -m pip install --no-cache-dir --break-system-packages (rayito-base no tiene pip en el PATH); la versión gestionada que se hereda se envía como 1, no como el eco 1.0 que create-microvm-image rechaza; la política concede CreateMicrovmImage sobre * (AWS no la autoriza por ARN) y lambda:PassNetworkConnector sobre los conectores gestionados, y su Deny cubre UpdateMicrovmImage sobre las bases (AWS_API_NOTES.md Q114-Q116).
  • gateways — pasarela de secretos en loopback (m15-secrets-gateway, M15, ADR-023, opcional y apagado por defecto): Sandbox.create({ gateways: { nombre: new SecretGateway({ upstream, headers, allow, ... }) } }) abre, dentro del agente, un listener de loopback por ruta que reenvía sólo lo que su allow cubre, dentro de su límite de peticiones por minuto, inyectando cada cabecera vaultada (resuelta con la misma SecretCache que secrets, nunca antes de Configure) y eliminando primero cualquier cabecera del mismo nombre que el sandbox intente poner. sbx.gateways.get("nombre")?.url da la URL de loopback; refresh() rota el secreto sin recrear el sandbox (relee Secrets Manager aunque la SecretCache no haya vencido, conserva el puerto y lanza si rayd rechaza la sección). También pool.take({ gateways }). Cualquier fallo al configurarla tras run-microvm (imagen anterior a 0.6.0, flag ausente, secreto que falta, sección rechazada) termina el VM salvo keepOnFailure. Sin gateways, ningún cliente Secrets Manager nuevo se construye y no se manda ningún ConfigureSandbox.

Fixed

  • OptionalStacks.deploy("events-webhooks", { artifactBucket }) ya no exige repetir parameters.ArtifactBucket: StackArtifact.bucketParameterKey pasa a la plantilla el mismo bucket al que se sube el código, y un valor distinto es InvalidArgumentError antes de subir nada (aceptación 0.6 en AWS real).

0.5.1 - 2026-10-01

Fixed

  • Los mensajes de error y los docstrings del SDK nombran la versión de la imagen o del SDK que hace falta (por ejemplo, "necesita una imagen 0.3.0 o posterior"), en lugar del hito interno (M9, M12…).

Documentation

  • Sitio de documentación reorganizado por tareas: primeros pasos, guías por función con ejemplos en Python y TypeScript, funciones opcionales con su coste, migración desde E2B y referencia (Python, TypeScript, CLI, errores y variables de entorno). Los ejemplos se comprueban en CI.

0.5.0 - 2026-10-01

Added

  • Spans OpenTelemetry del lado del SDK (m13-otel-sdk, opcional y apagado por defecto, ADR-014): tracerProvider en Sandbox.create() (también con pool), connect() (ambas formas) y las variantes estáticas kill/pause/resume; instrumenta commands.run, runCode, files.* y kill/pause/resume de instancia con spans rayito.* de SpanKind.CLIENT. @opentelemetry/api es una peerDependency opcional importada sólo con import type (se borra en el build: ningún require/import en tiempo de ejecución, comprobado por pack:check), y SpanKind.CLIENT/SpanStatusCode.ERROR son constantes numéricas locales. Lista cerrada de atributos (nunca texto de comandos, código, rutas, envs, secretos ni metadata); un error del span lleva el nombre de la clase del error, nunca su message. $0 de AWS; el coste (si lo hay) es el del backend de exportación del llamante. Fuera de alcance: propagación traceparent/tracestate hacia rayd, telemetría del sandbox y el shim de E2B.

  • Índice de metadatos sobre DynamoDB (m14-metadata-index, opcional y apagado por defecto, ADR-014): new DynamoDbIndex({ tableName, region, credentials, onWriteFailure: "terminate", ttlMarginSeconds: 3600 }) y la opción index en Sandbox.create(), Sandbox.list(), Sandbox.paginate() y PoolConfig. create() escribe una fila inmutable (PutItemCommand condicional) tras run-microvm; con metadata e index, list()/paginate() unen list-microvms con BatchGetItemCommand por página y filtran también sandboxes SUSPENDED sin ninguna sonda. Nuevos errores SandboxIndexError e IndexWriteError. @aws-sdk/client-dynamodb es una peerDependency opcional que sólo se carga con loadOptionalPeer al usar el índice. Coste: ~1 WRU por sandbox creado y 0,5 RRU por candidato listado. reincarnate() conserva index: el sucesor escribe su propia fila.

  • Shim de E2B: Sandbox.list({ query: { metadata, state: ["paused"] }, index }) y new E2B({ index }).
  • Secretos sobre AWS Secrets Manager (m13-secrets, opcional y apagado por defecto, ADR-014): new SecretStore({ region, credentials, prefix, kmsKeyId }) con create/update/getInfo/exists/list/ destroy, y new SecretCache({ ttlSeconds: 300 }) (una promesa en vuelo por clave; los aciertos no llaman a AWS; refresh()/invalidate(); toJSON/inspect nunca muestran valores). @aws-sdk/client-secrets-manager es una peerDependency opcional que sólo se carga con loadOptionalPeer al usar la función. Coste: $0,40/secreto-mes hasta destroy + $0,05/10 000 llamadas.
  • secrets / secretCache en Sandbox.create() (también con pool), Sandbox.connect(), sbx.connect(), pool.take() y, por llamada, en commands.run, pty.create, runCode (contextos Python) y createCodeContext: entregan el valor como variable de entorno por los envs que ya viajan a rayd, nunca en el runHookPayload, metadata, logs ni errores. La primera vez emiten un RayitoCompatWarning (process.emitWarning): el valor es visible para el código del sandbox.
  • SecretError/SecretNotFoundError en rayito y en rayito/e2b.
  • Shim de E2B: Secret (estáticos create/update/getInfo/list/ exists/destroy/fill/iamToken), SecretPaginator y el tipo SecretInfo sobre SecretStore, con los nombres de e2b 2.51.0; new E2B({ region }).Secret usa esa región.

Changed

  • El motivo de UnimplementedError de list(query.state=paused, query.metadata) en rayito/e2b nombra ahora la opción index.
  • rayito/e2b Secret y E2B(...).Secret ya no lanzan UnimplementedError.

Fixed

  • SecretStore.destroy() (y Secret.destroy de rayito/e2b) devolvía true para un nombre que nunca existió, porque AWS acepta el DeleteSecret forzado de un nombre inexistente sin ResourceNotFoundException (aceptación de 0.5.0 en AWS real). Ahora hace DescribeSecret antes y devuelve false sin borrar si no existe (o ya estaba programado para borrarse, o lo borró otro a la vez), como documenta y como E2B. Usa secretsmanager:DescribeSecret, ya incluido en RayitoSecretsAdmin.
  • SecretStore.create() sobre un nombre recién borrado agotaba su presupuesto de reintentos de 30 s: AWS liberó el nombre tras 19–28 s y el backoff no llegaba. El presupuesto es ahora de 60 s (CREATE_RETRY_BUDGET_MS en src/secrets/store.ts) con ±25 % de jitter.
  • docs/site/docs/secrets.md avisa de que un logger del SecretsManagerClient del SDK v3 imprime los valores (Rayito nunca lo hace) y de que list() es eventualmente consistente (~3–5 s).

0.4.0 - 2026-09-30

Cambios que rompen

  • UnimplementedError es ahora el único tipo para "algo que este sandbox no puede dar": un Unimplemented de gRPC (un RPC o un kernel de runCode/ createCodeContext ausente en la imagen) ya no es InvalidArgumentError/ SandboxError con grpcCode: Code.Unimplemented, sino UnimplementedError, fuera de esa jerarquía. LifecycleUnsupportedError (un plazo lógico contra un agente anterior a M9) pasa a ser subclase de UnimplementedError, no de InvalidArgumentError ni SandboxError; su feature nombra la llamada que pidió el plazo (connect({ timeoutMs }) también cuando el agente responde Unimplemented al SetTimeout de connect) y su reason usa los mismos textos que el SDK Python. El reason de un kernel ausente es el mensaje de rayd (que ya nombra rayito-base-poly), sin la pista de publicar una imagen actual. getMetricsHistory contra una imagen anterior a M9 ya lanzaba UnimplementedError; sólo cambia su error.cause, de InvalidArgumentError a UnimplementedError. Migración: cambia catch (error) { if (error instanceof InvalidArgumentError && error.grpcCode === Code.Unimplemented) ... } o catch (error) { if (error instanceof SandboxError) ... } alrededor de esos RPCs por error instanceof UnimplementedError (LifecycleUnsupportedError sigue distinguiéndose con su propio instanceof).
  • Sandbox.probedInfo (el static nativo que usaba internamente rayito/e2b) se elimina de rayito: vivía fuera de lugar en el núcleo nativo y su propio docblock ya decía que era interno del shim. Migración: ninguna para quien no lo llamaba directamente (rayito/e2b's Sandbox.getInfo hace exactamente lo mismo); quien lo usara pasa a Sandbox.getInfo(sandboxId) de rayito/e2b.

Changed

  • Una opción de conexión de una llamada de instancia del shim de E2B (sbx.kill({ retries, proxy }), sbx.setTimeout(ms, { headers })...) que esa llamada no aplica —el canal y el plano ya están construidos— avisa ahora con process.emitWarning (RayitoCompatWarning, nombrando sólo la opción, nunca su valor) en vez de perderse en silencio; pásala al crear o conectar el sandbox, o usa la variante estática (Sandbox.<método>(sandboxId, ...)), que sí la aplica. Sin cambio de comportamiento, sólo avisos nuevos.

0.3.3 - 2026-09-29

Security

  • clients/typescript/.npmrc nuevo (ignore-scripts=true): ningún pnpm install local o de CI ejecuta scripts de ciclo de vida de dependencias. .github/workflows/release.yml: el job typescript se parte en typescript-build (sin environment ni id-token, pnpm install --frozen-lockfile --ignore-scripts) y typescript-publish (con el token OIDC de npm, sin checkout ni pnpm, que verifica con sha256sum -c que el .tgz descargado es el que produjo typescript-build antes de npm publish --ignore-scripts) — cierra C-10/H-02 residuo (docs/SECURITY_AUDIT.md). Sin cambio de comportamiento en lo publicado ni en el Trusted Publisher de npm.

Changed

  • sbx.files y sbx.native.files del shim de E2B comparten ahora un único TransferClient (y por tanto una sola caché de soporte de transferencias y un solo cliente S3 por sandbox): el Filesystem nativo acepta un segundo parámetro interno sharedWith que reutiliza el TransferClient del Filesystem nativo del que el shim parte, en vez de construir uno nuevo sobre el mismo SandboxCore. Sin cambio de comportamiento para quien sólo use sbx.files; sbx.native.files deja de duplicar la sonda y los clientes S3 (.d.ts publicado: sólo gana un parámetro de constructor opcional).
  • El puerto ControlPlane declara ahora readonly awsClientSettings?: AwsClientSettings: las credenciales y el proxy que los clientes S3 de las transferencias heredan del plano de control (ADR-010) ya no llegan por un downcast estructural (AwsClientSettingsSource, eliminada), sino por un campo explícito del puerto que cualquier decorador debe reenviar. awsClientSettingsOf sigue exportada con el mismo nombre y la misma firma; sin cambio de comportamiento.

Internal

  • sandbox/core.ts ya no depende de sandbox/commands.ts: la clasificación de fallos de stream (STREAM_PROBE_TIMEOUT_MS, streamFailureError) vive en el nuevo sandbox/stream-errors.ts, sin ciclo con core.ts ni con commands.ts; commands.ts reexporta ambos nombres, así que ningún import existente cambia. Sin cambio de comportamiento.
  • e2b/compat.ts (el mapeo puro que refleja _compat.py) absorbe historyImageError, lifecycleImageError, LIFECYCLE_IMAGE_REASON y la nueva metricsHistoryOrSnapshot, antes en la clase IO e2b/sandbox.ts; Sandbox.getMetrics y Sandbox.metricsFor (estático) llaman a las versiones movidas. Sin cambio de comportamiento.

0.3.2 - 2026-09-29

Changed

  • typescript (dependencia de desarrollo) actualizado de 5.9.3 a 7.0.2 (el compilador nativo, tsgo, vía rolldown-plugin-dts): pnpm lint typecheck build test pack:check verdes y los .d.mts/.d.cts generados byte a byte idénticos a los de 5.9.3. Sin cambios de API pública.

Tests

  • Cobertura explícita de reconexión tras un RST_STREAM(CANCEL) del proxy de AWS (Canceled con el prefijo http/2 stream closed, ya distinguido de un AbortSignal/Canceled propio por isStreamReset/isOwnAbort en transport/errors.ts, sin cambio de comportamiento): un stream de comandos en vivo reconecta con Connect(pid, from_seq), agota el mismo presupuesto de tres reintentos que un Unavailable, y disconnect() sigue ganando la carrera aunque el corte llegue con la forma de un reset reconectable justo después (tests/unit/reconnect.test.ts), en paridad con el arreglo del SDK de Python para el CANCELLED "Stream removed" de grpcio (AWS_API_NOTES.md #33).

0.3.1 - 2026-09-28

Versión de mantenimiento, sin cambios de API.

Documentation

  • Instalación del SDK con pnpm, npm, yarn o bun en el README del paquete (el de npmjs.com) y en el quickstart; probado en un proyecto limpio con ESM, CommonJS, rayito/e2b y tsc --strict.

Changed

  • Dependencias del AWS SDK v3 actualizadas a 3.1140.0 en el lockfile.

0.3.0 - 2026-09-24

Rayito 0.3.0 (M9, paridad con E2B 2.x). Notas completas en docs/RELEASE_NOTES_0.3.0.md; todo lo de M9 exige una imagen publicada con el rayd de M9.

Cambios que rompen

  • Un disco lleno es DiskFullError (antes RateLimitError): un catch que distinguía el disco lleno por RateLimitError deja de verlo. En rayito/e2b se exporta también como NotEnoughSpaceError.

Added

  • Transferencias por URLs prefirmadas de S3 (m9-file-transfer, ADR-010): Sandbox.create/connect({ transfer }) con un S3Staging (bucket DNS sin puntos, prefix, region, maxExpiresIn, thresholdBytes, multipartThresholdBytes) o las variables RAYITO_TRANSFER_BUCKET/_PREFIX/_REGION (null lo desactiva), y el getter sandbox.transfer. files.uploadUrl(path, { user, expiresIn, maxBytes, form }) → UploadTicket (la URL como toString(), method, headers, fields, wait({ timeoutMs }), status(), cancel(); de un solo uso) y files.downloadUrl(path, { user, expiresIn, filename }) → DownloadLink (una foto con size y sha256); sandbox.uploadUrl / sandbox.downloadUrl(path, { user, useSignatureExpiration }) devuelven la URL como string (la forma de E2B). El SDK firma con tus credenciales (SigV4, host virtual regional, requestChecksumCalculation: "WHEN_REQUIRED"); rayd no guarda ninguna. Las URLs nunca se registran, serializan (toJSON lanza) ni imprimen.
  • Con transfer configurado, files.write/writeFiles de lo que mide >= thresholdBytes (y de todo ReadableStream, subido en streaming con @aws-sdk/lib-storage) y files.read de un fichero así van por S3 con el sha256 comprobado; sin transfer el camino gRPC no cambia. Los cuatro paquetes de S3 se cargan con import() sólo al usarlos.
  • files.read(path, { gzip, streamIdleTimeoutMs, format: "blob" }) y files.write/writeFiles(…, { gzip, metadata, useOctetStream }); el gzip de escritura usa un transporte sobre la sesión HTTP/2 de los unarios (nunca una tercera conexión). EntryInfo.metadata (claves en minúsculas). metadata, gzip al escribir y las transferencias exigen un agente M9: en uno anterior lanzan UnimplementedError antes de enviar nada.
  • Errores TransferError (code, reason), FileUploadError y DiskFullError; los códigos de StreamError failed_precondition, resource_exhausted, unavailable y cancelled.
  • Los clientes S3 de las transferencias heredan del plano de control sus credentials y su proxy (LambdaMicrovmsControlPlane.fromRegion(region, { credentials, proxy }), también detrás de un pool), como el SDK Python firma con la sesión del sandbox; antes usaban la cadena por defecto y salían sin pasar por el proxy.

  • Plazo del servidor (m9-server-timeout, ADR-011; exige una imagen M9): Sandbox.create({ maxLifetimeMs, onTimeout: "kill" | "pause" }); con cualquiera de los dos, timeoutMs es un plazo lógico que rayd hace cumplir aunque el cliente muera. sandbox.setTimeout(timeoutMs) y el estático Sandbox.setTimeout(sandboxId, timeoutMs, { accessToken }) (EXACT), sandbox.connect({ timeoutMs }) y Sandbox.connect(id, { timeoutMs }) (AT_LEAST); SandboxLifecycle en getHealth()/getInfo(); LifecycleUnsupportedError contra una imagen anterior (el VM se termina).

  • Historial de métricas y listado (m9-sandbox-observability): getMetricsHistory({ start, end, maxPoints }) (instancia y estático con el access token; UnimplementedError en una imagen anterior, con el mismo motivo que Python y el error gRPC en cause), SandboxMetrics.memCacheBytes, SandboxHealth.cpuCount y memoryTotalBytes, Sandbox.paginate({ limit, nextToken, order, startedAfter }) → SandboxListPaginator y list({ metadata, startedAfter, order }).
  • Política de egress (m9-egress-policy, ADR-012): Sandbox.create({ network, allowInternetAccess }), updateNetwork() (instancia y estático), getNetwork(), ALL_TRAFFIC, EgressEnforcement y SandboxHealth.egressEnforcement; sólo en rayito-base-caps, en otra imagen el SDK termina el VM y lanza UnimplementedError.
  • runCode(code, { language: "typescript" }) (alias ts) y "javascript" (js) con el kernel de Deno de rayito-base-poly (m9-deno-kernels).
  • sandbox.git (Git, GitAuthError, GitUpstreamError), extraHeaders en transport y proxy en el plano de control (m9-e2b-v2-surface).
  • Entrada rayito/e2b (m9-e2b-v2-surface): el shim de la API JS de E2B 2.x (import { Sandbox } from "rayito/e2b", export por defecto Sandbox), con E2B, ConnectionConfig, estáticos kill/getInfo/ getFullInfo/isRunning/connect/pause/setTimeout/getMetrics/ list/updateNetwork, getHost síncrono, trafficAccessToken, NotEnoughSpaceError, ServiceBusyError y UnimplementedError explícito para lo que no tiene primitiva. pack:check exige los cuatro dist/e2b.*. Como en Python, getMetrics (instancia con rango y estático) contra una imagen anterior a M9 y list con metadata y un estado paused lanzan UnimplementedError con el motivo de Python; el getMetrics estático sin token es AuthenticationError (diferencia documentada en docs/site/docs/e2b-compat.md).

Fixed

  • El proceso de Node ya no queda vivo hasta el deadline de un stream: un script que llamaba a runCode tardaba ~315 s en salir tras su última línea (~65 s con commands.run). En @connectrpc/connect 2.x abortar un server-stream sin volver a leerlo no limpia el timer (con ref) de su timeoutMs, y el SDK abortaba así todo stream ya terminado (tras el EndEvent) o abandonado. Ahora, al abortar el controller de cualquier stream (runCode, commands, pty, watchDir, lecturas y vigilancia de transferencias), el SDK pide una lectura más que hace a connect soltar el timer; el deadline sigue imponiéndose igual mientras el stream vive.
  • Auto-resume tras una pausa por el plazo con el cliente vivo: en modo onTimeout: "pause" con idle.autoResume, si este cliente suspendió el sandbox al vencer y la suspensión real duró menos de 2 s (el vigilante de rayd no la reconoce como congelación), el sandbox volvía expired y la siguiente llamada fallaba con sandbox_timeout. Ahora el SDK aplica la regla de E2B (max(timeout, 300 s), acotada al tope menos 5 s) con un SetTimeout en el primer sandbox_timeout tras reanudar, como en Python. Ese SetTimeout va por la unaria que reconecta (un Unavailable transitorio se reintenta), la marca de un solo uso sólo se consume cuando la resumeGeneration ya avanzó y el SetTimeout respondió, y los callers concurrentes comparten una sola promesa de reapertura (un único SetTimeout).
  • sandbox.getInfo() sólo relee Health cuando el sandbox está RUNNING con plazo lógico gestionado (ADR-011): antes sondeaba cualquier sandbox RUNNING, y cada Health es tráfico de entrada que reinicia el contador de idle de la plataforma, así que sondear getInfo() impedía la auto-suspensión. metadata y los hechos del guest salen del último Health sin RPC extra.
  • La readiness de create(), connect() y resume() exige además un Health con sandboxId: el proxy deja pasar Health antes de que rayd reciba /run, y ese Health (kernel del snapshot sin rotar) daba por listo un sandbox cuya rotación de /run ponía kernelReady=false justo después (AWS_API_NOTES.md Q78), como en Python.
  • requestTimeoutMs acota también la pata S3 de una escritura enrutada y de una lectura enrutada bytes/text/blob (aborta la subida o el GetObject, borra el objeto de staging y lanza TimeoutError), como en Python.
  • signal llega a setTimeout, connect, isRunning, getHealth, getMetrics, getMetricsHistory, getNetwork y updateNetwork (de instancia y estáticos) y a los mismos métodos de rayito/e2b.
  • rayito/e2b: Sandbox.getInfo(id) sondea un Health sobre un sandbox RUNNING (endAt es el plazo lógico, con metadata y lifecycle); un httpsPorts no vacío es UnimplementedError (regla QE2, como Python); network/allowInternetAccess siguen D10 (política leída sea cual sea su enforcement, respaldo en INTERNET_EGRESS) y getInfo() sólo absorbe UnimplementedError de GetNetwork. SandboxInfo gana ingress/egress.
  • Un nextToken con startedAtMs negativo es InvalidArgumentError.
  • SandboxInfo gana metadata (los de create({ metadata }) leídos del último Health; undefined si no se leyeron), como en Python: sandbox.getInfo() los trae y, en rayito/e2b, (await Sandbox.connect(id)).getInfo().metadata ya no sale vacío.
  • rayito/e2b: runCode/createCodeContext siguen el contrato de kernels de Python: r, java y el resto son UnimplementedError sin tocar el agente, y el Unimplemented de una imagen sin el kernel también (con el error nativo en cause); python viaja sin language.
  • rayito/e2b: un onEvent de files.watchDir o un onData de pty.create/pty.connect asíncrono que rechaza se registra como aviso en vez de quedar como una promesa rechazada sin manejar (que termina Node).
  • UnimplementedError acepta { cause } como cuarto argumento.

Security

  • El access token ya no aparece al inspeccionar (util.inspect, console.log), serializar ni hacer spread de LaunchPlan, del núcleo del sandbox ni de un SlotRecord del pool, ni el runHookPayload (con los envs) de LaunchRequest: son propiedades no enumerables, como el repr=False de Python.
  • JsonFilePoolBackend escribe por un temporal exclusivo (auditoría interna, fila H-04; mismo defecto que H-03 en el SDK Python): el fichero del pool se escribía por <path>.tmp, un nombre fijo que otro usuario del sistema podía crear o apuntar con un enlace antes que el SDK; ahora se abre con open(temp, "wx", 0o600) sobre un nombre aleatorio, se fija el modo con handle.chmod() antes de escribir y se renombra al destino. La lectura usa O_NOFOLLOW. Sin esto la promesa de 0600 de T14 no era cierta.
  • Los errores de AWS ya no exponen la petición firmada: el cause de los errores que traduce el plano de control (translateAwsError) era el error crudo del SDK v3, cuyo $response arrastra la petición HTTP y los buffers del socket; util.inspect, console.error o el "Serialized Error" de vitest imprimían authorization: AWS4-HMAC-SHA256 Credential=ASIA… y x-amz-security-token. Ahora el cause es un resumen (sanitizeAwsError, src/aws/sanitize.ts) con sólo name, code, el mensaje redactado, $fault y $metadata.{httpStatusCode, requestId, extendedRequestId, attempts}. Los errores de S3 de las transferencias (translateS3Error), que no llevaban cause, ganan el mismo resumen sin mensaje (para conservar el requestId). El mensaje de un InvalidSignatureException/SignatureDoesNotMatch, que AWS devuelve con la cadena canónica (y el token de sesión dentro), pierde esa parte y cualquier cabecera de firma, parámetro X-Amz-* de una URL prefirmada o id de clave de acceso. statusCode, awsCode, retryAfter y quotaCode no cambian.

0.2.0 - 2026-09-17

Primera versión con número de lockstep: el SDK TypeScript salta de 0.0.5 a 0.2.0 para llevar la misma MAJOR.MINOR que rayito 0.2.0 en Python y rayd 0.2.0 (docs/RELEASING.md, plugin linked-versions). Aceptado contra AWS real en M7 (MILESTONES.md).

Added

  • Persistencia del HOME en S3 (m7-s3-persistence, ADR-009): S3Prefix({ bucket, prefix, name, region }), Sandbox.create({ persist, persistTimeoutMs }) (exige executionRoleArn; con name restaura en sandbox.lastRestore), Sandbox.connect(id, { persist }), sandbox.persist, checkpointFiles({ target, exclude, timeoutMs, onProgress }) → CheckpointResult, restoreFiles({ source, timeoutMs, onProgress }) → RestoreResult (NotFoundError sin checkpoint) y reincarnate({ exclude, persistTimeoutMs }); nuevos PersistenceError (code), CheckpointProgress, RestoreProgress, LaunchOptions, DEFAULT_PERSIST_TIMEOUT_MS; create({ pool }) rechaza persist.
  • runCode(code, { language }) y createCodeContext({ language }) aceptan python, bash y javascript (alias js, sin distinguir mayúsculas; m7-poly-kernels): language selecciona el contexto por defecto de ese kernel (default-bash), creado por el agente en la primera celda, y es excluyente con context (InvalidArgumentError). El kernel bash sólo lo trae la variante de imagen rayito-base-poly; javascript es un nombre reservado que hoy ninguna imagen trae (AWS_API_NOTES.md Q57). Un kernel que la imagen no trae es InvalidArgumentError (Unimplemented) con rayito-base-poly en el mensaje; envs por ejecución sólo en contextos Python (docs/site/docs/kernels.md).
  • Pool de sandboxes suspendidos (SandboxPool, PoolConfig, PoolStats, PoolSlotInfo, PoolBackend, InMemoryPoolBackend, JsonFilePoolBackend, PoolClosedError; ADR-008, m7-suspended-pool): N MicroVMs calentados con create(), asentados con una celda trivial, aparcados con pause({ wait: true }) y entregados por take() con resumeMicrovm explícito y el token de la plaza (sin getMicrovm en la toma); fallback a create() sin plaza; relleno con backoff 1 s → 60 s por los token buckets compartidos; reciclado antes del muro de 8 h y reconciliación con listMicrovms; recuperación desde el backend JSON (rayito.pool/1, el mismo fichero que escribe Python); temporizadores sin referencia; Sandbox.create({ pool }) como azúcar que rechaza toda opción de lanzamiento. Documentación en docs/site/docs/pool.md.
  • metadata y cpuTimeLimit en Sandbox.create() (el mismo runHookPayload que Python: metadata y limits.cpu_seconds), y sandbox.launchInfo (la SandboxInfo con la que se abrió el handle).
  • Un endpoint con puerto explícito (host:puerto) manda sobre transport.port (los rayd falsos por plaza de los tests).

Changed

  • Licencia MIT → Apache-2.0 ("license": "Apache-2.0" en package.json, LICENSE con el texto Apache-2.0 y NOTICE incluidos en el tarball; pnpm pack:check los exige).

[0.0.5] - 2026-09-16

Primera versión candidata a publicar (aún no publicada en npm; el primer npm publish es manual, docs/RELEASING.md §3). Es la misma generación de SDK que rayito 0.1.0 en Python: la superficie aceptada contra AWS real en M6 (MILESTONES.md), en camelCase y milisegundos, sólo async.

Added

  • Ciclo de vida: Sandbox.create (run-microvm + JWE del proxy + sondeo de Health hasta agentReady y kernelReady), connect, kill, list, getInfo, isRunning, getHost(port) con las cabeceras del proxy, getHealth, getMetrics; idle (auto-suspensión a los 300 s por defecto); timeoutMs como vida máxima del MicroVM (tope 8 h); await using para terminar al salir del bloque.
  • Comandos: sbx.commands.run en foreground y background, onStdout / onStderr, stdin, list, kill, connect(pid, { fromSeq }), sendStdin, closeStdin; CommandHandle con wait, kill, disconnect, iteración for await y reconexión.
  • Ficheros: sbx.files.read (text / bytes / stream), write, writeFiles en un solo stream, list({ depth }), exists, getInfo, remove, rename, makeDir, watchDir con WatchHandle.
  • Código: sbx.runCode sobre kernels Jupyter con estado, Execution con results (mime bundles y charts de E2B), logs y error como dato; contextos (createCodeContext, listCodeContexts, removeCodeContext, restartCodeContext).
  • PTY y suspend/resume: sbx.pty (create, connect, sendInput, resize, kill; PtyHandle), pause() / resume() con procesos, PTYs, watches y kernels vivos al otro lado, el contrato de reconexión (Connect(fromSeq), Pty.Connect, WatchDir, Reattach) y reconnectTimeoutMs.
  • Red y rol: executionRoleArn (ninguno por defecto), ingress / egress con nombres gestionados (ALL_INGRESS, INTERNET_EGRESS, ...) o ARNs de conectores propios; getHealth() expone agentVersion, resumeGeneration, clockOffsetMs y kernelStateLost.
  • Empaquetado: ESM + CJS con tsdown, tipos .d.mts / .d.cts, exports map, tarball comprobado (scripts/pack-check.mjs), limits.ts generado desde limits.json (scripts/gen_limits.py).

Requisitos de imagen

  • runCode necesita rayito-base ≥ 7.0 (sidecar de kernels); pty y la reconexión tras pause() necesitan ≥ 10.0 (M5). La aceptación de 0.0.5 corrió sobre rayito-base 16.0 (pnpm test:e2e, 2026-09-16).

Limitaciones conocidas

  • Sólo async: no hay árbol síncrono (Node no tiene cliente gRPC bloqueante).
  • Sin metadatos por sandbox (create({ metadata }), list({ metadata })), sin cpuTimeLimit ni campos de endurecimiento en Health: la superficie de M6 del SDK Python llega al SDK TypeScript en una versión posterior.
  • La vida de un sandbox no se puede extender (no existe UpdateMicrovm); tope 8 h running + suspended.
  • Sólo kernels Python; sin URLs firmadas; sin historial de métricas.

[0.0.1] - [0.0.4]

Builds internos de los hitos M1-M5, nunca publicados.

Unreleased

Security

  • ctutils 0.4.4 en Cargo.lock (RUSTSEC-2026-0385: máscaras y comparaciones u128 que no eran de tiempo constante; dependencia transitiva de la criptografía de rayd).
  • tokio 1.53.3 (RUSTSEC-2026-0386: los constructores seguros de AsyncFd aceptaban cualquier AsRawFd sin garantizar la seguridad de E/S). La PTY registra su OwnedFd con AsyncFd::register, que deja esa garantía explícita.

Fixed

  • m5_pty: los tests ya no escriben en la terminal antes de que bash interactivo la tenga (ready_bash teclea un marcador hasta que bash lo ejecuta; en aarch64 una línea tecleada en esa ventana se perdía), y el fin de una shell con un hijo en segundo plano se acota con la gracia de drenaje más 5 s contra un sleep 60, en vez de 1,5 s de reloj. El fin de la terminal (end_of_terminal) se extrae de supervise con tests de tiempo pausado que prueban que la gracia se aplica exactamente.

0.17.0 - 2026-10-23

0.16.0 - 2026-10-22

0.15.0 - 2026-10-21

0.14.0 - 2026-10-17

0.13.0 - 2026-10-16

0.12.0 - 2026-10-15

Security

  • rustls 0.23.45 -> 0.23.46 (cliente HTTPS de las transferencias prefirmadas de S3). Cierra RUSTSEC-2026-0346, RUSTSEC-2026-0347, RUSTSEC-2026-0348 y RUSTSEC-2026-0349, que hacían fallar cargo deny. Sólo cambian Cargo.toml (el pin exacto del workspace) y Cargo.lock.

0.11.0 - 2026-10-13

Sin cambios en el agente: versión en paso con los SDK 0.11.0 (el arranque rápido de AgentTemplate y las claves de artefacto por ejecución son del SDK y de la CLI). El SDK 0.11 se valida con el rayd del tag rayd-v0.11.0.

0.10.0 - 2026-10-08

Fixed

  • El timeout y SendSignal de un proceso con StartRequest.kill_tree alcanzan a todo su árbol. Hasta ahora sólo llegaban al grupo del proceso (killpg), y un descendiente que hiciera setsid o se demonizara sobrevivía: es lo que dejaba vivas las órdenes de la herramienta de shell de un agente tras su timeout. Con el campo (nuevo, false por defecto) el proceso es child subreaper de sus descendientes; el timeout manda SIGTERM a todos antes que al grupo, acaba la gracia en cuanto no queda ninguno y, si queda alguno, lo congela (SIGSTOP) y lo mata (SIGKILL), también a los que ya colgaban de PID 1; el EndEvent llega después. Un SIGKILL congela el árbol en pasadas acotadas antes de matarlo. Cada miembro se identifica por pid y hora de arranque, y nada fuera del árbol recibe señales ni se cosecha fuera del reaper de PID 1.

0.9.1 - 2026-10-07

Sin cambios en el agente: versión en paso con los SDK 0.9.1 (la idle por defecto que se adapta a un timeout corto es sólo del SDK). El SDK 0.9.1 se valida con el rayd del tag rayd-v0.9.1.

0.9.0 - 2026-10-07

Sin cambios en el agente: versión en paso con los SDK 0.9.0 (la recuperación de sbx.gateways en connect() lee el ConfigureStatus que ya existía). El SDK 0.9 se valida con el rayd del tag rayd-v0.9.0.

0.8.0 - 2026-10-07

Sin cambios en el agente: versión en paso con los SDK 0.8.0 (el agente de IA corre sobre los RPC que ya existían). El SDK 0.8 se valida con el rayd del tag rayd-v0.8.0.

0.7.1 - 2026-10-06

Added

  • Avisos de licencia de terceros (third-party-licenses): rayd enlaza estáticamente ~220 crates (MIT, Apache-2.0, BSD, ISC, Unicode-3.0, Zlib y el CC0 de notify) cuyas licencias piden que el aviso viaje con el binario. THIRD_PARTY_LICENSES.md lo genera make licenses con cargo-about 0.9.2 (about.toml, misma lista que deny.toml) desde Cargo.lock cada vez que se empaqueta (make image-licenses, CI y la release; no se versiona, así que un PR de Dependabot no necesita ningún commit más), y falla si sus crates no son exactamente los que cargo tree compila en rayd o si lista uno que el .dep-v0 del binario no registra (scripts/check_third_party_licenses.py). LICENSE, NOTICE y THIRD_PARTY_LICENSES.md viajan en licenses/ de rayito-image.zip, en /usr/share/doc/rayd/ de la imagen y como assets de la release, y SHA256SUMS se firma (SHA256SUMS.sigstore.json), con lo que la firma cubre también los avisos y el SBOM.

Fixed

  • Los tests de adapters::exec_posture que abren descriptores heredables o sellan la tabla de descriptores del propio proceso de test se serializan con un cerrojo: con los tests en hilos paralelos, el close_range de uno marcaba FD_CLOEXEC el descriptor de otro entre su open y su aserción, y a_posture_inheriting_a_slot_seals_only_above_it fallaba de vez en cuando en aarch64. Sólo cambian los tests.

0.7.0 - 2026-10-05

Added

  • Volúmenes EFS (m15-efs-volumes, ADR-018, experimental): rayd_core::volume (VolumeSpec/VolumePlan/MountState/VolumeError, la atribución de efs-proxy y la decisión de /resume, todo puro) y el adaptador real EfsUtilsMounter (mount -t efs -o tls,iam,accesspoint, medido en AWS real, AWS_API_NOTES.md §16 Q128), activo sólo en una imagen con amazon-efs-utils y CAP_SYS_ADMIN (en las demás Health.features.efs_volumes es false y la sección responde UNSUPPORTED). Monta sobre un directorio de root y enlaza el resultado a la ruta pedida sin seguir enlaces simbólicos (recorrido compartido con mounts=, adapters::mountpoint); termina el efs-proxy de cada volumen al desmontar y en /terminate (umount no lo para, Q128); en /resume remonta el volumen cuya pausa cruzó la caducidad de las credenciales del túnel o cuya sonda falla (en segundo plano si no cabe en el presupuesto, con el estado en ConfigureStatus; Q129); en /suspend vacía cada volumen con plazo y lo marca degraded/flush_timeout si no termina (Q130). proto/rayito/v1/efs_volumes.proto documenta las clases nuevas (invalid_path, credentials_expired, flush_timeout, stale, unreachable, gone).

Changed

  • EfsUtilsMounter pasa AWS_REGION (la región del MicroVM) al entorno de mount -t efs: amazon-efs-utils 3.1.3 la lee antes que su efs-utils.conf, así que una imagen sin región horneada monta en cualquier región. Sin AWS_REGION, el helper sigue con sus propios respaldos.
  • image/Dockerfile: capa condicional de amazon-efs-utils-3.1.3-1.amzn2023 (sólo con el marcador efs_variant de --with-efs), que rehace el enlace de /usr/bin/python3 a 3.12; scripts/check_pins.py clava su NEVRA y ve los dnf install dentro de if …; then.

Security

  • Las operaciones de ficheros ya no vuelven a resolver la ruta que la lista de denegación comprobó (sec-rayd-agent-hardening, RAYD-01): cada RPC de FilesystemService (y la exportación e importación por S3) abre el directorio padre componente a componente sin seguir enlaces simbólicos y actúa sobre ese descriptor, así que un componente que el código del sandbox cambie entre la comprobación y el uso se rechaza con la denegación de la política en vez de seguirse; un directorio en proc, sysfs o devpts se rechaza llegue por donde llegue. El borrado recursivo y las escrituras atómicas trabajan también por descriptor.
  • La pasarela de secretos valida la ruta con una lista de permitidos (RAYD-02): en vez de rechazar unas cuantas codificaciones conocidas, rayd sólo deja pasar caracteres de ruta de RFC 3986 salvo ;, y cada segmento decodificado una sola vez debe ser UTF-8 válido, sin /, \, %, ; ni bytes de control y distinto de ./... Cierra el uso de la credencial inyectada fuera de allow a través de un upstream que quite parámetros ; o decodifique dos veces. Una ruta de allow que ninguna petición pueda cumplir se rechaza en Configure (invalid_allow_path).
  • Los lanzadores internos ya no heredan el entorno de rayd (RAYD-04): la sonda de disponibilidad de los montajes S3 (uid 1000) arranca con un entorno con sólo PATH, un binario absoluto, el sellado de descriptores y el restablecimiento de señales que ya tenían los procesos del usuario; mount-s3 gana el mismo sellado por encima de su descriptor FUSE.
  • Un /run desde un proceso del sandbox ya no consume el /run del arranque (RAYD-08): rayd lee el uid dueño del socket del llamante y rechaza (200 sandbox_origin, contado en hook_anomalies) un /run de un uid del sandbox, así que el start_cmd de una plantilla, que corre antes de que llegue el /run de la plataforma, no puede instalar su propio token ni dejar el sandbox sin él. Pendiente de aceptación en AWS real.
  • Documentado el alcance de la reescritura de Host del proxy de egress (RAYD-06): sólo cubre la forma absoluta http://; un túnel CONNECT/SOCKS5 a un nombre permitido en el 80 comparte el riesgo residual de IPs compartidas que ya tenía el 443 (SECURITY.md T17).
  • Los puertos de rayd tienen un tope de conexiones: el gRPC (8080) atiende como mucho 256 conexiones a la vez y los hooks (9000) 32; las demás esperan en el backlog del kernel sin ocupar un descriptor, así que un proceso del sandbox ya no puede agotar los 1024 descriptores de rayd abriendo conexiones ociosas. Los hooks cortan una cabecera que no llega en 10 s y cierran la conexión tras cada respuesta, y un accept que falla por falta de descriptores espera 100 ms en vez de reintentar en bucle (SECURITY.md T7). Pendiente de aceptación en AWS real.
  • rayd comprueba los pids de kernel que le reporta el sidecar antes de señalarlos: un proceso del sandbox puede escribir en la tubería del protocolo del sidecar, así que rayd (root) sólo registra un kernel que /proc confirma como hijo del sidecar en curso, líder de su grupo y del mismo usuario, lo fija por su hora de arranque y lo vuelve a comprobar antes de cada killpg, que nunca va a los grupos 0 y 1 ni al suyo propio. Un pid que no pasa se descarta (kernel_pid_rejected) (SECURITY.md T12).
  • La puerta de identidad tiene techo: un proceso, PTY, operación de ficheros o kernel sólo corre con uid y gid entre 1000 y 65535, el mismo rango que cubren el bloqueo de IMDS y las reglas de egress y DNS; una cuenta por encima de 65535 en una imagen propia se rechaza como cuenta con privilegios (SECURITY.md T1).
  • Los hooks de ciclo de vida comprueban quién abrió la conexión (sec-sandbox-isolation, C-01): rayd busca el extremo cliente de cada conexión al puerto de hooks en las tablas de sockets del kernel. Un /terminate o un /validate que llega desde un proceso del sandbox (uid 1000-65535) responde 200 peer_refused sin hacer nada y cuenta en hook_anomalies; un /suspend o un /resume desde el sandbox se sigue aceptando pero también cuenta. Ningún hook responde nunca un no-2xx por esta comprobación.
  • /validate y /ready ya no actúan tras el /run (C-02, C-03): un /validate posterior al /run responde 200 validate_skipped sin reiniciar el contexto default del kernel ni ejecutar la celda de validación, y tanto él como un /ready tardío quedan en hook_audit como anomalía. Las llamadas del build, antes del /run, no cambian. Además, el camino de ejecución propio del build (el que se salta el stream_gate) se niega por sí solo fuera de la fase de build, aunque se llegue a él sin pasar por el hook.
  • Checkpoint y Restore quedan dentro del ámbito que liga el /run (C-07): si el runHookPayload trae el bloque persist (bucket y base del prefijo, lo manda el SDK desde create(persist=)), rayd responde PERMISSION_DENIED, antes de tocar S3, a cualquier destino de otro bucket o fuera de esa base. Un prefijo por inquilino separa así inquilinos que comparten execution role. Sin el bloque nada cambia.
  • La pasarela de secretos no devuelve la credencial en las cabeceras de la respuesta: se eliminan las cabeceras de la respuesta del upstream que llevan el nombre de una cabecera inyectada o contienen un valor vaultado. El cuerpo sigue llegando sin cambios, así que la documentación (T24 y la página de la pasarela) avisa de no permitir endpoints que reflejen las cabeceras de la petición.
  • Los assets firmados de rayd se construyen sin credenciales y no se pueden reemplazar desde la release. release.yml parte el job rayd en rayd-build (sólo lectura), rayd-sign (token OIDC, sin checkout ni herramientas de build, firma lo que sha256sum -c confirma que salió del build) y rayd-upload (environment release con aprobación del mantenedor, sin --clobber). Ningún job de la release restaura ya una caché de Actions: zig se descarga contra un sha256 fijado y las herramientas de cargo se compilan en una raíz nueva en cada release. Publicar exige que el commit del tag esté en main.
  • La receta de verificación liga la firma a la versión que instalas: cosign verify-blob --certificate-identity "…/release.yml@refs/tags/rayd-v${RAYD_VERSION}" en vez de una regexp que aceptaba la firma de cualquier tag rayd-v* (docs/site/docs/verify.md).
  • Firmar rayd espera la aprobación del mantenedor. rayd-sign corre en el environment release y sólo cuando el run publica: sin aprobación no hay token OIDC ni certificado de Sigstore con la identidad release.yml@refs/tags/rayd-v<versión>, y un ensayo (dry_run) ya no firma. Antes, un ensayo lanzado desde un tag rayd-v* firmaba con la identidad exacta que verifican los usuarios sin pasar por ningún revisor. verify.md dice ahora qué prueba la firma y qué no.
  • La receta recomendada de rayito-image.zip verifica antes de publicar: descarga SHA256SUMS, comprueba el bundle con cosign verify-blob y la suma, y sólo después llama a rayito image publish (docs/site/docs/primeros-pasos/configurar-aws.md). Ya no es un paso opcional: un asset de una release se podría reemplazar con un token del repositorio.
  • CI sin cachés donde hay credenciales y con herramientas fijadas: los jobs del sitio de documentación y del e2e ya no restauran cachés de Actions; uv se instala fijado por versión y sha256 en todos los workflows y nunca re-bloquea un uv.lock desfasado; pip-audit y cfn-lint salen de requisitos con --hash en vez de uvx.

0.6.1 - 2026-10-04

Fixed

  • rayd (PID 1) recoge los zombis huérfanos (rayd-orphan-reaper, Q80): un demonio con doble fork (o mount-s3 en modo daemon) ya no deja procesos <defunct> colgados de rayd durante toda la vida del sandbox. Todo hijo de rayd (procesos, PTYs, el sidecar de kernels, mount-s3, sondas internas) se lanza por un único ChildRegistry por proceso que lo registra con su pid y su hora de arranque antes de que ninguna pasada de recogida pueda verlo; el reaper, en cada SIGCHLD y cada 5 s, sólo hace waitpid sobre los zombis reasignados a rayd que no son suyos, así que los códigos de salida de comandos, PTYs, kernels y mount-s3 nunca se pierden. Sólo se activa como PID 1 (o child subreaper). clippy.toml prohíbe lanzar procesos fuera de ChildRegistry::spawn.

0.6.0 - 2026-10-03

Added

  • ConfigureService y los seis slots de función 0.6 (v06-foundations, M15 foundations, ADR-015): el servicio gRPC Configure/ConfigureStatus, el orden de aplicación fijo (eventos → telemetría → pasarela → montajes S3 → volúmenes EFS), Health.features (AgentFeatures, campo 16, configure=true y el resto false hasta que su propia función construye un adaptador real) y LifecycleParticipant para que una función futura participe en /suspend//ready sin cambiar su contrato. El reaper de zombies huérfanos de PID 1 (rayd_core::orphans, ChildRegistry, OrphanReaper, Q80) queda implementado y probado, sin activarse todavía en main.rs (ver MILESTONES.md M15). El registro de .proto pasa a descubrirse por glob (crates/rayito-proto/build.rs). Sin ninguna sección de ConfigureSandbox, el comportamiento es idéntico al de 0.5.x.
  • Un único FeatureSet por proceso y participantes acotados (v06-foundations §15, ADR-015): main comparte un Arc<FeatureSet> entre ConfigureService, Health.features (derivado de supported() de cada slot) y los hooks (grpc::router_with_features). /suspend, /resume y /terminate llaman a on_suspend/on_resume/ on_terminate de cada participante sólo ante una transición aceptada, cada uno con su propio tope (PARTICIPANT_RESUME_TIMEOUT, PARTICIPANT_TERMINATE_TIMEOUT). Sin participantes, idéntico a 0.5.x.
  • s3_mounts feature slot (m15-s3-mounts, ADR-017): rayd_core::s3_mount (S3Mount, MountErrorClass incl. InvalidPath, validate_mounts/parse_allowed_buckets, ports FuseDevice/FuseDaemon) and rayd_core::mount_path (the absolute/canonical/allowed-roots/ no-overlap/max-count check, shared with a future efs_volumes, re-run here before anything else so a non-SDK or buggy client can never steer mount(2) outside /mnt///home/user/). The Linux adapters (adapters::fuse_device: the mountpoint is walked from / one component at a time with O_PATH|O_DIRECTORY|O_NOFOLLOW (missing ones created with mkdirat), any symlink is invalid_path, and mount(2)/ umount2(MNT_DETACH|UMOUNT_NOFOLLOW) only ever see /proc/self/fd/<n>, so uid 1000 can never redirect a root mount onto a system directory by swapping its own folder for a symlink; mount(2) with allow_other plus a real probe_ready (a bounded, killable stat subprocess as the guest uid); adapters::mount_s3: mount-s3 --allow-other --uid 1000 --gid 1000 (without --allow-other Mountpoint answers only its own uid: every guest access was EACCES, AWS_API_NOTES.md Q101) run as the dedicated rayito-mount user, uid 990, with a from-scratch environment — no credential ever in argv or env, SEC-3 — its pid registered in the shared ChildRegistry, its exit classified from its status and a stderr ring buffer drained for the daemon's whole life, so a chatty daemon never blocks on a full pipe, and classified from its last bytes, never logged) and a real features::s3_mounts::S3MountsFeature: apply() reports SECTION_CODE_PENDING immediately and settles each mount to Mounted/Failed in the background; a background watcher relaunches a dead daemon with a fresh FUSE attach and backoff, each relaunch as its own task and never on top of an attempt still Pending; /resume's probe forces the same relaunch for a daemon that survived the snapshot but whose FUSE connection did not. supported()/Health.features.s3_mounts require CAP_SYS_ADMIN in rayd's effective set (only rayito-base-caps has it; the binary, /dev/fuse and the rayito-mount user alone ship in every variant) instead of a literal true. Health.features.root_egress is now derived generically from FeatureSet too (a new ConfigurableFeature::root_egress_class(), reported only for a supported() slot), so later features add nothing to grpc/health.rs. s3_mounts.proto now carries real fields (S3Mount, S3MountState, S3MountPhase). With no S3MountsConfig section sent, behaviour is unchanged from 0.5.x: no /dev/fuse open, no mount-s3 spawn.
  • Eventos de ciclo de vida firmados (m15-events-webhooks, ADR-020): rayd emite created/paused/resumed/killed{reason: request} como una línea rayito.event.v1 <b64url(json)> <b64url(hmac-sha256)> en su propio stdout, sólo cuando ConfigureSandbox trae una LifecycleEventsConfig con clave (la deriva y empuja el SDK; rayd nunca ve el secreto del stack). Cola acotada y no bloqueante; paused y killed esperan a que la línea salga a stdout (barrera flush en la misma cola) dentro de la cuota de /suspend y del tope de /terminate. Si la fuente aleatoria falla, el evento se descarta y se cuenta (random_unavailable). El estado vive en el FeatureSet único del proceso. Sin events=, cero líneas y cero coste (el participante de esta función comprueba su propio estado antes de hacer nada, y no espera ningún flush si no encoló nada).
  • Exportador OTLP/HTTP a CloudWatch (m15-rayd-otlp, ADR-021): rayd exporta 7 gauges de CPU, memoria y disco cada interval_s (15-300 s) sobre el telemetry_export de ConfigureSandbox, firmado con SigV4 sobre el execution role o con una API key de CloudWatch Metrics empujada por el SDK; cola acotada con backoff y jitter por sandbox (rayd_core::telemetry::Batcher), participante de /suspend con un vaciado de hasta 2 s que nunca pierde puntos aunque el hook lo corte, y /resume (su on_resume, bajo el tope por participante de los hooks) rehace el pool de conexiones sin esperar envíos previos. Firma SigV4 con aws-sigv4 (ya en Cargo.lock), corrigiendo el reloj del guest con la cabecera Date de AWS; persistencia y funciones 0.6 comparten un único proveedor de credenciales IMDS. Lee traceparent entrante y lo registra como trace_id/span_id. Un subconjunto vendido y mínimo de los tipos de OpenTelemetry (Apache-2.0, crates/rayito-proto/vendor/opentelemetry/). Health.features.telemetry_export (y root_egress) sólo son true con AWS_REGION conocido. Sin telemetry=, rayd no abre ninguna conexión nueva: comportamiento idéntico a 0.5.x.
  • Templates declarativos (m15-templates, ADR-022): rayd_core::template (StartSpec, ReadyPoll, ready_decision, rayito.template/1) y el slot template_start: al arrancar, rayd lee /etc/rayito/template.json (adapters/fs_template_spec.rs; ausente o inválido = arranque de 0.5.x) y, si existe, antes del /ready del build lanza start_cmd como proceso gestionado (visible en commands.list) y sondea ready_cmd con /bin/sh -c (adapters/shell_ready_probe.rs); /ready responde 503 hasta que ready_cmd sale con 0 y falla al agotarse su plazo. Health.features.template_start pasa a true.
  • Pasarela de secretos en loopback (m15-secrets-gateway, M15, ADR-023): el slot secret_gateway deja de ser Unsupported. Un listener axum por ruta de SecretGatewayConfig (rayd::secret_gateway), con allowlist de método/ruta y límite de peticiones por minuto (cubo de tokens entero y determinista, rayd_core::secret_gateway) antes de reenviar al upstream fijo de la ruta por un cliente HTTPS compartido (GatewayUpstream: raíz de confianza del SO, FilteringResolver para que un upstream nunca resuelva a loopback/link-local/IMDS, cuerpos en flujo sin bufferizar). Las cabeceras del guest con el mismo nombre que una vaultada se eliminan antes de inyectar el valor real (T24): el código del sandbox nunca puede leer ni suplantar su propio secreto. Una ruta de petición con segmentos ./.. (también codificados), un / o \ codificado, una barra invertida o un segmento vacío se rechaza (403) antes de la allowlist. Los nombres de cabecera se validan (token RFC 9110, únicos sin distinguir mayúsculas, nunca host/content-length/ hop-by-hop: invalid_header_name/duplicate_header_name), igual que el nombre de ruta (invalid_route_name) y el upstream (invalid_upstream_host). Un Configure que repite el nombre de una ruta conserva su listener y su puerto y sólo cambia su estado (la siguiente petición, también por una conexión keep-alive abierta, ya lo ve); una ruta retirada cierra sus conexiones keep-alive. Sólo un CONNECT_TIMEOUT (10 s) o RESPONSE_HEAD_TIMEOUT (600 s) es upstream_timeout; cualquier otro fallo tras conectar es upstream_error. Health.features.secret_gateway sale del FeatureSet construido: si el slot degrada a Unsupported (sin raíz de confianza), no se anuncia. Sin ningún Configure con secret_gateway, rayd no abre ningún socket de loopback para esta función.

0.5.1 - 2026-10-01

Sin cambios en el agente: versión en paso con los SDK 0.5.1. Una imagen con rayd 0.5.0 sigue siendo compatible.

0.5.0 - 2026-10-01

Fixed

  • /suspend ya no puede colgarse en sync(2): el paso 4 del checklist llamaba a sync(2) sin plazo, y un montaje de red o FUSE colgado (o un disco muy lento) lo dejaba en estado D; un /suspend que no responde a tiempo hace que AWS termine el MicroVM. Ahora se hace un syncfs(2) por sistema de ficheros (sacados de /proc/self/mountinfo: escribibles, uno por dispositivo, sin pseudo-sistemas), cada uno en un hilo desechable, y el hook espera como mucho el plazo de SuspendBudget (5 s por defecto, acotado a que gracia + quiesce + sync quepan en la mitad del presupuesto del hook). Lo que no termine sigue fuera del camino del 200; su sistema de ficheros se salta en los siguientes /suspend mientras siga colgado, y el plazo vencido se registra sólo con contadores (nunca rutas). La regla vive en rayd_core::suspend_sync; las syscalls, en adapters::bounded_sync.

0.4.0 - 2026-09-30

Cambios que rompen

  • LifecycleService.SetTimeout con timeout_ms=0: la regla del timeout es sólo del dominio, que juzga primero la fase. Un sandbox sin plazo responde FAILED_PRECONDITION lifecycle_unmanaged (antes INVALID_ARGUMENT "timeout_ms must be positive") y uno terminando FAILED_PRECONDITION sandbox_timeout; con plazo activo, 0 ms da el mismo INVALID_ARGUMENT que 1-999 ms. Migración: ninguna para los SDK (validan >= 1 s antes del RPC); un cliente gRPC propio debe tratar lifecycle_unmanaged como antes trataba el INVALID_ARGUMENT.
  • Los mensajes de error del agente (status gRPC, StreamError.message y motivo del /run rechazado) pasan al español; timeout below 1 s es ahora el timeout debe ser de al menos 1 s. Los tokens que parsean los SDK (suspending, terminating, sandbox_timeout, lifecycle_unmanaged, timeout beyond cap; cap_unix_ms=, kernel not ready, disk_reserve, disk_full, metadata_unsupported, metadata_too_large, los <reason>: de las transferencias, egress_update_failed, egress_verify_failed) no cambian y viven en rayd_core::wire_tokens. Migración: quien compare mensajes por texto debe usar el código gRPC o esos tokens.

Changed

  • RegistryError::Full/Unknown se convierten en TransferError (From) y toman su texto de él en lugar de repetirlo; el cierre por suspensión de un stream de proceso o PTY es un solo constructor de dominio (ProcessEnd::suspending).

0.3.3 - 2026-09-29

Changed

  • Plazo del sandbox (refactor interno, sin cambio de comportamiento): el veredicto de congelación de ADR-011 (is_thaw/was_frozen) pasa a rayd-core (sandbox_timeout::freeze) como funciones puras con tests de tabla, y el hilo del plazo sólo guarda los instantes; una sola conversión de SystemTime a milisegundos Unix (clock::unix_millis, reexportada desde metrics) sustituye la copia privada del hilo del plazo.
  • Egress (refactor interno, sin cambio de comportamiento): los casos de uso de egress de /run y /resume pasan del adaptador de hooks a NetworkManager (on_run/on_resume); el estado instalado (política, plan, slot, guardia DNS) vive en rayd-core (Installation) y el veredicto de verificación se decide con comprobaciones puras (VerifyFailure); una sola clasificación de direcciones especiales (SpecialAddress) sirve a la guardia SSRF de transferencias, a las dos guardias del proxy y a la sonda; el mapeo de NetworkError a código gRPC es exhaustivo (status_class); la tabla y prioridad de IMDS son constantes tipadas con su invariante de orden; ConnectFailure y sus respuestas HTTP/SOCKS pasan a rayd-core.
  • Transferencias (refactor interno, sin cambio de comportamiento): toda mutación del registro pasa por un único punto que refresca el contador de la barrera de lectura tras subida; importación y exportación comparten el cierre de la tarea (TaskEnding); el presupuesto de reintentos de una petición a S3 vive en rayd-core (RequestRetries); el plan de una exportación lleva la URL de cada parte y un UploadPart correcto lleva siempre su ETag (PutOutcome::PartStored).

Security

  • Imagen: las dos capas pip install -r de image/Dockerfile (el stack científico principal y la variante poly) llevan ahora --require-hashes --no-deps --only-binary=:all:. kernel-sidecar/requirements.txt y requirements-poly.txt se regeneraron con uv pip compile --generate-hashes sin cambiar ninguna versión (diff de nombre==versión vacío); un fichero añadido a una release ya publicada, o un sdist que compilase en la VM de build, se rechazan en vez de instalarse en silencio. Cierra C-12 (docs/SECURITY_AUDIT.md); gate nuevo en scripts/check_pins.py (puerta 5). Sin cambio de comportamiento en tiempo de ejecución.

Fixed

  • CI: el job x86_64 (check) vuelve a correr la suite completa del workspace, incluidas las suites de integración de rayd. El runner no se congelaba por rayd: a_stalled_second_subscriber_is_truncated_alone (tests/m5_pty.rs) buscaba el final de 3 MB de salida reescaneando todo el buffer en cada trozo (cuadrático, ~30 s en debug en x86 frente a un plazo de 30 s) y, al vencer, imprimía el buffer entero en el panic: una línea de ~2.9 MB en el log que dejaba mudo al runner (ni timeout ni la cancelación respondían). La búsqueda ahora es lineal (1-2 s) y los panics muestran sólo la longitud y los últimos 512 bytes. Investigación en docs/research/2026-10-ci-x86-freeze.md.

0.3.2 - 2026-09-29

Fixed

  • Imagen: e2b/data convierte los valores ausentes (None, NaN, NaT, pd.NA) en null. Un DataFrame con un NaT junto a otras columnas hacía fallar la celda entera (NaTType does not support strftime), y con pandas 3 un texto ausente salía como 'nan'.

  • Con la política de egress en deny-all (imagen rayito-base-caps), el DNS (puerto 53, UDP y TCP) queda bloqueado para uid >= 1000 con reglas ip rule instaladas de forma atómica y con rollback (egress, opción A de ADR-012): antes los nombres se resolvían aunque toda conexión fallara.

  • /run marca la rotación del kernel de forma síncrona, así que una sonda de readiness durante la restauración ya no ve el contexto anterior como listo.
  • Los hijos de rayd (procesos, shell PTY, sidecar) restauran todas las señales a SIG_DFL y vacían la máscara antes de exec: un SIGHUP ignorado por el padre ya no impide que el job en primer plano muera con la shell.
  • ci.yml (job build) y release.yml (job rayd) compilan rayd con el mismo --remap-path-prefix que make build (REMAP_CONFIG del Makefile): el binario publicado ya no incrusta las rutas del runner (/home/runner/...) de CARGO_HOME ni del directorio de compilación. Un paso nuevo en ambos jobs falla el build si strings encuentra /home/runner en el binario.

0.3.1 - 2026-09-28

Versión de mantenimiento, sin cambios en el contrato gRPC.

Changed

  • Dependencias de Cargo actualizadas (versiones menores y de parche).
  • Los tests generan sus certificados TLS con rcgen: el repositorio ya no contiene claves privadas de prueba.
  • Tests de descriptores de la PTY sin la carrera de bash ($(...)) que los hacía intermitentes.

0.3.0 - 2026-09-24

rayd 0.3.0 (M9). Todos los cambios del contrato (proto/rayito/v1/) son aditivos y compatibles con buf breaking (FILE): un SDK 0.2 sigue hablando con este agente. Los SDK 0.3 necesitan este agente para las features de M9.

Added

  • FilesystemService: transferencias por URLs prefirmadas (m9-file-transfer, ADR-010): StartImport, StartExport, GetTransfer, WatchTransfer y CancelTransfer; rayd mueve bytes con un cliente HTTPS sin credenciales (HyperSignedHttp) sólo hacia URLs cuyo host y ruta son los del objeto nombrado (transfer::url_policy), con un resolvedor que descarta loopback, link-local e IMDS; barrera de lectura tras subida; Write/Read con gzip opcional (rayito-compress: gzip) y metadatos por fichero como xattrs user.rayito.*.
  • LifecycleService.SetTimeout y HealthResponse.lifecycle (campo 12) (m9-server-timeout, ADR-011): el plazo lógico del bloque lifecycle del runHookPayload, vigilado por el hilo rayd-timeout; al vencer en modo kill, streams cerrados con sandbox_timeout, SIGTERM/SIGKILL a todos los grupos y salida con código 124; la capa timeout_gate responde FAILED_PRECONDITION sandbox_timeout a todo RPC salvo Health y SetTimeout.
  • HealthService.MetricsHistory, MetricsResponse.mem_cache_bytes y HealthResponse.cpu_count/memory_total_bytes (campos 14 y 15) (m9-sandbox-observability): muestreo procfs cada 5 s sólo en running/resumed, anillo de 5 760 muestras.
  • NetworkService (UpdateNetwork, GetNetwork) y HealthResponse.egress_enforcement (campo 13) (m9-egress-policy, ADR-012): política de egress en el guest con CAP_NET_ADMIN (tablas de rutas por uidrange 1000-65535, cambio atómico, verificación), proxy local HTTP CONNECT/SOCKS5 en 127.0.0.1 con guardia contra IMDS y las direcciones propias del guest, cadena al SOCKS5 del operador, deny-all en /run con network.enforce y re-verificación en /resume.
  • language = "typescript" en CodeService (m9-deno-kernels): el catálogo del agente conoce los cuatro nombres; en una imagen sin el kernel, UNIMPLEMENTED nombrando rayito-base-poly.

Fixed

  • Un Write en un directorio observado llega como WRITE (m9-e2b-v2-surface): el rename del temporal .rayito-tmp-* sobre el destino se emparejaba como un RENAME sin entry, y el código portado de E2B que espera WRITE tras files.write (el ejemplo de docs.e2b.dev) no veía nada. WatchTranslator empareja las dos mitades del rename por su cookie de inotify (RawWatchEvent.cookie) y emite un único WRITE del destino, con entry si se pidió include_entry. Los mv/files.rename corrientes siguen siendo RENAME.
  • El plazo en milisegundos ya no oscila: deadline_unix_ms y cap_unix_ms (en Health.lifecycle y en el detalle de SetTimeout más allá del tope) se calculaban redondeando por separado el reloj de pared y el monotónico, y dos lecturas seguidas podían diferir en 1 ms. Ahora la suma se hace en nanosegundos y se redondea una sola vez hacia abajo (también antes de la época).
  • Health no dice agent_ready mientras se instala el deny-all de /run (egress_settling): la plataforma deja pasar Health antes del 200 de /run, y con network.enforce el SDK podía dar por listo un sandbox cuya política aún no estaba aplicada (el primer ip de un guest recién restaurado tarda segundos). agent_ready espera a que la política quede publicada, verificada o no; una imagen sin CAP_NET_ADMIN queda lista tras /run como siempre.
  • La salida por el plazo no cuenta como reinicio del sidecar: el sidecar y los kernels parados para la salida con código 124 (o cualquier apagado) no suben sidecar_restarts ni se registran como caída.
  • Egress en un guest recién creado: ip route flush|show table <T> responde "FIB table does not exist" (salida 2) si la tabla de la política nunca se creó; ahora se trata como una tabla vacía en vez de como un fallo que hacía fracasar la instalación de la primera política. ip nunca se registra: su stderr sólo se lee para reconocer esa frase.
  • Una sola gracia de reanudación por plazo, nunca más allá del tope (m9-server-timeout, revisión independiente): el hilo vigilante toma cualquier hueco ≥ 2 s entre dos ticks por una congelación, y dejarlo sin CPU desde dentro del sandbox lo imita; seguido de un /suspend + /resume forjado, la gracia de 30 s podía reabrirse una y otra vez y, en modo kill, pasar del tope. Ahora cada gracia acaba como mucho en el tope (ninguna se abre en él o después) y un plazo abre una sola; sólo un plazo que se mueve (SetTimeout, que exige el token, o la regla de auto-resume de 5 min, que sigue igual) vuelve a tenerla.
  • El proxy de egress reescribe Host en la forma absoluta http:// (m9-egress-policy): conservaba el Host del cliente, así que un nombre permitido podía servir de fachada para otro host virtual de la misma IP. Ahora el Host es la autoridad comprobada (RFC 9112 §3.2.2) y las líneas plegadas (obs-fold) se rechazan con 400. CONNECT y SOCKS5 no inspeccionan TLS: el SNI sigue siendo del cliente (residual en T17).
  • Las direcciones IPv4-compatibles (::a.b.c.d) se juzgan como IPv4 en la guardia y la política del proxy (canonical_ip, con Ipv6Addr::to_ipv4 como la política de URLs de transferencia): antes sólo ::ffff:a.b.c.d, y ::169.254.169.254 o ::127.0.0.1 pasaban la guardia. :: y ::1 siguen siendo IPv6.
  • Health ya no puede quedarse sin agent_ready para siempre: si la tarea del deny-all de /run entraba en pánico, egress_settling no se limpiaba nunca; ahora lo limpia un guardián al terminar la tarea de cualquier modo. Además cada invocación de ip tiene un tope de 5 s (IP_COMMAND_TIMEOUT, por encima de los 2,76 s medidos para el primer ip tras un restore): pasado, el hijo se mata y se recoge y la llamada falla como timed out, en vez de retener el lock del gestor de egress.

Dependencies

  • zlib-rs aparece en Cargo.lock y en el SBOM, pero no se enlaza (m9-file-transfer, ADR-010): la feature gzip de tonic 0.14.6 pide flate2 con sus features por defecto, y la feature débil runtime_detection (zlib-rs?/std) basta para que Cargo anote la dependencia opcional zlib-rs en el lockfile aunque nadie la active. El workspace ya fija flate2 con default-features = false y rust_backend, pero las features son aditivas y no pueden quitar el default que pide tonic, así que el lock no cambia. El binario enlaza sólo miniz_oxide (cargo tree -i zlib-rs no encuentra la crate en el grafo resuelto).

Security

  • user= sólo acepta cuentas sin privilegio (auditoría interna, fila C-05): UserPolicy::authorize_identity era una lista negra de uid 0, así que un alias de uid 0, una cuenta de sistema con uid < 1000 —fuera del uidrange 1000-65535 con el que M6 agujerea IMDS— o cualquier miembro del grupo root pasaban la puerta. Ahora la comprobación es positiva (uid >= 1000 && gid >= 1000 y sin el grupo 0) y devuelve PrivilegedAccount. La puerta duplicada de persistence desaparece: ese camino llama a la misma política con el opt-in de root retirado (UserPolicy::without_root).
  • Ningún proceso de usuario hereda descriptores más allá de 0/1/2: openpty devuelve master y slave heredables y rayd sólo los marcaba close-on-exec unas instrucciones después; un spawn concurrente (otro PTY, un proceso o el sidecar) que hiciera fork en esa ventana entregaba el master o el slave de otra terminal a la shell o al proceso del usuario (visto en la CI aarch64 de GitHub: la shell listaba 0 1 142 145 2 255 3). Ahora el PreExecPlan común a procesos, shells PTY y sidecar marca close-on-exec todo descriptor >= 3 en el hijo justo antes de exec (close_range(3, ~0U, CLOSE_RANGE_CLOEXEC), con un bucle acotado de fcntl(F_SETFD) hasta el NOFILE blando, máximo 4096, si el kernel es anterior a 5.11), después de que std haya colocado stdio en 0/1/2. Cubre también los descriptores que rayd hereda de su padre. El harness de tests deja de sellarlos por su cuenta.

0.2.0 - 2026-09-17

Agente de M7, aceptado contra AWS real (MILESTONES.md).

Added

  • FilesystemService.Checkpoint y Restore (m7-s3-persistence, ADR-009): tar.gz del HOME del usuario (lista de exclusión fija, exclude ≤ 64, lectura bajo FsIdentityGuard) subido a s3://<bucket>/<key_prefix>/ (home.tar.gz en partes de 8 MiB + manifest.json v1) y restaurado sin salir del HOME (sólo regular/dir/symlink, modo 0o777, sha256 verificado), con las credenciales IMDSv2 del execution role como root (--persistence-credentials imds|default, imds por defecto), una operación por sandbox (FAILED_PRECONDITION), progress ≤ 1/s, KeepAlive cada 30 s y cierre suspending en /suspend con abort del multipart. Nuevo módulo rayd-core::persistence, adaptadores S3ObjectStore (aws-sdk-s3 1.148.0 + aws-config 1.12.0, rustls + aws-lc-rs compilados con zig cc) y TarHomeArchiver (tar 0.4.46 + flate2 1.1.10 rust_backend). Binario ARM64 musl auditable: 4 700 984 → 12 524 384 B; build limpio 106 → 222 s.
  • ExecuteRequest.language (optional string, campo 5; m7-poly-kernels): python, bash o javascript seleccionan el contexto por defecto de ese kernel (default, default-<language>), que rayd crea perezosamente en la primera celda bajo un lock por lenguaje (nunca antes de /run; cuenta para el tope de 8; DestroyContext permitido y recreación en la siguiente celda). CreateContextRequest.language acepta los tres nombres. Hoy sólo rayito-base-poly trae el kernel bash; javascript es un nombre reservado que ninguna imagen trae (AWS_API_NOTES.md Q57). Nuevos estados: INVALID_ARGUMENT para language junto a context_id y para envs por ejecución en un contexto no Python; UNIMPLEMENTED (mensaje con rayito-base-poly) para un lenguaje conocido que la imagen no trae, según ready.languages del sidecar.
  • Protocolo del sidecar (v1, aditivo): language en create_context, languages en ready (ausente = sólo Python) y skipped en la respuesta de reseed (contextos no Python, logueado como cuarto contador). ListContexts informa el lenguaje real de cada contexto.

Changed

  • Licencia MIT → Apache-2.0 (license = "Apache-2.0" en [workspace.package], heredada por los tres crates; LICENSE junto al crate).

Compatibilidad

  • Health.agent_version es 0.2.0. Los SDKs 0.2.0 (Python y TypeScript) exigen agent_version ≥ 0.2.0 (Checkpoint/Restore y ExecuteRequest.language): rayito doctor lo evalúa con la tabla de docs/site/docs/limits.md; sobre un rayd 0.1.0 persist= responde UNIMPLEMENTED y language= se ignora. El número de versión de imagen sigue siendo un contador de builds por cuenta (la aceptación de M7 corrió sobre las versiones anotadas en MILESTONES.md).

0.1.0 - 2026-09-16

Primer agente completo, aceptado contra AWS real en M1-M6 (MILESTONES.md). Un proceso por MicroVM, como root, estático musl, con gRPC h2c (tonic) en :8080 y los hooks de Lambda (axum, HTTP/1.1) en :9000, nunca en allowedPorts.

Added

  • M1 — arranque y readiness: HealthService.Health (el único RPC sin x-access-token: agent_ready, kernel_ready, agent_version, uptime, sandbox_id) y Metrics; hooks /ready, /validate, /run, /terminate bajo /aws/lambda-microvms/runtime/v1/; el secreto del sandbox llega como hash en runHookPayload y se compara en tiempo constante (zeroize).
  • M2 — procesos: ProcessService (Start, Connect(from_seq), SendInput, CloseStdin, SendSignal, List) con usuario por defecto uid 1000, entorno construido desde cero, setrlimit, grupos de procesos
  • killpg, canales de salida acotados con output_truncated, máximo 256 procesos/PTYs; HealthService.Metrics desde procfs.
  • M3 — ficheros: FilesystemService (Read en stream, Write en stream, Stat, ListDir, MakeDir, Move, Remove, WatchDir) con lista de denegación sobre la ruta canónica, .. rechazado, setfsuid/setfsgid por operación, O_NOFOLLOW, escrituras a temporal con fchown.
  • M4 — código: CodeService (CreateContext, Execute, Reattach, ListContexts, DestroyContext, RestartContext) sobre el kernel sidecar Python (hijo de rayd, JSON lines por stdio; ADR-002): reinicio del sidecar con backoff, /run reinicia el kernel por defecto (clave HMAC y semillas nuevas por sandbox), máximo 8 kernels, result > 12 MiB recortado.
  • M5 — PTY y suspend/resume: PtyService (Create, Connect, SendInput, Resize, Kill) con openpty + setsid + TIOCSCTTY como uid 1000 (ADR-005); hooks /suspend (cierra streams con suspending, quiesce + sync, no destruye nada) y /resume (resume_generation, sonda de kernels, reseed de random/numpy.random, kernel_state_lost); deadlines que excluyen el tiempo suspendido (clock_offset_ms).
  • M6 — endurecimiento: bloqueo de IMDS para uid 1000-65535 en la variante rayito-base-caps (Health.imds_blocked), auditoría de hooks y watchdog de /suspend estancado (Health.hook_anomalies), RLIMIT_CPU opcional por proceso (limits.cpu_seconds del payload), presupuesto de salida de 128 MiB por sandbox, reserva de disco de 256 MiB (disk_full), metadata del payload devuelto en Health.

Compatibilidad

  • Health.agent_version es 0.1.0 (el CARGO_PKG_VERSION del crate). Los SDKs 0.1.0 (Python) y 0.0.5 (TypeScript) exigen una imagen rayito-base ≥ 10.0 (la primera con este rayd y el sidecar de M5); la superficie de M6 (imds_blocked, hook_anomalies, metadata, cpu_seconds, disk_full) requiere la imagen construida de este tag (la aceptación corrió sobre rayito-base 16.0 y rayito-base-caps 6.0).
  • Sin cambio de imagen agent_version no cambia: las versiones de imagen son números de build opacos de AWS, anotados en MILESTONES.md. Esos números (10.0, 16.0, 6.0) son los de la cuenta del mantenedor: el contador es por imagen y por cuenta, y el criterio de compatibilidad que aplica rayito doctor es sólo agent_version.

[0.0.1] - [0.0.5]

Builds internos de los hitos M1-M5, publicados sólo como versiones de imagen de la cuenta de desarrollo.