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 setuppone al día la pila de IAM de una versión anterior. Antes reutilizabarayito-m0-iamsi suArtifactBucketcoincidía, aunque se hubiera desplegado con uninfra/iam.yamlmá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ámetroTemplateFingerprint(el sha256 de la plantilla empaquetada, el mismo en los dos SDK) y su salida;setuplo manda al crear o actualizar y planificaupdatesi la pila no lo tiene o lleva otro, con el texto «actualizar a la plantilla infra/iam.yaml de este SDK» ypreviousBucket: nullen--jsoncuando 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 etiquetarayito:sdk-version) nunca vuelve a la plantilla de este SDK: con el mismo bucket se reutiliza («… más nuevo que este SDK: se conserva»,stackSdkVersionen--json) y, si el plan le cambia el bucket, falla con el error nuevostack_newer(salida 2). Al actualizar la pila,setupvuelve a mandar las etiquetas que ya tenía, con las de--tagpor encima, porqueUpdateStacksustituye la lista entera.StackStatusganatags(las deDescribeStacks). Una pila creada con la plantilla de 0.6.1 o anterior se actualiza sin revertirse: la plantilla gana el parámetroLegacyCallerPolicy, que conserva la descripción original deCallerPolicy(IAM no la cambia y CloudFormation sustituía la política, que chocaba con su propio nombre fijo y dejaba la pila enUPDATE_ROLLBACK_COMPLETE), ysetuplo pone atrueen una pila sin la salidaSandboxLauncherPolicyArn.- Un
--token-fileque dejó unrayito agent deployfallido 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-failurese conserva, porque es el único acceso al sandbox que se queda, y un fichero de una ejecución anterior nunca se toca. rayito setup --jsoninforma la versión de cada imagen publicada en su paso. Trasapply, los pasosbaseImageycapsImageconpublishllevan enimageVersionla versión recién publicada (antesnull, aunqueresultsí la tenía). En--dry-runy si la preparación falla, siguen ennull.- 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 dePutSecretValue/GetSecretValue, el token derunHookPayload, el entorno deCreateMicrovmo elItemde unTable.put_item(la URL de un webhook de eventos de ciclo de vida y el nombre de su secreto).import rayitodescarta ahora los registros por debajo de INFO debotocore.endpoint,botocore.parsers,botocore.auth,botocore.regions,boto3.resources.action,boto3.resources.collection,boto3.dynamodb.table,s3transfer.tasksys3transfer.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ásbotocore,boto3,s3transferyurllib3a WARNING, comohttpxyhttpcoreen 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"ymax_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 enCapacityExceptiontras 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_timeoutde 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 hastarequest_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 nuevosrestoreSecondsentestdata/agent-fleet/lifetime.json.
Seguridad¶
opentelemetry-apiyopentelemetry-sdk1.45.1 enuv.lock(GHSA-xcq7-p3pf-p88h, afectaba a 1.45.0 y anteriores), y el extraotelpide ahoraopentelemetry-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 untracestateajeno.
0.17.0 - 2026-10-23¶
Añadido¶
rayito setup --caps-imagepublica tambiénrayito-base-caps, la base que usarayito agent deployen MicroVM y que antes había que publicar a mano. Es un cuarto paso del plan (capsImage) que sale del mismorayito-image.zip(una sola descarga) conadditionalOsCapabilities=["ALL"]; el plan y--dry-runmuestran 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 comoskipy no se lee ni se publica esa imagen. Sirayito agent deployen MicroVM no encuentrarayito-base-caps, el error piderayito setup --caps-image. El documento de--jsonganacapsImageArnycapsImageVersionenresult.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()yrayito agent deployguardan, 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ámetroSecureStringestándar de Parameter Store (/rayito/agents/<nombre>/recipe), con nombres de secretos y nunca valores.AgentFleet.connect(nombre)sinprojectla lee y puede crear VMs iguales;use_recipe=Falsela ignora. Es la única función encendida por defecto (no cuesta en el nivel estándar):AgentDeployOptions(save_recipe=False)/--no-save-recipela apagan. Un fallo al guardarla es un aviso (recipe_access_denied,recipe_not_saved,recipe_too_large), nunca un despliegue fallido. Nueva sentenciaAgentRecipe:CallerPolicyla guarda, la lee y la borra;SandboxLauncherPolicysólo la lee y la borra (escribirla equivale a poder desplegar) eImagePublisherPolicysólo la guarda (AgentRecipeWrite). FleetStatus.scaling_source(explicit,project,recipeodefault).AgentFleet.stop(keep_recipe=...)yrayito agent stop --keep-recipe: por defectostop()borra la receta que leyó o guardó.AgentDeployOptions.scale_in_after_secondsyrayito agent deploy --scale-in-after SECONDS.- Renovación opcional de la clave de corta duración de Bedrock
(
renew_gateway_keysenAgentFleet,AgentFleet.connect,AsyncAgentFleetyAgentDeployOptions;--renew-gateway-keys), apagada por defecto, con el extrabedrock-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 --envrechaza un nombre que parece un secreto, también*_SECRETySECRET, los que tienen un segmentoPASS,PASSWD,PAT,CREDENTIALoCREDENTIALS, y los que llevanAUTHoBEARERcomo último segmento o junto aTOKEN,KEYoHEADER(AUTH,X_AUTH,AUTH_HEADER; sin distinguir mayúsculas; antes pasaban).AUTH_MODE,AUTH_URLoAUTHOR_NAMEsiguen 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 deenvdel proyecto ([env]derayito-agent.tomlo la biblioteca) se guardan tal cual en la receta, así que no pongas enenvnada secreto. rayito setup --caps-imagepublicarayito-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/AsyncSandboxPooladoptaba al arrancar todas las plazas del backend. Con el fichero de estado por defecto derayito 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 sinmax_lifetime/on_timeout, así queraydrechazaba elset_timeoutque 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 conon_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
httpxyhttpcore, que a nivel INFO escriben la URL de cada petición con su consulta, arrancan en WARNING enserve(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
AttributeErrortras responder400a una línea de petición mal formada; las invocaciones siguientes nunca se vieron afectadas. Ese400lleva ahora su línea de estado también en Python < 3.14, y un cliente que cuelga a media respuesta deja una líneahttp_error code=connection_closeden vez de una traza en stderr. --create-secretbusca la clave por el preset de la pasarela: una pasarela[gateways.model] preset = "bedrock"leeAWS_BEARER_TOKEN_BEDROCKen vez de buscar una variable por el nombremodel. Conpreset = "custom"sigue mirando el nombre de la pasarela, como en 0.16.--create-secretya no duplica el prefijorayito/: la detección escribía el secreto de la pasarela comorayito/agents/<proyecto>/<proveedor>y, como la pasarela lo lee bajorayito/, el secreto acababa enrayito/rayito/agents/.... Ahora escribeagents/<proyecto>/<proveedor>y el secreto queda enrayito/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; unrayito-agent.tomlcon un nombre propio no cambia.- OpenCode sin fichero de proyecto usa
AGENTS.mdy los permisos deopencode.json(ADR-034 D8). Antes sólorayito-agent.tomllos aplicaba. Ahora la detección lleva elAGENTS.mdde la raíz aagent.instructions_file, y el bloquepermissiondeopencode.jsonuopencode.jsonc(sin comentarios ni comas finales) aagent.permissions.askse aplica comodeny, porque la ejecución es desatendida, y una clave que no se puede aplicar se ignora. Los dos casos avisan conopencode_permission_ignored. Lo que dicerayito-agent.tomlgana, 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 oescalado desconocido: sin proyecto ni receta.AgentFleet.connect(nombre)sinprojectya no falla si Parameter Store no responde (sin endpoint de VPC para SSM, limitación de tasa, un error de KMS): es el avisorecipe_unreadabley la flota de 0.16, yrayito 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 conrenew_gateway_keys=Trueexplícito. Si la receta lo pedía y no lo pasas, la flota no renueva y avisarecipe_renewal_ignored(sólo el código). Listar, consultar o terminar la flota nunca necesita el extrabedrock-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_keysno renueva un secreto compartido: si el secreto de la pasarelabedrocklo lee también una pasarela cuyo upstream no es Bedrock, la opción esInvalidArgumentException(nombra las pasarelas, nunca el secreto). Sólo cuenta como Bedrock un hostbedrock-runtime.<región>.amazonaws.comcon una región de AWS válida; un host comobedrock-runtime.evil.s3.amazonaws.comya no se toma por Bedrock.- Un nombre de flota
.,..o vacío ya no da una ruta de receta (recipe_not_saved). rayito setupinformaartifactUploadedtambién cuando sólo publicarayito-base-caps(una cuenta que viene de 0.16).- La ayuda de
--scale-in-aftersale de la constante del SDK y dice que, sin la bandera, manda el[scaling]del proyecto. - La ayuda de
rayito agent deployya no pierde los corchetes: Typer la pinta con el marcado de rich, que se comía[scaling]en--scale-in-aftery[#clave]en--secret-env(éste, desde 0.16) como si fueran estilos. rayito agent deploycon--min-vms 0desplegaba, guardaba la receta y terminaba conIndexErroral imprimir el resultado: una flota sin VMs con servicio no imprimesandbox_idniurly--jsonlos da comonull.
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íainvalid_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 variableid_env,base_url_envyapi_key_env(nunca valores), y en MicroVMs Rayito los apunta a la pasarela con el marcador como clave. La detección vebase_url=en el constructor del cliente (openaipasa aopenai-compatible) y los nombres de variable del modelo, y avisa conmodel_id_from_code. El id sigue siendo obligatorio con las pasarelasbedrockygeminiy con los runtimes que construyen el modelo; con unentrypoint,ctx.modelesNoney la telemetría omitegen_ai.request.model. --model-id,--model-providery--model-base-urlenrayito agent detectydeploy(DetectOverrides.model_id,model_provider,model_base_url).--external-modelen AgentCore (AgentCoreOptions.external_model, propexternal_modeldeRayitoAgent): 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íneaexternalModel.- Entrada estructurada y selección de grafo (ADR-033 D3): un grafo de
LangGraph cuyo esquema de entrada no es de mensajes (por ejemplo,
InputStateconticket_id) se invoca coninvoke(input={...})ystream(input={...})enAgentDeployment,AgentFleet,AgentCoreFleety sus gemelos asíncronos (promptpasa a ser opcional; exactamente uno de los dos), y devuelveAgentResult.outputyDone.output. El host lee el esquema conget_input_jsonschema(), comprueba elinputcon la biblioteca estándar y pasa la sesión comothread_id; un grafo de mensajes sigue igual. Fallosprotocol_errorconinput_required,invalid_inputuoutput_not_serializable, con su propio mensaje, yInvalidArgumentExceptioncon una pista de redespliegue si el host desplegado es anterior. Con varios grafos enlanggraph.json,--graph,[runtime] graphyDetectOverrides.grapheligen uno; sin elegir y sin uno llamadoagent, el error lista los nombres.rayito agent invokeacepta--inputy--input-file(-lee stdin) en lugar dePROMPT. Límite nuevoAGENT_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=...)conSecretEnvRefySecretEnvSource, y--secret-env NOMBRE=<secreto>[#clave]y--secret-env-from <secreto>[,prefix=P][,keys=A|B]enrayito agent deploy. En AgentCore el runtime sólo recibeRAYITO_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 consecret_env_*; los secretos se suman aReadableSecretArns(la plantilla no cambia) y el plan lo dice con la líneasecretEnvysecretEnvNames. 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 JSONrayito/agents/<nombre>/envcon los valores de un.envy lo usa comosecret_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.exampleque no reciben valor.- Protocolo
langgraph-api(experimental) para servir unlanggraph.jsonconlanggraph dev:HttpApp(protocol="langgraph-api", assistant_id=...)(y[runtime.app] protocol = "langgraph-api"conassistant_id) hace que el host traduzca cada invocación aPOST /threads/{thread_id}/runs/waitdel LangGraph Platform API, con el hilo derivado de la sesión (la misma sesión sigue el mismo hilo), salud en/ok, el texto deresponse_fieldo del último mensaje y el uso de suusage_metadata. Uninterruptdel grafo esAgentException(protocol_error)condetail_codegraph_interrupted.rayito agent detectydeploycon--protocol langgraph-apisirven así un proyecto conlanggraph.json(kind = "custom", el launcher delanggraph devy el grafo elegido comoassistant_id), y sin la opción la detección no cambia.langgraph deves un servidor de desarrollo en memoria; Rayito no instalalanggraph-cli[inmem](el paquetelanggraph-apiusa la licencia Elastic-2.0). Sin streaming nilanggraph upen esta versión.HttpApp.served_health_pathyserved_invoke_pathdicen las rutas a las que llama el host. Coninvoke(input={...})el objeto llega tal cual como entrada del grafo y, si el estado final no tiene mensajes, vuelve comoAgentResult.output;--graphelige elassistant_id.
Cambiado¶
AgentModel.idpasa destrastr | None, y con élAgentSpec.effective_small_model: sólo cambia el tipo que se lee, ninguna llamada existente. Un proyecto con id se comporta igual, peromypyavisa donde el código lo usa comostr; estréchalo (if spec.model.id is not None).model_requiredse lanza por fin: AgentCore con un proveedorbedrocksin[model] idni--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_requiredya no lleva la pista «(bedrock_models en el SDK)», y unlanggraph.jsoncon varios grafos y ninguno llamadoagentda el texto deambiguous_graph(los nombres de los grafos y--graph) en vez del deambiguous_entrypoint, con el mismo códigoambiguousenruntime.entrypoint. - La detección sólo convierte
openaienopenai-compatiblepor unbase_urlpropio 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 avisocustom_base_url). -
Un prompt a un grafo con esquema de entrada propio sin claves
requiredsigue el camino de mensajes, como en 0.15;input_requiredqueda para los grafos que declaran claves obligatorias. -
Un
400a una petición coninputse explica con el cuerpo del host: elinvalid_requestde 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. Coninput, el SDK mide el cuerpo entero de la petición contraAGENT_SERVICE_MAX_REQUEST_BYTES, no sólo elinput; con un prompt no cambia nada. rayito agent invokesale con el código de uso (2) por unInvalidArgumentExceptionsólo con--input/--input-file; conPROMPTsigue 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,deepagentsostrandssinentrypoint) y sinAgentModel.idfalla al construirse, antes de cualquier llamada o de crear el secreto de--create-secrets-from.- Con
langgraph-api, un estado final que esoutputya no se repite como texto, el JSON del estado sin mensajes es estricto (invalid_responsecon unNaN) yhealth_path = "/ok"(el de la detección) ya no da la pista de rutas ocultas. Un grafo con esquema propio acepta enintegerun número sin parte fraccionaria (3.0), y suoutputserializa modelos de pydantic (model_dump(mode="json")) y dataclasses.
Seguridad¶
- Con
--external-modelel 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_envnunca 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, sinkms:Decrypt), y un test falla si se amplía. El agente y lo que lance pueden leer su entorno: la guía,security.mdySECURITY.mdlo dicen. - El fichero de
--create-secrets-fromse 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-envsin=o con un nombre que no es de variable da el error ensecret_env, sin repetir lo escrito. En MicroVMs el host recibe tambiénRAYITO_SECRET_ENV_NAMES(sólo nombres) y redacta los valores desecret_enven el diagnóstico de arranque igual que en AgentCore. - El host lee los valores de
secret_envpor 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 setupprepara 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 pilarayito-m0-artifacts), crea o actualiza la pilarayito-m0-iamy publicarayito-basedesde elrayito-image.zipde la release de tu versión, comprobado contra suSHA256SUMS(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 conrayito doctor. Con--jsonimprime un único documento. La pila de IAM va empaquetada con el SDK y no aparece enrayito stack list.rayito setupcrea el bucket de artefactos con la pilarayito-m0-artifactscuando no hay ninguno, ydeploy_agent/rayito agent deploylo encuentran por su salidaBucketName(sólo lectura). Con--json, un error de AWS o de pila quesetupno 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(ydeploy_agent/deploy_agent_async/plan_deploycontarget="agentcore"yagentcore=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 pilarayito-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()ystream()tienen la API y la semántica de sesión deAgentFleet;rayito agent invoke,statusystopaceptan--target agentcorey hay unrayito agent listy unrayito agent deletenuevos (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,AgentCoreDeployPlanyAgentCoreException. El destino por defecto sigue siendo MicroVM y sintarget="agentcore"no se crea ningún cliente de AgentCore, CodeBuild ni ECR.- El export
agentcorese despliega tal cual: suDockerfiletoma la imagen base de un argumento de build (AGENT_BASE_IMAGE, con el mismo digest), el README explica--target agentcorey 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. ElDockerfiley el README del exportdockerno cambian. - OpenCode obtiene el rol del runtime con un perfil
credential_process: OpenCode sólo arranca su proveedor de Bedrock conAWS_PROFILEu otra variable de credenciales de AWS, y en AgentCore el rol sólo lo sirve el servicio de metadatos de la microVM. El exportagentcorede un agente OpenCode añade el ayudante de botocore, unAWS_CONFIG_FILEcon un perfil cuyocredential_processlo ejecuta (conExpiration, para que el SDK renueve) yAWS_CONFIG_FILEyAWS_PROFILEen la imagen. MicroVM, el exportdockery los demás frameworks no cambian. - Códigos de error de
rayito setup:bucket_unavailable(un bucket que existe sin acceso, 403),bucket_takenyartifact_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-Idcon 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(extracdk:aws-cdk-lib>=2.261.0,<3yconstructs>=10.5.0,<11).RayitoAgentdespliega concdk deployel mismo agente querayito agent deploy --target agentcore, con la misma plantillainfra/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()), yCustom::RayitoRuntimeMmdsexige MMDSv2.RayitoMicrovmImagepublica una imagen de Lambda MicroVMs conrayd(y sus tamaños) desdeMicrovmImageArtifact.download_release()(comprobado contraSHA256SUMS, con caché para síntesis sin red) ofrom_file(), con su propio rol de build y la misma configuración querayito image publish.cdk synthno llama a AWS; la región del stack debe ser concreta. Errores propios enRayitoCdkException(region_unresolved,image_name_reserved,artifact_checksum_mismatch…); los del plan siguen siendoAgentCoreException. Sin el extra,import rayitoy 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+cdkse lista, se consulta y se invoca como cualquier otro (rayito agent list/status/invoke,AgentCoreFleet.connect), perorayito agent deploy,rayito agent deleteydeploy_agent(target="agentcore")lo rechazan conname_conflict, yrayito image publishrechaza una imagen con la etiquetarayito:managed-by=rayito-cdk. Los runtimes con la marcarayito.agentcore/1no 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 puentenode_bridge.mjsllevan ahora el soporte de AgentCore (id de sesión y credenciales del rol). Como los dos forman parte del contexto de build, el primerdeploy_agenttras 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. ElDockerfiley el README del exportdockerno cambian; sus ficheros deagent_host/y, en Node,node_bridge.mjssí. - Los errores de una cuenta sin preparar apuntan a
rayito setup: elSandboxNotFoundExceptiondeSandbox.create()cuando falta la imagen y los errores dedeploy_agentpor falta de bucket o de imagen base terminan con «si la cuenta o la región aún no están preparadas, ejecutarayito 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_agento build deAgentTemplatede 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 condeploy_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 comocustomcon su adaptador. Strands instala conuvcuando el proyecto lo usa, y el host recibequeue_maxyqueue_timeout_secondsenhost.json. La detección de una app HTTP que lee su puerto dePORTahora 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):ServeOptionssumaqueue_maxyqueue_timeout_seconds(la cola del host; con 0, el valor por defecto, la llamada que no cabe recibe 429 como hasta ahora) ymax_concurrencyqueda acotada por las conexiones que admite el MicroVM dememory_mib.AgentProjectsumascaling(ScalingOptions:min_vms,max_vms,warm_vms, sesionessharedodedicatedyscale_in_after_seconds),env(sin secretos),networkconallow_outydeny_out,region,bucket,base_image_version,language,frameworkyversions(RuntimeVersions). El ficherorayito-agent.tomladmite las tablas[scaling],[env]y[deploy], las clavesallow_out,deny_out,queue_maxyqueue_timeout_seconds, y en[runtime]las delanguage,framework,python_versionynode_version. Todo es aditivo y los valores por defecto son los de un solo VM. Los errores de lectura suman los códigosambiguous,not_detectedyunsupported. 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 paradeploy_agent. - Host y adaptadores de instalación del agente:
el host del agente sirve una cola FIFO acotada (
queue_maxyqueue_timeout_secondsdehost.json; sin cola por defecto: la llamada que no cabe recibe 429, como hasta ahora) y supervisa las apps de larga vida (http-appy Hermes): una app que muere se relanza con espera creciente,/pingesHealthyBusyy las invocaciones reciben503conRetry-Aftermientras tanto, y agotado el presupuesto el marcador de fallo llevaapp_crashloopcon la cola saneada de su salida de error.HttpApp(protocol="json")traduce/invocationsal contrato propio de una app (request_fieldyresponse_field) y reenvía sus demás rutas;agentcoresigue siendo el valor por defecto de unHttpAppexplícito. Un único puente de Node (node_bridge.mjs, drivernode-callable) ejecuta agentes de Strands (el antiguostrands-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") conCustomRuntime(entrypoint="ruta.ts#exportacion", package_manager=...). YCustomRuntimeyStrandsRuntime(language="typescript")aceptanpackage_manager(pip,uv,npm,pnpm,yarn),python_versionynode_version: el plan de instalación (_install_plan.py) baja CPython y Node con sha256 fijado enlimits.jsony 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()yAgentFleet: despliega el agente que ya tienes con una llamada (agent-zero-config):deploy_agent(ruta)(ydeploy_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 deRAYITO_BUCKETo el de activos del bootstrap de CDK; nunca crea un bucket) y el access token, construye la imagen sólo si cambió y devuelve unAgentFleet: uno o varios MicroVMs que sirven el agente.ScalingOptionsfijamin_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, elsession_iddel resultado es un handlers1.<sandbox>.<sesión>que enruta derecho a su VM sin servidor propio; una flota llena espera en una cola cliente y, al agotarse, lanzaCapacityExceptionconcode="agent_capacity"; un VM que desaparece esSandboxExceptionconcode="agent_vm_lost". Los VMs por encima demin_vmsse terminan solos (set_timeout), un VM que deja de contestar se sustituye yAgentFleet.connect()reconstruye la flota en otro proceso.plan_deploy()calcula el plan y el coste de lista sin llamar a AWS.AgentDeployOptions,DetectOverridesyPoolConfig(access_token=)son aditivos:AgentProject.deploy()no cambia. Nada que cueste se crea sin pedirlo: un secreto sólo concreate_secret_from_env=True, la imagen base sólo con un publicador. -
rayito agent detectyrayito 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-artifactcon--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-fileniRAYITO_ACCESS_TOKEN, y los logs del build salen resumidos.invoke,statusystopaceptan 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 (losRUNNINGcon las etiquetas de un despliegue de Rayito, sondeados con unHealthsin token), y si la imagen tiene unRUNNINGque no lo es, tampoco se cuentan los suspendidos, que no se pueden sondear sin reanudarlos:stopnunca termina un sandbox ajeno. La salida de 0.13 deagent deployse conserva (mismas claves, con otras añadidas) y--filesigue 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),HermesRuntimeyCustomRuntimeconHttpAppsuman runtimes aAgentTemplate,AgentyAgentPool, y el tipo de proyectolanggraphcubre 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 nuevosInstallableRuntimeyServableRuntime(conHostConfig,GatewayWiring/DirectWiringeInstallTarget) describen cómo se instala y cómo se sirve un runtime sin tocar el puertoAgentRuntime, así que los runtimes de 0.13 siguen funcionando.AgentTemplateacepta nombres del registro u objetos runtime. -
AgentProjectyAgentDeployment: del ficherorayito-agent.tomla un agente desplegado o exportado:AgentProjectyAsyncAgentProjectconstruye la imagen, ejecuta una vez, despliega un servicio (rayito.agent-service/1, el contrato HTTP de AgentCore:GET /pingyPOST /invocations, con SSE) o exporta un contexto Docker/AgentCore (arm64, puerto 8080) sin llamar a AWS.AgentDeploymentyAsyncAgentDeploymentinvoca, consultaDeploymentStatusy 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. -
AgentProjectyrayito agent: describe un agente en un fichero y construye, ejecuta, despliega o exporta (agent-runtimes-byo): la CLI sumarayito agent init,build,run,deploy,export,invoke,statusystop, conchas sobreAgentProjectyAgentDeploymentcon--json, los valores por defecto del SDK y salida 2 para un fichero de proyecto inválido.initescriberayito-agent.tomly un andamiaje por runtime (Strands, Strands harness, Hermes, LangGraph, deepagents, OpenCode o propio) sin sobrescribir nada sin--force;exportyinitno llaman a AWS, ydeploynunca imprime las cabeceras de acceso. El andamiaje deinitse comparte con la CLI de TypeScript entestdata/cli/agent-scaffold/. Guía nueva, Despliega tu agente, y un adaptador de StrandsSandboxsobre Rayito endocs/examples/strands_rayito_sandbox.py. -
AgentProject.deploy()acepta el access token del sandbox yrayito agent deploy|invoke|status|stopse pueden encadenar entre procesos:deploy( access_token=...)lo pasa aSandbox.create(), y la CLI guarda el token en--token-file(o usaRAYITO_ACCESS_TOKEN) antes de crear el sandbox, igual quesandbox create --detach. Sin ninguno de los dos,deploysale con 2 sin crear nada: antes dejaba un MicroVM que niinvoke, nistatusnistoppodían volver a abrir.invokeystatusleen el token con--token-filey sin él salen con 2;stopya no lo necesita (terminate-microvmpor id). -
AgentDeployment.pause(),resume()yaccess_token, ydeploy(keep_on_failure=)(agent-runtimes-dx):AgentDeploymentyAsyncAgentDeploymentsuspenden y reanudan el sandbox del despliegue (Sandbox.pause()yresume()) y exponen el access token con el que se creó, también el que se generó cuando no se pasóaccess_token=, para reconectarse conAgentDeployment.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 unaccess_tokenque el llamador conozca y anota el id del sandbox en el error.rayito agent deploy --keep-on-failurehace lo mismo. -
AgentResult.usage_reportedyDone.usage_reported:Falsecuando el agente no informó de tokens (unaHttpAppcuya respuesta no llevausage, un comando conprotocol="text"o una función que no lo devuelve).usagevale entonces cero porque se desconoce, no porque no se gastara nada, y la CLI imprimetokens=desconocidoyusageReporteden--json. UnaHttpAppinforma deusagesi su respuesta lo trae (input/output,input_tokens/output_tokensoprompt_tokens/completion_tokens). -
HttpApp.hides_own_routesy el aviso derayito agent deploy: conprotocol="agentcore"u"openai-chat"el despliegue sólo expone/pingy/invocations; si tuhealth_pathoinvoke_pathson otros, esas rutas no se alcanzan desde fuera (404).rayito agent deploylo avisa y apunta aprotocol="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 listalist-managed-microvm-image-versionsy 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 deployahora despliega una flota, con más llamadas a AWS (agent-zero-config): el mismo comando con un--filede 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 (HeadBucketyGetCallerIdentity, y el de activos del bootstrap de CDK si no pasas--bucketniRAYITO_BUCKET), construye la imagen del proyecto si su contenido cambió (excluye por defecto.git,.env*,.venv,venv,node_modules,__pycache__,*.pycy.rayito, así que un proyecto que los subía a la imagen la reconstruye) y devuelve una flota. Las credenciales necesitan por tantosts:GetCallerIdentity,s3:ListBucketsobre el bucket,lambda-microvms:GetMicrovmImageysecretsmanager:DescribeSecret, además de las de 0.13. Con--no-buildy sin bucket reutiliza la imagen existente sin construir nada. -
ServeOptions(max_concurrency=)queda acotada por la memoria de la VM: un MicroVM dememory_mibadmite 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 conInvalidArgumentException: bajamax_concurrencyo subememory_mib. -
CustomRuntimecorre tu app y tu comando desde el código copiado, con su venv primero en elPATH(agent-runtimes-dx): el directorio de trabajo de unaHttpAppes el delsourcecopiado (/opt/agents/app) y va el primero en suPYTHONPATH, así quepython -m paquete.modulofunciona; conrequirements,pythonypipson los del venv, y sin élpythones el intérprete de la imagen (que sólo traepython3). Loscommandde unCustomRuntimereciben el mismoPATHyPYTHONPATH, y el directorio de trabajo de uncommandno 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_reportedenPOST /invocationsy dejausagesin valor en elstep_finishedde un comando conprotocol="text"o de una función sinusage. Un cliente de 0.13 que no conoce el campo sigue leyendousage. -
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 unaHttpApp, el final de su salida de error (acotado aAGENT_STARTUP_DIAGNOSTICS_MAX_BYTES, sin valores de entorno ni credenciales);deploy()lo incluye en laSandboxExceptionen vez del nombre de clase opacoAgentFailure. -
rayito agent init --runtime customescribe una app HTTP ylanggraphun grafo de LangGraph a secas:customescribe[runtime.app](conprotocol = "raw", sus rutas/healthe/invoke),requirements,instally unserve.pymínimo;langgraph, uncreate_react_agenten vez decreate_deep_agent. El runtimelanggraphtraduce además el nodoagentdecreate_react_agent(antes sólomodel), 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
0600con--chmod), la imagen de OpenCode instalacurlantes 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 startque muere dejaba al servidor huérfano ocupando el puerto y el relanzado nunca quedaba listo. excludede unAgentProject/AgentTemplate(app_exclude=)filtra antes de leer: un.venvexcluido se leía entero y luego se descartaba (y sucacert.pemdisparaba el aviso de ficheros con secretos). Ahora los patrones deexcludese suman a los de.dockerignorey 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 proxyrecorta sóloOWSy valida el tamaño de trozo antes de reenviarlo (ADR-027, vectores compartidos con la CLI de TypeScript entestdata/cli/proxy-vectors.json). Los valores de cabecera, los elementos deContent-Length/Transfer-Encodingy elchunk-sizepierden sólo espacios y tabuladores (RFC 9110 §5.6.3):str.strip()quitaba también\x85o\xa0, así que unContent-Length: 12\x85tenía longitud 12 en el proxy y un upstream estricto lo rechazaría. Unchunk-sizeinválido ya no se escribe en el upstream antes de cortar. Los topes y plazos del proxy (PROXY_*) pasan alimits.json, con los mismos valores. -
La CLI escribe siempre los instantes en UTC (
2026-09-15 14:39:02+00:00en--jsony en las tablas,isoformaten UTC enimage prune): antes salían en la zona local de la máquina, porque boto3 entregadatetimeen 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-iamde la piladashboard(opt-in, por defectocognito-jwt): la API tiene su propia Function URL conAuthType 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 contrabotocore) y envía el token de Cognito enX-Rayito-Token. Verificada en AWS real (2026-10-14). -
Panel: cuentas desde el entorno:
rayito dashboardlee--accountdeRAYITO_DASHBOARD_ACCOUNTS(entradasNOMBRE=ARNseparadas 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 deListMicrovmsyListMicrovmImages, 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 conRAYITO_E2E_BILLED_COST=1) yFleet. -
Coste a la vista en el panel (ADR-026): la ayuda de
rayito dashboardy derayito dashboard cloudwatchdice qué es gratis y qué cuesta (Cost Explorer, DynamoDB de eventos,resumeque vuelve a facturar cómputo, el dashboard de CloudWatch y la métrica de--quota), y los textos de coste de la pila salen delimits.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 proxyquita la cookie del panel con cualquier separador. -
Panel, fase 3 (ADR-026), todo opcional y apagado por defecto:
rayito dashboard cloudwatchimprime el JSON de un dashboard de CloudWatch (Logs Insights sobre/rayito/*;--otlpy--quotaañaden las métricas OTLP y el uso de cuota) sin llamar a AWS, y la piladashboard-cloudwatchlo 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 piladashboard-read-rolecrea ese rol en la cuenta destino; la pilaquota-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 dashboardpasa a ser un grupo (sin subcomando sigue arrancando el panel local, con las mismas opciones). RutaGET /api/v1/accounts(siempre[]salvo con--account). Constantes nuevas enlimits.json(dashboardAccountsMax,dashboardAssumeRole*,quotaMetric*,quotaAlerts*). -
Pila
dashboard(el panel de gestión dentro de tu cuenta, ADR-026, fase 2): componente deOptionalStacks/rayito stack deploy|status|destroy dashboard. Una Lambda con Function URL sirve la misma página y la misma API/api/v1querayito 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 porThemeJsono 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 enlimits.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 en127.0.0.1con una página sin compilación (módulos ES y web components, claro y oscuro) y la API JSON versionada/api/v1sobreFleet,QuotasyUsageCost: 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-actionshabilita matar, suspender y reanudar con confirmación y una cola que respeta el TPS;--cost-explorery--events-stackactivan lo que cuesta o necesita una pila. Defensas:Host/Origincomo el proxy local, token de arranque de un solo uso canjeado por una cookieHttpOnly,SameSite=Strict,Sec-Fetch-Site, CSRF en todoPOSTy en la consulta de coste facturado, CSP estricta y nada registrado. Constantes nuevas enlimits.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(.jsono.css, oRAYITO_DASHBOARD_THEME) y--logo(PNG o SVG) se usa la paleta propia, validada y servida del mismo origen./api/v1/metaañadetheme. - 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) yFleet(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ónAsync*. Apagados por defecto: construirlos no llama a AWS, Cost Explorer ($0,01 por petición) sólo se toca conUsageCost.billed()y las acciones exigenallow_actions=True. Nueva excepciónDashboardExceptiony constantes nuevas enlimits.json(catálogo de cuotas, umbrales, TTL y precios de cómputo).rayito doctoryrayito image listreutilizan los lectores de cuotas e imágenes. Cada clase, método y opción dice si es gratuito o de pago (billed()yevents()son de pago;resumevuelve a facturar cómputo) con el mismo texto en ambos SDK. Guía: Panel de gestión. Template.dnf_install()yTemplate.npm_install(packages=None, g=False)(sync y async). Atajos del DSL para instalar paquetes del sistema condnf(sin paquetes débiles) y paquetes Node connpm; los nombres se escapan para el shell y se rechazan las listas vacías y los nombres que empiezan por-.dnf_installynpm_install(g=True)corren como root aunque unset_user()previo dejara otro usuario.npm_installfalla con el código 127 y un mensaje claro si la imagen no tiene Node.js.apt_installsigue lanzandoUnimplementedError, ahora apuntando adnf_install.
Cambiado¶
-
La variante de imagen se reconoce con un catálogo cerrado (
parity-image-variants).resolve_image_variantsólo interpretarayito-<variante>[-<talla>]con la variante enbase,base-caps,base-caps-efs,base-polyybase-slimy la talla del catálogo de tamaños. Una imagen propia que empieza porrayito-(por ejemplorayito-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 aHealth.features, como ya ocurría con un ARN.volumes=(yvolume_mounts=del shim) exige ademásrayito-base-caps-efscuando la variante se conoce:rayito-base-capssinamazon-efs-utilslanzaUnimplementedErrorantes de lanzar en lugar de después. -
Pila
dashboard-cloudwatch:DashboardNamevacío (el valor por defecto) usa el nombre de la pila en lugar del fijorayito, 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: conIncludeOtlpoIncludeQuotalos widgets añadidos se creaban con la región literal${AWS::Region}, porqueFn::Subno sustituye dentro de los valores de sus variables. La plantilla resuelve ahora la región conFn::JoinyRef 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 arayito/images/runs/<RUN_ID>/rayd-<sha>.zipen 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--jsonañadeartifactUploaded(si esta invocación subió el objeto).make image-publish*pasa el flag cuando hayRAYITO_E2E_RUN_IDoARTIFACT_RUN_ID. Sin el flag, nada cambia.
Cambiado¶
AgentTemplatearranca antes (ai-agent-first-start). Dos cambios en la imagen que construye, medidos en AWS real (AWS_API_NOTES.mdQ155 y Q156):kernel_warmup=Falsepor defecto (opción nueva; CLI--kernel-warmup/--no-kernel-warmup) escribe el marcadorslimdel sidecar, así que el kernel derun_codeya 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 unready_cmdque 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:
AgentTemplateya no precalienta el kernel derun_code(kernel_warmup=Falsepor defecto). En una imagen construida con 0.11, la primera celda derun_codeque importe numpy, pandas, matplotlib, scipy o scikit-learn paga esa importación. Para volver al comportamiento de 0.10, construye la imagen conAgentTemplate(..., kernel_warmup=True)orayito agent template build --kernel-warmup. rayito doctorconoce la serie 0.11: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITYydocs/site/docs/limits.md) exige elrayddel tagrayd-v0.11.0para el SDK 0.11.
0.10.0 - 2026-10-08¶
Añadido¶
- Más proveedores para el modelo del agente (
ai-agent-providers).AgentModel.provideradmite"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_gatewayylitellm_gateway(upstream=, base_path="/v1"). OpenCode usa los proveedores nativosopenai,googleyazurea través de la pasarela; deepagents construyeChatOpenAI(use_responses_api=True),ChatGoogleGenerativeAIoAzureChatOpenAI(Responses API, clave enapi-key). Elopencode.jsondeclara 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/v1yOPENCODE_EXPERIMENTAL_WEBSOCKETSqueda apagado; la prueba de humo deAgentTemplateimporta tambiénlangchain_openaiylangchain_google_genai.gen_ai.provider.namevalegcp.geminiyazure.ai.openaipara 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 doctorconoce la serie 0.10: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITYydocs/site/docs/limits.md) exige elrayddel tagrayd-v0.10.0para el SDK 0.10.
Fixed¶
-
El timeout y
abort()desbx.agentparan 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: unsleepo un servidor lanzado por el agente sobrevivía y el siguienterun()encontraba el agentebusy. Cada ejecución arranca ahora conkill_tree=Trueyraydcongela y mata todo su árbol (también lo demonizado); el stream acaba conreason="timeout"cuando ya no queda nada.abort()y los límites del SDK sólo matan el handle: desaparece la ordenpgrepque corría antes como segundo comando.commands.run()aceptakill_tree=(sync y async) para lo mismo en cualquier comando. Necesita elraydde esta versión: con uno anterior el campo se ignora y el timeout yabort()sólo alcanzan al grupo del runtime; reconstruye la imagen. -
deepagents cuenta los tokens de los modelos compatibles con OpenAI. Con
"openai-compatible",ChatOpenAIno pedía el uso en streaming (stream_options.include_usage): cada paso llegaba con 0 tokens,result.usagequedaba a cero yAgentLimits.max_total_tokensno cortaba nunca. El runner crea ahoraChatOpenAI(stream_usage=True). Medido de verdad, con el egress cerrado, contra la Chat Completions compatible con OpenAI de Amazon Bedrock (bedrock-runtime/openai/v1ybedrock-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) llevasocksio, el extrasocksde httpx.raydexportaALL_PROXY=socks5h://...en el sandbox y, sin él, httpx fallaba al crear el cliente (AgentFailedconImportError) 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 derequirements-deepagents.txt: reconstruye la plantilla de agente.
0.9.1 - 2026-10-07¶
Fixed¶
Sandbox.create(timeout=120)sinidleya no lanzaInvalidArgumentException. Desde 0.3.0, cualquiertimeout(omax_lifetimeconon_timeout) de 300 s o menos fallaba porque el SDK validaba su propiaIdlePolicypor defecto contra él. Ahora, sinidleexplícito, la auto-suspensión por defecto se desactiva si no cabe (el sandbox termina en sutimeout, como en E2B) y, conon_timeout="pause", baja a 60 s (PAUSE_DEFAULT_IDLE_FALLBACK_SECONDS). Unidleque pasas tú se valida igual que antes. Igual enSandboxyAsyncSandbox.
Documentation¶
- Los e2e de
s3-mountsyrayd-otlpdocumentan como precondición que la política gestionada de su pila (PolicyArn) esté adjunta al rol de ejecución;rayito stack deploysólo la crea.
0.9.0 - 2026-10-07¶
Cambios que rompen¶
-
rayito.e2b.BuildExceptionyrayito.e2b.TemplateExceptionson las clases nativas. Antes eran clases propias del shim que nada lanzaba, así queexcept rayito.e2b.BuildExceptionno atrapaba unTemplate.build()fallido. Ahora sonrayito.BuildException/rayito.TemplateException, yBuildExceptionhereda deSandboxException(en E2B hereda deException). Migración:except rayito.e2b.BuildExceptionahora atrapa los builds fallidos reales. Como la clase es unSandboxException, cambian losisinstance/issubclassque 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()oBuildException(a, b)fallan conTypeError. -
Se retira la opción D del arranque rápido del agente (pool con
opencode serveresidente), sin periodo de obsolescencia: apenas ganaba unas décimas a la opción C (AWS_API_NOTES.mdQ154) y costaba más memoria y más por plaza. Desaparecenagent_pool_warmup(..., serve=),sbx.agent.prepare(serve=),attach=desbx.agent.run()/stream()(sync y async) yRunRequest.attach; el script de OpenCode ya no busca ni se engancha a un servidor (--attach) ni relee la vuelta de él, y el spanrayito.agent.rundeja de llevarrayito.agent.attached. El puertoAgentRuntimepierdeabort_command(ningún runtime lo usaba ya) ywarmup_steps()no lleva argumentos;OPENCODE_SERVE_PORTdesaparece. Migración: quitaserve=Trueyattach=; para un primer mensaje rápido usaagent_pool_warmup(runtime)(opción C) o el arranque normal (Sandbox.create(...)ysbx.agent.run(...)). Un runtime propio borraabort_commandy el parámetroservedewarmup_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 queraydrechaza esSandboxException. Migración: quita el import y cualquierexcept GatewayException; si capturabas fallos de la pasarela al crear o refrescar, capturaSandboxException. AWS_REGIONgana aAWS_DEFAULT_REGIONy a la región del perfil en el SDK de Python (ver "Fixed"). Si exportasAWS_REGIONcon un valor distinto deAWS_DEFAULT_REGIONo de laregionde tu perfil, el SDK pasa a operar (sandboxes, secretos, índice, EFS…) en la región deAWS_REGION. Migración: para conservar la región anterior, quitaAWS_REGIONdel entorno o pasaregion=explícita.
Fixed¶
-
El SDK de Python honra
AWS_REGION, como el de TypeScript y la CLI. Antes boto3 sólo leíaAWS_DEFAULT_REGIONo 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 prioridadregion=> 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 exportaAWS_DEFAULT_REGIONno nota cambios. -
Sandbox.connect()yAsyncSandbox.connect()recuperansbx.gateways: otro proceso ya puede usarsbx.agentsobre un sandbox creado congateways=sin recrearlo.connect()lee una vez elConfigureStatusderayd(nombre, puerto y último error de cada ruta; nunca el upstream ni las cabeceras) si el agente anunciasecret_gatewayy el handle no aplicógateways=él mismo. En un handle recuperado,refresh()sólo relee el estado y cadaconnect()vuelve a leerlo con el timeout de petición de esa llamada. Rotar la clave (refresh()oreincarnate()) sigue siendo cosa del handle que llamó acreate(gateways=): en uno recuperado,reincarnate()lanza. Siraydrechaza el token en esa lectura,connect()no falla: se salta la recuperación y elAuthenticationExceptionsale en la primera llamada autenticada, como hasta ahora.
Changed¶
rayito doctorconoce la serie 0.9: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITYydocs/site/docs/limits.md) exige elrayddel tagrayd-v0.9.0para 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_mismatchyoutput_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 deTemplate.buildpidenAWS_REGION. La ayuda derayito image sizes,doctor,sandbox proxy,agent template buildydomainya no nombra detalles internos.
0.8.0 - 2026-10-07¶
Cambios que rompen¶
register_webhook(eventos de ciclo de vida) rechaza conInvalidArgumentException, antes de cualquier llamada a AWS, una URL cuyo host eslocalhost(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"oruntime=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 (unprintdel usuario no lo rompe), construye el modelo contra la pasarela con un marcador como clave, aplica los permisos con un middleware, guarda las sesionesrda_…dentro del sandbox y emiteTextDelta.mcp,raw_configyattach=Truefallan conInvalidArgumentExceptionantes de cualquier RPC.make agent-runner-testlo prueba con el venv real en arm64. - Agente de IA, arranque rápido (
ai-agent-fast-start):AgentTemplate/AsyncAgentTemplateyrayito agent template buildconstruyen 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 manifiestorayito.agent-template/1y, salvoprefetch=False, un demonio que precarga los binarios tras cada restauración del snapshot.PoolConfigganawarmup(pasos que cada plaza corre antes de aparcarse; un paso fallido es un calentamiento fallido),allow_internet_accessynetwork;agent_pool_warmup()da los pasos del agente y, conserve=True, deja unopencode serveresidente con contraseña propia de cada VM al queagent.runse engancha tras eltake(). Coste sólo si construyes la plantilla o pasaswarmup(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 escribeopencode.jsonsin credenciales, lanzaopencode runcon el prompt por stdin, un cerrojo por sandbox y--attacha unopencode serveresidente 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 quesbx.agentdescribirá 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 desbx.gateways),AgentPermissions(sóloallow/deny;question,webfetchywebsearchdenegadas por defecto),SubAgent,McpLocal,McpRemoteyAgentLimits(50 pasos, 600 s, 16 MiB y 1 000 000 de tokens por defecto); los eventos (TextDelta,Text,Reasoning,ToolCall,StepStarted,StepFinished,AgentFailed,Done),TokenUsageyAgentResult;AgentExceptioncon unreasonde una lista cerrada y un mensaje fijo que nunca lleva texto del proveedor; y las pasarelas ya hechasbedrock_gateway,anthropic_gatewayyopenai_compatible_gateway, cuyoallowcubre sólo los modelos elegidos.sbx.agentllega 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 yasync.run()corre el agente hasta el final y lanzaAgentExceptionsi falla;stream()devuelve unAgentStreamiterable (gestor de contexto) que nunca lanza por un fallo del agente —el último evento esDoneoAgentFailed— y expone.abort(),.session_idy.result();prepare()dispara los pasos de calentamiento del runtime en segundo plano y vuelve enseguida. Aplica la configuración del runtime confiles.write_files(una sola vez por sha de configuración), lanza el script del runtime concommands.run(background=True, stdin=True)y trocea su stdout en eventos;AgentLimits.max_stepsymax_total_tokenslos impone el propio SDK sobre esos eventos, no el runtime.sbx.agentes una propiedad perezosa: tocarla no manda ningún RPC. Contracer_provider=, cada ejecución abre un spanrayito.agent.runcon atributosgen_ai.*yrayito.agent.*(nunca el prompt, el texto de la respuesta ni argumentos de herramienta). UnAgentRuntimeconcreto (el adaptador de OpenCode) llega en un cambio posterior; mientras tanto,runtime=admite cualquier objeto que implemente elProtocolAgentRuntime.
Fixed¶
sbx.agentcon unopencode serveresidente (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íaAgentFailed(reason="protocol_error")o unDonesin texto aunque el servidor completaba la respuesta. Ahora el script crea la sesión en el servidor, y al salirrunrelee de él los mensajes de esa vuelta y emite las partes que faltaban (sin duplicar las ya emitidas), incluido el error del modelo. Unsession_idque no tiene forma de id de OpenCode esInvalidArgumentException.AgentTemplateconprefetch: el demonio de precarga leía los binarios nada más restaurar el snapshot y su E/S competía con el arranque, así quecreate()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 enrunner_sha256del manifiesto. Sin él,runtime="deepagents"fallaba siempre en una imagen construida conAgentTemplate(aceptación en AWS,AWS_API_NOTES.mdQ150).sbx.agent:abort()y los límitesmax_steps/max_total_tokensparan 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 matarayd); antes unsleepo 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 siguienterun()no lo encuentrabusy.sbx.agent: un timeout deAgentLimitsterminaba conreason="runtime_error"en vez de"timeout".runtime="deepagents": las herramientas de ficheros escribían una ruta absoluta dentro del workdir (/home/user/xacababa 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: conserve=Trueel servidor residente moría al agotartimeout_seconds.import rayitovuelve a no cargar el agente de IA (rayito._agent.*,rayito.sandbox_*.agent) ni el adaptadorboto3de 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.agentse construye en el primer uso yPoolConfig(warmup=)importaWarmupStepsólo al validarlo. Unas 20 entradas menos ensys.modulesy menos tiempo de import;tests/unit/test_lazy_imports.pylo vigila.
Changed¶
rayito doctorconoce la serie 0.8: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITYydocs/site/docs/limits.md) exige elrayddel tagrayd-v0.8.0para el SDK 0.8. Sin la fila,doctordaba FAIL en cada instalación 0.8 yscripts/prepare_release_pr.pyse negaba a preparar la release.- Montajes S3: el
MountExceptionde 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 enRAYITO_ALLOWED_MOUNT_BUCKETSy cómo publicar la imagen con él (--env RAYITO_ALLOWED_MOUNT_BUCKETS=<bucket>omake image-publish-caps MOUNT_BUCKETS=<bucket>), sin nombrar el bucket. El texto vive enMOUNT_ERROR_HINTS, igual en los dos SDKs (testdata/s3-mounts/error-hints.json).
Security¶
- Eventos de ciclo de vida (
webhook-url-public-address):register_webhookrechaza también, conInvalidArgumentExceptiony antes de cualquier llamada a AWS, un hostlocalhost(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_addressaplica la misma regla que el deliverer y los tres pasantestdata/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(yscripts/image_zip.py) se niega a empaquetar un directorio de imagen que llevaraydsin sus avisos de licencia enlicenses/(LICENSE,NOTICEyTHIRD_PARTY_LICENSES.md, que dejamake image-licenses):image/Dockerfilelos copia a/usr/share/doc/rayd/, y sin ellos la build de la imagen fallaría en AWS (third-party-licenses). Un directorio sinraydno cambia.
0.7.0 - 2026-10-05¶
Added¶
- Dominio propio (
m15-custom-domain, ADR-024, experimental, apagado por defecto):CustomDomain/AsyncCustomDomaindespliegan 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), yregister/unregister/refresh/host_forgestionan las rutas{puerto}-{alias}.<tu-dominio>(hostname HTTPS normal, sin las cabecerasx-aws-proxy-*).register()exigetraffic_tokensalvopublic=Trueexplícito.deploy(alternate_domain_names=[...])(--alternate-domain-name) sustituye el alias comodín por hostnames exactos. Nuevo extrarayito[custom-domain](awscrt), porque el plano de datos del KeyValueStore firma con SigV4A. Sin instanciarCustomDomainno se crea ningún cliente de CloudFront ni de CloudFormation.Sandbox.create(domain=)sigue lanzandoUnimplementedError: la integración conget_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 deniegacloudfront:CreateDistributionpor una SCP, así que nunca se ha servido tráfico por una distribución desplegada con esta plantilla (AWS_API_NOTES.mdQ121, Q140, Q141). La API puede cambiar en una minor. rayito image zip --with-efsyrayito image publish --with-efs(m15-efs-volumes): publicanrayito-base-caps-efs, la imagen caps conamazon-efs-utilsque necesitavolumes=(+~198 MB de imagen, snapshot igual; exige--os-capabilities ALLy un zip con el marcadorefs_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 paramounts=,volumes=ytelemetry=(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 clienteefshasta el primer uso) ySandbox.create(volumes=), que valida la petición (tipos, rutas, variantecapsy un único conector propio enegress=: sin él, conINTERNET_EGRESSo con dos conectores esInvalidArgumentException, porque un MicroVM sólo admite un conector de egress,AWS_API_NOTES.md§16 Q131), como mucho 4 volúmenes yexecution_role_arn=antes de cualquier llamada a AWS; resuelve la IP de mount target que falte con unaDescribeMountTargetspor sistema de ficheros antes derun-microvm, manda la secciónefs_volumesen el únicoConfigureSandboxdecreate()(con plazo de 65 s), sólo vuelve con todos los volúmenesmountedy, si uno falla, termina el sandbox (salvokeep_on_failure) y lanzaVolumeMountException(code:network,iam_denied,not_found,tls,helper_missing,timeout,invalid_path); sobre una imagen sinamazon-efs-utils,UnimplementedError.reincarnate()la vuelve a mandar.sbx.volumes(await sbx.volumes()enAsyncSandbox) da el estado en vivo.EfsVolume(validamount_target_ipcomo IPv4),VolumeStatusy las excepcionesVolumeException/VolumeMountException/VolumeNotFoundException/VolumePathNotFoundException. El shim de E2B (rayito.e2b.Volume/AsyncVolume) hace CRUD real sobreE2B(volume_store=...)(volume_ides el nombre del volumen, el mismo que recibenconnect/get_info/destroy); sus operaciones de contenido (UnimplementedError("volume.content")) siguen sin plano de datos, ySandbox.create(volume_mounts=)monta conE2B(volume_store=..., volume_connector_arn=...): ese sandbox sale sólo por el conector del volumen yallow_internet_access=Trueexplícito esInvalidArgumentException. Componenterayito stack {deploy,status,destroy} efs-volumes(infra/efs-volumes.yaml: sistema de ficheros EFS cifrado, un mount target por subred deSubnetIds, 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óloec2: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énrayito doctor --efs-vpc-id ... --efs-subnet-ids ...),deploy()se niega si algún hallazgo esFAIL,volume_store()da unVolumeStoresobre la pila, ydestroy(delete_file_system=True)/delete_file_system(id)borran el sistema de ficheros conservado (sólo uno con la etiquetarayito=efs-volumes).AccessPointArnsacota la política del execution role a access points exactos ydeploy(read_only_access_point_arns=...)(ReadOnlyAccessPointArns) le deniegaClientWritea los de sólo lectura: la opciónrodel montaje no basta, porque el usuario del sandbox alcanza el puerto local deefs-proxy(Q133). El hallazgointernet-egressdecheck()explica que un sandbox con volumen sólo tiene internet por la VPC.list/getson eventualmente consistentes, comoDescribeAccessPoints(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):createde un nombre que ya existe reintentagethasta 30 s ydestroyde un access point que el listado aún mostraba pero ya no existe devuelveFalse.
Changed¶
rayito doctorconoce la serie 0.7: la tabla de compatibilidad (rayito.cli._compat.COMPATIBILITYydocs/site/docs/limits.md) exige elrayddel tagrayd-v0.7.0para el SDK 0.7, porque los volúmenes EFS y el endurecimiento del agente viven enrayd.
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 colaForwarderFailuresQueue(destinoOnFailurede 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.mdT22). - Eventos: sólo lo que emite
rayd, y reciente. Tras el MAC, el evento debe ser del mismo sandbox que el stream, con unevent_idde 32 hex (nunca un id del reconciliador),kill_reasonsólo enkilledy sólorequest, un ARN demicrovm-imagey unoccurred_at_msde 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 aDELIVERY#<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_webhookrechaza conInvalidArgumentExceptionuna 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 arayito/stacks/<componente>/<sha256>.zip(dentro del espaciorayito/que protegen las plantillas de IAM, ya no en la raíz del bucket), mandaExpectedBucketOwnercon la cuenta del llamante y, si el objeto ya existe, compara su contenido antes de reutilizarlo (si no coincide, lo sobrescribe conChecksumSHA256y avisa por el logger). infra/iam.yaml: el lanzador ya no puede publicar imágenes. Nuevas políticasSandboxLauncherPolicy(sólo lanzar y manejar sandboxes: la del rol de un servicio en producción) eImagePublisherPolicy(sólo publicar);CallerPolicysigue igual, como su unión. ElDenydel execution role sobre los artefactos de imagen existe ya siempre, no sólo conPersistenceBucket.infra/templates.yaml:RayitoTemplateBuildersólo leerayito/*del bucket de artefactos o de la imagen base, nunca el bucket entero (que puede guardar losHOMEpersistidos y las transferencias).- Secretos de firma de webhooks fuera de
secrets=.RayitoSecretsReaderniegarayito/webhooks/*y el SDK se niega a leer esos secretos porsecrets=/SecretCache(InvalidArgumentException).SecretPrefixdeinfra/secrets-access.yamldebe 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 exigeaws:SourceAccount. - Eventos: el código del sandbox ya no puede inundar la pila con
paused/resumed. Esos dos eventos los emiteraydcuando 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 traskilled, ningúncreatedrepetido, ninguna posición repetida o anterior) y limitapaused/resumedcon un cubo por sandbox (20 seguidos, después uno cada 30 s); lo rechazado se cuenta comoinvalid_transitionorate_limitedy 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/resumedquedan documentados como orientativos (SECURITY.mdT22). 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-signatureno 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 emiteEventsLauncherPolicy(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) yEventsWebhookAdminPolicy(register_webhook/listar/borrar: sólo filasWEBHOOK). 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:RayitoRunAllowedSizesniega también publicar imágenes. Sin ello, una identidad con laCallerPolicyestá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 conimageVersion(SECURITY.mdT27).SECURITY.md: filas T20, T23, T26 y T27, que los cambios de M15 planearon y nunca se aplicaron (infra/otlp-export.yamlya citaba T23).infra/ci-oidc-role.yamlyinfra/README.mdpiden 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 deTestImageArns.SecretGatewayrechaza una ruta deallowqueraydnunca dejaría pasar (sec-rayd-agent-hardening): las rutas deallowsiguen ahora la misma lista de permitidos queraydaplica 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 lanzaInvalidArgumentExceptionantes de cualquier llamada, con los mismos vectores compartidos querayd(testdata/secret-gateway/request-paths.json).rayito sandbox proxycompruebaHostyOrigin. 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 unWebSocketde otro origen). Ahora sólo reenvía siHostes de loopback con el puerto local, la dirección de--bind,<id>.localhosto un nuevo--allowed-host(si no,421), y si elOrigin, 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 otroHost(p. ej. un proxy inverso) necesita--allowed-host. Como mucho--max-connections(8) conexiones a la vez (503después). Anuncia tambiénhttp://<id>.localhost:<puerto>, con cookies separadas de tus otras apps locales.rayito sandbox proxyreenvía un solo mensaje por conexión: la primera petición, con su cuerpo delimitado porContent-Lengthochunked; 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 responde101se reenvía conConnection: closey se cierra en vez de quedar como túnel sin filtrar. Cambio: una delimitación ambigua (Transfer-Encodingjunto aContent-Length, unTransfer-Encodingque no acaba enchunked, variosContent-Lengthdistintos) es400..dockerignorecon la semántica de Docker enTemplate.build: anclado en la raíz,**como cero o más directorios (así**/.env,**/.gity el resto de los valores dedocker initexcluyen 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/(*.pycsólo excluye los de la raíz; usa**/*.pyc). UnUserWarning(con las rutas, nunca el contenido) avisa si el contexto va a empaquetar.env*,.git/,.aws/,.ssh/,*.pemo*.key.Template.buildno sigue enlaces simbólicos dentro de un directorio copiado (ni a fichero ni a directorio), como Docker y el SDK de TypeScript (el zip derayito image zip/publishlos 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 conO_NOFOLLOWjusto antes de leerlo.- Credenciales de git:
clone/push/pullconusername/passwordrestauran 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 loggerrayito.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
ClientErrorcrudo;Template.buildtraduce un rechazo de AWS aBuildException(reason="aws_error")(antes subía elClientError); el manejador de errores de la CLI,rayito doctoryrayito pruneredactan 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 yCommandResult.truncated/CommandExitException.truncatedlo indican.commands.run/connectaceptanmax_output_bytes(0no guarda nada; los callbacks reciben siempre todo) yrayito sandbox execya 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
\xNNvisibles.rayito sandbox logssólo usa un stream que no es el esperado si tiene exactamente la formaYYYY/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 esInvalidArgumentException. Los generados (32 bytes) no cambian. ProxyTokeny el contexto derayito doctorya no muestran el JWE enrepr.Sandbox.create(persist=)yAsyncSandbox.create(persist=)ligan el sandbox a su prefijo de persistencia (C-07): elrunHookPayloadlleva el bucket y la baseprefixdelS3Prefix(nuncaprefix/name), y unraydcon la corrección rechaza conpermission_deniedcualquier checkpoint o restore fuera de esa base. Con unprefixpor inquilino, el token de un sandbox ya no alcanza elHOMEpersistido de otro aunque compartan execution role. Cambia un uso: un restore o checkpoint explícito hacia otra base desde un sandbox creado conpersist=ahora falla conpermission_denied. No añade llamadas a AWS.- La wheel y el sdist se hashean justo después de
uv build, antes de quecheck_wheel.pyo twine ejecuten nada, y se vuelven a comprobar al final del job: elsha256sum -cdel job que publica ahora prueba que lo publicado es lo que produjouv 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 -cno detecta un fichero que no esté enSHA256SUMS, y la acción de publicación sube todo lo que haya en el directorio. rayito image zipymake image-publishrechazan 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(salidaOperatorPolicyArndeevents-webhooks): sigue siendo la unión de las tres políticas nuevas durante esta versión. Vincula a cada identidad la suya (EventsLauncherPolicy,EventsReaderPolicyoEventsWebhookAdminPolicy).
0.6.1 - 2026-10-04¶
Fixed¶
- Redesplegar una pila opcional ya no deshace su configuración
(
OptionalStacks.deploy()/AsyncOptionalStacks.deploy()yrayito 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 conUsePreviousValue. Antes, por ejemplo, redesplegars3-mountssin repetirPrefixesvolvía a'*'(todo el bucket),metadata-indexsinTableNamereemplazaba (y borraba) la tabla ysecrets-accesssinKmsKeyArnquitabakms:Decrypt. Nuevoparameter_changes()para ver qué cambiaría sin desplegar;rayito stack deploylo 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=lanzanUnimplementedErrorcon "todavía no disponible" en vez de "llega en 0.6";rayito domain(pendiente) ya no aparece enrayito --help;rayito template build|status|logs,rayito stackyrayito eventstienen texto de ayuda.reincarnate()reaplica todas las secciones deConfigureSandbox(m15-reincarnate-configure-replay, sync y async): hasta ahora sólo reenviabagateways=y un sucesor perdíamounts=,events=ytelemetry=.create()guarda sus opciones 0.6 enLaunchOptions.features(relaunch_features: todas menossize, que ya va en el ARN de la imagen) yreincarnate()las reenvía campo a campo (feature_kwargs) alcreate()del sucesor, que las aplica por el mismo camino en su únicoConfigure:events=derivak_sbxdel nuevosandbox_id,mounts=espera otra vez amountedytelemetry=usa la imagen y la memoria del sucesor.LaunchOptions.gatewayspasa aLaunchOptions.features.gateways(atributo interno).
Documentation¶
- Bloques "Coste y activación" completos para
mounts/S3Mountysize/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.mdy las notas de 0.6.0 ya no describen como pendientes funciones publicadas en 0.6.0. - Metadatos del paquete: la URL
Documentationapunta al sitio de documentación y se añadeIssues; el README del paquete termina con la licencia, elNOTICEincluido y la nota de marcas (proyecto independiente, no afiliado a E2B). ElNOTICEconserva 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
OptionalStackyConfigureSandbox(v06-foundations, M15 foundations, ADR-015/ADR-016, opcional y apagado por defecto): el servicioOptionalStacks(deploy/status/destroy/components) y la CLIrayito stacksobre un catálogo de nueve componentes (metadata-indexysecrets-access, migrados sin cambios de comportamiento; los otros siete son stubs hasta su propia función); las siete opciones 0.6 deSandbox.create()(mounts,volumes,size,events,telemetry,gateways,domain) existen ya en la firma y lanzanUnimplementedErrornombrando el cambio que las trae mientras sigan siendo un stub, antes derun-microvm; en cuanto una deja de serlo (mounts=, ver más abajo),create()/take()ejecutan susconfigure_sectionsjusto tras el primerHealth(_configure_base:require_capabilities,build_configure_request,check_configure_response), dentro del mismo camino que ya termina el sandbox ante cualquier fallo anterior aagent_ready. Fila de compatibilidad "0.6" enrayito.cli._compat.COMPATIBILITY. Sin ninguna opción nueva, el comportamiento es byte a byte el de 0.5.x (traza de oro entests/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 desderayito) en el guest conmount-s3/FUSE, sólo sobrerayito-base-caps(require_caps_forlo exige antes derun-microvmcuando la imagen se nombra directamente; sobre un ARN opaco la decisión se difiere aHealth.featurestras/run, que termina el sandbox si falta la capacidad).create()no vuelve hasta que cada montaje está montado: sondeaConfigureStatus(como mucho 15 s) y, si uno falla o no se asienta, termina el sandbox y lanzaMountException.sbx.mounts(propiedad) da el estado en vivo de cada montaje ("pending"/"mounted"/"failed",ConfigureStatusen cada lectura); una sección rechazada o un montaje fallido lanzaMountExceptioncon uncodecerrado (network,iam_denied,not_found,not_allowed,invalid_path,helper_missing,timeout).raydlanzamount-s3como el usuario dedicadorayito-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 IAMRayitoS3MountAccessdel componenteOptionalStacks3-mounts(infra/s3-mounts.yaml, desplegable conrayito stack deploy s3-mounts --param BucketName=... --param Prefixes=..., pideCAPABILITY_IAM), acotada al bucket y a sus prefijos (hasta 4) también para leer, escribir y borrar objetos. El bucket debe estar enRAYITO_ALLOWED_MOUNT_BUCKETSde la imagen (rayito image publish --env, dem15-sizes-catalog).- Catálogo de tamaños, aceptación en AWS real (
m15-sizes-catalog):rayito image publish --env/--sizesvuelve a reutilizar una versión ya construida con la misma configuración:list-microvm-image-versionsno devuelveenvironmentVariables(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 conGetMicrovmImageVersion(gratuita), sólo cuando la publicación lleva variables.rayito image sizesacepta--image-name, comopublish, 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 conRayitoCompatWarningsi 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, esInvalidArgumentExceptionantes de cualquier llamada a AWS.rayito image publish --sizes 512mb,4gbpublica, desde el mismo artefacto, una imagen adicional por tamaño (en oleadas de hasta 10 construcciones simultáneas), horneandoRAYITO_BASELINE_MEMORY_MIBen la imagen (nunca un interruptor de activación);--env KEY=VALUEañade cualquier otra variable de imagen.get_info()confirma, con una única llamada cacheada aGetMicrovmImageVersionpor versión de imagen,SandboxInfo.baseline_memory_mibybaseline_cpu(vCPU medido exactamente para los cinco tamaños del catálogo);cpu_count/memory_mbsiguen siendo lo que el guest reporta de verdad. El shimrayito.e2breporta ese mismo baseline enSandboxInfo.cpu_count/memory_mbcuandosize=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, unaGetMicrovmImageVersionsin cuota propia por imagen ya publicada, nunca lanza ningún sandbox). Guardarraíles de coste opcionalrayito stack deploy sizes-guard(RayitoRunAllowedSizes: un Deny delambda:RunMicrovmfuera de los ARN de imagen permitidos, efectivo aunque la identidad ya tenga elmicrovm-image:*de laCallerPolicyestándar). Sinsize=, el comportamiento sigue siendo exactamente el de 0.5.x. LifecycleEvents/AsyncLifecycleEvents(m15-events-webhooks, opcional y apagado por defecto): despliegainfra/events-webhooks.yaml(deploy/status/destroy, componenteevents-webhooksdeOptionalStacks), 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). CLIrayito events deploy|status|destroy|listyrayito events webhook add|list|remove, con el mismo bloque de coste y confirmación querayito stack. Los errores de AWS llegan comoWebhookExceptioncon sólo el código (aws_code).events=enSandbox.create()/AsyncSandbox.create()valida el tipo y queloggingllegue a CloudWatch antes de lanzar y, trasrun-microvm, manda la clave del sandbox (k_sbx, derivada de la clave del stack y elsandbox_id; unGetSecretValuepor instancia deLifecycleEvents) en el mismoConfigureSandboxquemounts=/gateways=/telemetry=(LifecycleEventsSectionFactory, ADR-020); sin la pila desplegada, sinlifecycle_eventsen el agente o con la sección rechazada, termina el sandbox (salvokeep_on_failure). Sinevents=, ni un cliente DynamoDB/Secrets Manager nuevo ni una llamadaConfigureSandbox.rayito events deploy --tag K=V(repetible) para cuentas cuya organización exige etiquetas al crear recursos. Aceptado en AWS real (AWS_API_NOTES.mdQ105–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 derayda CloudWatch (m15-rayd-otlp, ADR-021, opcional y apagado por defecto):TelemetryExport/OtlpAuthnuevos (OtlpAuth.execution_role(), exigerayito-base-caps;OtlpAuth.bearer(secret_name=...), experimental, funciona enrayito-base; el nombre se resuelve bajorayito/por la mismaSecretCachequesecrets=). Se envía como una sección deConfigureSandboxtras/run; una imagen cuyoraydno exporta termina el sandbox (salvokeep_on_failure=True) y lanzaUnimplementedError. Contracer_provider=, cada RPC del handle lleva además eltraceparentdel span de esa misma llamada haciarayd(lo calcula un interceptor del canal en el hilo o la tarea que llama, no elAuthMetadataPlugin, que grpc ejecuta en un hilo suyo sin el contexto OpenTelemetry del llamante); sin él, ninguna cabecera nueva. El token deOtlpAuth.bearer(...)es una API key de CloudWatch Metrics.sbx.get_telemetry_status()(síncrono yAsyncSandbox) leeConfigureStatus, como una llamada explícita aparte deget_health(). Sintelemetry=, 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/AsyncTemplatecompilan un DSL (igual alTemplatede E2B v2) a un Dockerfile y un zip deterministas sobre una imagenrayito-base/rayito-base-capsya publicada;Template.build()/build_in_background()/get_build_status()/exists()suben el artefacto por hash de contenido y llaman acreate/update-microvm-image, reutilizando una versión idéntica en vez de reconstruir. Un build fallido se explica conBuildException(step/command/exit_code/log_taildel log de BuildKit, oreason="ready_client_error"|"ready_server_error"si falló elready_cmd), sin repetir nada.from_image/from_template/from_dockerfile/from_gcp_registry/apt_installlanzanUnimplementedError(documentados en ADR-022). El shimrayito.e2b.Template/AsyncTemplateya construye de verdad, con la firma de E2B (alias,skip_cache,memory_mbredondeado conRayitoCompatWarning,cpu_countcon aviso) yE2B(bucket=...);TemplateException/BuildExceptiondel shim pasan a ser las clases nativas.set_start_cmd()necesita una imagen base conrayd0.6. La imagen compuesta hereda la configuración de la base (additionalOsCapabilitiesincluida);skip_cache()equivale aforce=True; la cuota de builds de AWS llega comoreason="build_quota". El núcleo de build de imágenes pasa arayito._images, compartido conrayito image publish(sin cambio de comportamiento). Sin llamar aTemplate.build(), el SDK no crea ningún cliente nuevo.infra/templates.yaml(rayito stack deploy templates): sólo la política IAMRayitoTemplateBuilder, $0 en reposo, que no puede sobrescribir las imágenes base publicadas. Aceptación en AWS real:pip_install()compila apython3 -m pip install --no-cache-dir --break-system-packages(rayito-base no tienepipen elPATH); la versión gestionada que se hereda se envía como1, no como el eco1.0quecreate-microvm-imagerechaza; la política concedeCreateMicrovmImagesobre*(AWS no la autoriza por ARN) ylambda:PassNetworkConnectorsobre los conectores gestionados, y suDenycubreUpdateMicrovmImagesobre las bases (AWS_API_NOTES.mdQ114-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 suallowcubre, dentro de su límite de peticiones por minuto, inyectando cada cabecera vaultada (resuelta de Secrets Manager con la mismaSecretCachequesecrets=, nunca antes deConfigure) y eliminando primero cualquier cabecera del mismo nombre que el sandbox intente poner.sbx.gateways["nombre"].urlda la URL de loopback;refresh()/arefresh()rotan el secreto sin recrear el sandbox (releen Secrets Manager aunque laSecretCacheno haya vencido, conservan el puerto y lanzan siraydrechaza la sección). TambiénSandboxPool.take(gateways=)/AsyncSandboxPool.take(gateways=). Cualquier fallo al configurarla trasrun-microvm(imagen anterior a 0.6.0, flag ausente, secreto que falta, sección rechazada) termina el VM salvokeep_on_failure. Singateways=, ningún clientesecretsmanagernuevo se construye y no se manda ningúnConfigureSandbox.
Fixed¶
rayito stack deploy events-webhooks --artifact-bucket B(yOptionalStacks.deploy("events-webhooks", artifact_bucket=B)) ya no exige repetir--param ArtifactBucket=B:StackArtifact.bucket_parameter_keypasa a la plantilla el mismo bucket al que se sube el código, y un valor distinto esInvalidArgumentExceptionantes 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=enSandbox.create()(también conpool=),connect()(ambas formas) y las variantes de clasekill/pause/resume; instrumentacommands.run,run_code,files.*ykill/pause/resumede instancia con spansrayito.*deSpanKind.CLIENT. Sintracer_provider=no se importaopentelemetry(extraotel,pip install rayito[otel]) ni se crea ningún span (NOOP.span()devuelve siempre el mismonullcontext; 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óntraceparent/tracestatehaciarayd, 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ónindex=enSandbox.create(),Sandbox.list(),Sandbox.paginate()(y sus gemelos asíncronos) yPoolConfig.create()escribe una fila inmutable (PutItemcondicional) trasrun-microvm; conmetadata=eindex=,list()/paginate()unenlist-microvmsconBatchGetItempor página y filtran también sandboxesSUSPENDEDsin ninguna sonda deHealth, token niget-microvm. Nuevas excepcionesSandboxIndexExceptioneIndexWriteException. Coste: ~1 WRU por sandbox creado y 0,5 RRU por candidato listado (DynamoDB on-demand); la tabla la despliegas tú coninfra/metadata-index.yaml.reincarnate()conservaindex=: el sucesor escribe su propia fila. - Shim de E2B:
Sandbox.list(query=SandboxQuery(metadata=..., state=[PAUSED]), index=...)yE2B(index=...)filtran sandboxes en pausa por metadatos con el índice. - CLI:
rayito sandbox list --metadata K=V --state suspended --index-table TABLA(y--statesola);--index-tablesin--metadataes 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=)concreate/update/get_info/exists/list/destroy(versión entera enClientRequestToken, metadatos enDescription, borrado sin ventana de recuperación, reintento acotado decreatetras un borrado reciente) ySecretCache(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 hastadestroy+ $0,05/10 000 llamadas. secrets=/secret_cache=enSandbox.create()(también conpool=),connect(),SandboxPool.take()y, por llamada, encommands.run,pty.create,run_code(contextos Python) ycreate_code_context, síncrono y asíncrono: entregan el valor como variable de entorno por losenvsque ya viajan arayd(sin RPC nueva, vale con imágenes 0.4.0), nunca en elrunHookPayload,metadata, logs ni errores. Se resuelven antes derun-microvm. La primera vez avisan conRayitoCompatWarning: el valor es visible para el código del sandbox.- Shim de E2B:
Secret/AsyncSecret,SecretInfo,SecretPaginator/AsyncSecretPaginator,SecretException/SecretNotFoundExceptionsobreSecretStore, con los nombres dee2b2.51.0;E2B(...).Secretusa su región y su sesión.fill()devuelve el placeholder, que nada resuelve;iam_tokensigue enUnimplementedError.
Changed¶
- El motivo de
UnimplementedErrordelist(state=PAUSED, query.metadata)en el shim de E2B nombra ahora la opciónindex=DynamoDbIndex(...). RayitoCompatWarningvive ahora enrayito.exceptions(y se re-exporta desderayito.e2b.exceptions: es la misma clase).-
rayito.e2b.Secret/AsyncSecretyE2B(...).Secretya no lanzanUnimplementedError: llaman a Secrets Manager cuando se usan. -
rayito sandbox proxy ID --port N(m12-sizes-proxy): expone un puerto del guest enhttp://<bind>:<local-port>(--local-port, por defecto igual a--port;--bind, por defecto127.0.0.1;--allow-remotepara escuchar fuera de loopback) sin coste de AWS más allá deGetMicrovm+CreateMicrovmAuthToken(gratuitos). Reutiliza elTokenRefresher/TokenStoredel SDK (mismo refresco a los 45 min); siempre acuñaPortSpec.single(N), nuncaallPorts; rechaza el puerto 9000 (hooks, ADR-006) antes de llamar a AWS. Quita cualquier cabecerax-aws-proxy-*del cliente, fijaHost/X-aws-proxy-auth/X-aws-proxy-porty fuerzaConnection: closesalvo 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 responde502(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 (400y cierra) cualquier CR/LF suelto fuera de un\r\n(contrabando de cabeceras que podía colar unx-aws-proxy-*falso),obs-foldy nombres fuera de lostcharde RFC 9110.--porty--local-portse validan y el socket local se reserva antes deGetMicrovm/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()(ySecret.destroy/AsyncSecret.destroydel shim de E2B) devolvíaTruepara un nombre que nunca existió, porque AWS acepta elDeleteSecretforzado de un nombre inexistente sinResourceNotFoundException(aceptación de 0.5.0 en AWS real). Ahora haceDescribeSecretantes y devuelveFalsesin borrar si no existe (o ya estaba programado para borrarse, o lo borró otro a la vez), como documenta y como E2B. Usasecretsmanager:DescribeSecret, ya incluido enRayitoSecretsAdmin.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.mdavisa de que el DEBUG de botocore/urllib3 imprime los valores (Rayito nunca lo hace) y de quelist()es eventualmente consistente (~3–5 s).
0.4.0 - 2026-09-30¶
Fixed¶
-
rayito doctortiene la fila0.4de la tabla de compatibilidad (SDK 0.4 exigerayd0.4.0); sin ella la comprobacióncompatibilitydaba FAIL. -
files.read()de un fichero grande enrutado por S3: si el plazo (request_timeout/stream_idle_timeout) vencía justo cuando elget_objectseguía en vuelo, el SDK seguía lanzando eseget_objecttras una cancelación ya vista, aunque el resultado tardío se descartaba y el llamante siempre recibíaTimeoutException.ObjectFetch.runcomprueba ahora la cancelación antes de abrir el cuerpo, así que no sale ningúnget_objectde más una vez vista la cancelación.
Cambios que rompen¶
LifecycleUnsupportedExceptionpasa a ser subclase deUnimplementedError(ya no deInvalidArgumentException/SandboxException), y el kernel ausente enrun_code/create_code_context(y cualquier RPC que el agente no implemente, vía la tabla unaria genérica de_transport.py) deja de serInvalidArgumentException/SandboxException: ahora esUnimplementedErroren los tres casos, un único tipo para "esta feature no está disponible en este sandbox". Migración: cambiaexcept InvalidArgumentException/except SandboxExceptionalrededor de esas llamadas porexcept rayito.UnimplementedError(oexcept LifecycleUnsupportedException, que ahora es subclase suya);MetricsHistoryUnavailablesigue siendoUnimplementedErrorpara quien la captura, pero su__cause__cambia: ya no es laRpcErrorde gRPC directamente, sino elUnimplementedErrorgenérico de la tabla unaria (cuya propia__cause__sí es laRpcError;__cause__.__cause__para llegar a ella).rayito.e2bno cambia de superficie: seguía lanzandoUnimplementedErroren los tres casos con el mismofeature,reasonydoc; sólo se simplifica su traducción interna. La pista "publica una imagen con una versión actual de rayd" deunimplemented_rpc_errorsólo sale para un RPC de verdad ausente: un kernel ausente enrun_code/create_code_contextya nombrarayito-base-polyen el propio detalle derayd, así que ya no repite un consejo que no aplica (la imagen ya está actualizada).
Changed¶
sbx.kill(),sbx.pause()ysbx.connect()(rayito.e2b, llamadas de instancia) avisan ahora conRayitoCompatWarningcuando se les pasaheaders=,proxy=oretries=(ysbx.kill()/sbx.pause()también conrequest_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; usaSandbox.<kill|pause|connect>(sandbox_id, ...)si necesitas aplicarlos.
0.3.3 - 2026-09-29¶
Security¶
.github/workflows/release.yml: el jobpythonse parte enpython-build(sinenvironmentniid-token, el único que hace checkout y correuv build) ypython-publish(con el token OIDC de PyPI, sin checkout y sinuv, que verifica consha256sum -cque eldist/descargado es el que produjopython-buildantes 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 leeConnectionConfig.current_integration()ni construye un plano de control compartido (shared_control_plane) — la construcción con AWS se mueve al nuevobind_control_plane(rayito.e2b._connection), el único punto de I/O;native_call_kwargsrecibeintegrationcomo parámetro y sólo describe, enNativeCall.plane_settings, si hace falta un plano nuevo.snapshot_configya no escribe el atributo privado deConnectionConfigdesde fuera de la clase; ahora pasa por el nuevoConnectionConfig._snapshot. Sin cambio de comportamiento observable ni de ninguna firma pública (ConnectionConfig,Sandbox,AsyncSandbox,E2B); veropenspec/changes/m10-python-shim.
0.3.2 - 2026-09-29¶
Fixed¶
- Reconexión tras un reset de stream del proxy de AWS (
RST_STREAMantes del plazo real): grpcio lo presenta comoCANCELLED "Stream removed"(AWS_API_NOTES.md#33), queis_stream_resetno clasificaba como corte reconectable (sólo mirabaUNAVAILABLE/INTERNAL), así quecommands.connect/pty.connect,watch_diry el reenganche derun_codefallaban en vez de reconectar. AhoraCANCELLEDcon esa marca (u otra deSTREAM_RESET_MARKERS) reconecta igual que unSocket closed; unCANCELLEDsin la marca (el propiocall.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 dedisconnect()tampoco reconecta (_transport.py). rayito-mcpypython -m rayito.mcpsin el extrarayito[mcp]: antes fallaban con unModuleNotFoundErrorsin capturar (traza completa) porquerayito.mcp/__init__.pyimportaba todo el paquete de forma ansiosa, así que cualquier import derayito.mcp(incluidorayito.mcp.__main__) disparaba el fallo antes de que su propiotry/exceptpudiera atraparlo. Ahorarayito.mcpresuelve sus cuatro nombres con__getattr__perezoso (PEP 562) yrayito.mcp.__main__imprime un aviso de una línea con el comando de instalación y sale con 2, sin traza, igual que la CLIrayitoconrayito[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/botocore1.43.103 en el lockfile de desarrollo y rango deuv-buildampliado 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.e2bpasa de E2B 1.x a 2.x y exige una imagen M9:create()siempre pide un plazo lógico araydy, contra una imagen anterior, termina el VM y lanzaUnimplementedError("lifecycle").timeoutdeja de ser la vida inmutable del MicroVM (ahora es el plazo lógico; el tope esmax_lifetime). Detalle en "Changed (shim)".rayito.e2b:sbx.pause()devuelvebool(antes el id del sandbox);files.watch_dir(path, on_event)ypty.create(size, on_data)toman el callback por nombre (el segundo posicional esuser, como en E2B 2.x).rayito.e2b:set_timeout,upload_url/download_url,get_metrics(start=, end=),list(next_token=)yallow_internet_access=Falseya no lanzanUnimplementedError, ymcp=,network=ylifecycle=ya no sonTypeError: 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)(oRAYITO_TRANSFER_BUCKET/RAYITO_TRANSFER_PREFIX/RAYITO_TRANSFER_REGION; sin bucket por defecto),create(transfer=),connect(transfer=)ysbx.transfer.files.upload_url(path, user=, expires_in=3600, max_bytes=, form=)devuelve unUploadTicket(unstrcon la URL, másheaders,fields,expires_at,wait(),status(),cancel(); de un solo uso, con la importación ya armada en el sandbox) yfiles.download_url(path, user=, expires_in=3600, filename=)unDownloadLink(strconsize,sha256yexpires_at; una foto del fichero al llamar, multiparte desdemultipart_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, vidamin(expires_in, max_expires_in, 604800));raydno guarda ninguna.reprnunca muestra la URL ypicklela rechaza. NuevosTransferStatus,AsyncUploadTicket,TransferException(code, reason),FileUploadExceptionyUnimplementedError(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_filessuben lo que mide al menosthreshold_bytes(y todo stream binario no buscable) directamente a S3 yraydlo importa verificando su sha256;files.readexporta y descarga verificando el sha256. Sintransfer, el camino gRPC no cambia.request_timeout(o60 s + 1 s por MB) acota también la pata S3 ystream_idle_timeoutrige entre trozos de la descarga enformat="bytes"/"text": una subida o descarga colgada falla conTimeoutExceptionsin 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=)yEntryInfo.metadata(mapa de sólo lectura, claves en minúsculas).gzipymetadataen una escritura exigen una imagen M9 (UnimplementedErrorantes de mover bytes);use_octet_streamse acepta sin efecto. Nuevos códigos deStreamError:resource_exhausted(DiskFullExceptionoRateLimitException) yfailed_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,timeoutes un plazo lógico queraydhace cumplir aunque el cliente muera ymax_lifetime(120–28 800 s, por defectotimeout + 60) esmaximumDurationInSeconds.set_timeout(timeout)(instancia y clase, con el access token) lo fija conSetTimeoutEXACT (puede acortarlo; más allá del tope,InvalidArgumentException) yconnect(timeout=)(instancia y clase) lo alarga conAT_LEAST.on_timeout="pause"suspende al vencer yidle.auto_resumereanuda con un plazo nuevo demax(timeout, 300 s). NuevosSandboxLifecycle(enget_health().lifecycleyget_info().lifecycle) yLifecycleUnsupportedException(una imagen anterior a M9 con un ciclo de vida pedido: el VM se termina salvokeep_on_failure). Sinmax_lifetimenion_timeoutel cable y la semántica son los de siempre.TimeoutExceptiontambié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;UnimplementedErroren una imagen anterior, como las transferencias y el plazo, con elUNIMPLEMENTEDde gRPC en__cause__),SandboxMetrics.mem_cache_bytes,SandboxHealth.cpu_countymemory_total_bytes,SandboxInfo.agent_version/cpu_count/memory_mbenget_info().Sandbox.paginate(limit=, next_token=, order=, started_after=, states=, metadata=)→SandboxListPaginator(next_items(),has_next,next_tokenopaco) ylist(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_TRAFFICySandboxHealth.egress_enforcement. Se aplica en el guest derayito-base-caps; en cualquier otra imagen el SDK termina el VM (también conkeep_on_failure) y lanzaUnimplementedError. - Kernels JavaScript y TypeScript (
m9-deno-kernels):languageaceptatypescript(aliasts) además dejavascript(js), servidos por el kernel de Deno derayito-base-poly. - Git (
m9-e2b-v2-surface):sbx.git(Git/AsyncGit) con la API git de E2B sobrecommands.run(clone,status,commit,push,pull,dangerously_authenticate, ...),GitStatus,GitBranches,GitFileStatus,GitAuthExceptionyGitUpstreamException; las credenciales nunca se registran y se redactan de la salida. - CLI (
rayito[cli]):rayito sandbox create [--detach --token-file],connect,exec -- CMDymetrics [--follow], con terminal interactiva (POSIX y consola de Windows) y el token sólo por fichero oRAYITO_ACCESS_TOKEN, nunca por argv. create()/connect()aceptanlogger=(los logs de ese sandbox van allogging.Loggerdado),is_running(request_timeout=)yCommandHandle.wait(on_pty=, on_stdout=, on_stderr=).- Shim
rayito.e2b2.x (m9-e2b-v2-surface, contratoe2b2.51.0 ye2b-code-interpreter2.10.0):set_timeout,connect(timeout)y la instanciasbx.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)conSandboxQuery.state/started_after/template,allow_internet_access=Falseynetwork(enrayito-base-caps),update_network,git,run_code(language="typescript"),E2B,ConnectionConfig,sbx.connection_config,proxy=,headers=,retries=,logger=,traffic_access_token,envd_api_urly las excepcionesFileNotFoundException,SandboxNotFoundException,ServiceBusyException,FileUploadException,BuildException.UnimplementedErrorexplícito (nuncaTypeErrorniAttributeError) parafork, snapshots,pause(keep_memory=False),network.rules,mask_request_host,allow_public_traffic=True,mcp,iam, volúmenes, secretos y templates.
Changed (shim)¶
rayito.e2bexige una imagen M9:create()siempre pide un plazo lógico; contra una imagen anterior termina el VM y lanzaUnimplementedError("lifecycle").timeoutes ahora el plazo lógico ymaximumDurationInSecondsesmax_lifetime(por defectomax(3600, min(timeout + 60, 28800))).rayito.e2b.Sandbox.set_timeoutdeja de lanzarUnimplementedError(lo que decía la entrada de 0.2.0): mueve el plazo lógico hastamax_lifetime.sbx.pause()devuelvebool(antes el id);files.watch_dirypty.createtoman el callback por nombre (el segundo posicional esuser);proxy=se honra;NotEnoughSpaceExceptionesDiskFullExceptiony se lanza;rayito.e2b.UnimplementedErrores la misma clase querayito.UnimplementedError.
Fixed¶
- Auto-resume tras una pausa por el plazo con el cliente vivo: en modo
on_timeout="pause"conauto_resume, si este cliente suspendió el sandbox al vencer y la suspensión real duró menos de 2 s (el vigilante deraydno la reconoce como congelación), el sandbox volvíaexpiredy la siguiente llamada fallaba consandbox_timeout. Ahora el SDK aplica la regla de E2B (max(timeout, 300 s), acotada al tope menos 5 s) con unSetTimeouten el primersandbox_timeouttras reanudar, una vez por suspensión. La marca de un solo uso sólo se consume cuando laresume_generationya avanzó y elSetTimeoutrespondió (unsandbox_timeoutque llega antes de la congelación ya no la gasta), y los callers concurrentes comparten una sola reapertura (un lock enSandbox, una tarea compartida enAsyncSandbox): un únicoSetTimeouty todos reintentan. rayito doctor: la tabla de compatibilidad gana la fila0.3(raydmínimo0.3.0); sin ella la comprobacióncompatibilitydabaFAILcon 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 pasasession=, 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 ensbx.info.UnimplementedErrorde una política de egress en una imagen sinCAP_NET_ADMINnombraallow_internet_access=Falsesólo cuando ese flag, y nonetwork=, es lo que pide la política (la misma regla que TS).- CLI: al salir de
rayito sandbox connect/createel 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.extrase lee del proto con las claves ordenadas: el orden de un mapa protobuf no está garantizado.- La readiness de
create(),connect()yresume()exige además unHealthconsandbox_id: el proxy deja pasarHealthantes de queraydreciba/run, y eseHealth(kernel del snapshot sin rotar) daba por listo un sandbox cuya rotación de/runponíakernel_ready=falsejusto después (AWS_API_NOTES.md Q78, visto en la regresión de M9 concreate()en 2,35 s).
Security¶
repr()deLaunchPlanya no muestra el access token ni el deLaunchRequestelrunHookPayload(que lleva losenvsdecreate()): ambos campos quedan fuera derepr(field(repr=False)).JsonFilePoolBackendescribe 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 contempfile.mkstemp()(O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW) yfchmodsobre el descriptor antes de escribir nada, y se renombra al destino. Sin esto la promesa de0600de T14 no era cierta.rayito-mcp --httppasa siempreTransportSecuritySettings(fila H-05): el SDKmcpsólo auto-activa la protección anti-DNS-rebinding para tres cadenas de host exactas, así que--host 127.0.0.2servía sin mirarHostniOrigin. Ahora el servidor exige que ambos sean elhost:puertocon el que arrancó, y un--hostcomodín (0.0.0.0,::) se rechaza con error de uso (exit 2).- Aviso único cuando
RAYITO_ACCESS_TOKENse 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
ClientErrorcrudo: botocore no cuelga la petición firmada de unClientError, pero lo que AWS devuelve repite la firma: unSignatureDoesNotMatchde S3 traeAWSAccessKeyIdyCanonicalRequest(con el valor dex-amz-security-token) enresponse["Error"], y unInvalidSignatureException/SignatureDoesNotMatchde lambda-microvms o STS mete la cadena canónica en el mensaje, que acababa en el mensaje de la excepción traducida y, porraise ... from exc, en cualquier traceback (también ellogger.debug(..., exc_info=True)de la reconexión). Ahora el plano de control y las transferencias por S3 lanzan fuera delexcepty desde unAwsErrorSummary(rayito._aws_sanitize.sanitize_aws_error) con sóloname,code, el mensaje redactado,status_code,request_id,extended_request_idyattempts(S3 sin mensaje: unEndpointConnectionErrornombra 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ámetrosX-Amz-*y los ids de clave.status_code,aws_code,retry_afteryquota_codeno 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
HOMEen S3 (m7-s3-persistence, ADR-009):S3Prefix(bucket, prefix="rayito", name=None, region=None),Sandbox.create(persist=, persist_timeout=600)(exigeexecution_role_arn; connamerestaura el checkpoint existente ensbx.last_restore, sinnamelo fija alsandbox_id),connect(persist=),sbx.persist,checkpoint_files(target=, exclude=, timeout=, on_progress=)→CheckpointResult,restore_files(source=, timeout=, on_progress=)→RestoreResult(NotFoundExceptionsin checkpoint) yreincarnate(exclude=, persist_timeout=)(checkpoint →create(persist=)con las mismas opciones →kill(); el viejo sigue vivo si elcreatefalla). NuevosCheckpointProgress,RestoreProgress,LaunchOptionsyPersistenceException(code)(permission_denied,internal,failed_precondition,unimplemented,interrupted,resource_exhausted);create(pool=)rechazapersist=. Misma superficie enAsyncSandbox.rayito.e2b.Sandbox.set_timeoutsigue siendoUnimplementedErrory su mensaje nombrareincarnate(). run_code(code, language=...)ycreate_code_context(language=...)aceptanpython,bashyjavascript(aliasjs, sin distinguir mayúsculas;m7-poly-kernels):languageselecciona el contexto por defecto de ese kernel (default-bash), que el agente crea en la primera celda, y es excluyente concontext(InvalidArgumentException). El kernelbashsólo lo trae la variante de imagenrayito-base-poly;javascriptes un nombre reservado que hoy ninguna imagen trae (AWS_API_NOTES.mdQ57). Un kernel que la imagen no trae esInvalidArgumentExceptioncongrpc_codeUNIMPLEMENTEDyrayito-base-polyen el mensaje;envspor ejecución sólo en contextos Python. El shimrayito.e2breenvíabash,javascriptyjsal core y mantieneUnimplementedErrorparar,javay 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 concreate(), asentados con una celda trivial, aparcados conpause(wait=True)y entregados portake(wait=...)conresume_microvmexplícito y el token de la plaza (singet_microvmen la toma); fallback acreate()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 conlist_microvms; recuperación desde el backend JSON (rayito.pool/1, modo0600, el mismo fichero que lee TypeScript); custodia del secreto por plaza hastatake()(SECURITY.mdT14);Sandbox.create(pool=...)/AsyncSandbox.create(pool=...)como azúcar que rechaza todo kwarg de lanzamiento. Documentación endocs/site/docs/pool.md. - Servidor MCP (
rayito.mcp, extrarayito[mcp], scriptrayito-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 endocs/site/docs/mcp.md. - CLI
rayitocomo extrarayito[cli](typer) con el entry pointrayitoypython -m rayito.cli:rayito image publish|list|prune|zip,rayito sandbox list|kill|info|logs(stream de CloudWatchYYYY/MM/DD[<versión>]<id>resuelto desdeget-microvm) yrayito doctor(diez comprobacionesOK|WARN|FAIL|SKIP: credenciales y región, imágenes gestionadas, Service Quotas frente a los defaults,iam:SimulatePrincipalPolicyorientativa, bucket, gate de tres estados de la imagen, MicroVMsRUNNING, token,Healthdel agente y la tabla de compatibilidad SDK ↔raydderayito.cli._compat(la versión de imagen, contador de builds por imagen y cuenta, sólo se informa);--launchcrea y mata un sandbox de 300 s;--jsonen todos los comandos). Ninguna salida contiene un JWE, un access token ni unrunHookPayload. probe_health/probe_health_async(unHealthsin access token sobre un canal dedicado) como base deprobe_metadata; superficie pública sin cambios.
Fixed¶
rayito image zip/copy_sidecar.py: la lista de exclusión se evalúa antes deis_file(), así que un symlink de Linux dentro de un.venvexcluido (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.pyycopy_sidecar.pyson shims derayito.cli(_publish,_prune,_artifact) con los mismos argumentos;publishexige--bucketoRAYITO_BUCKET(la biblioteca ya no trae el bucket del mantenedor) y elMakefilelo pasa con la variableBUCKETcorriendo los dos shims de AWS conuv run --project clients/python. -
Licencia MIT → Apache-2.0 (PEP 639:
license = "Apache-2.0",license-files = ["LICENSE", "NOTICE"], sin clasificadorLicense ::; la wheel llevaLicense-Expression: Apache-2.0yNOTICEen.dist-info/licenses/, comprobado porscripts/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 deHealthhastaagent_readyykernel_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);timeoutcomo vida máxima del MicroVM (tope 8 h). - Comandos (M2):
sbx.commands.runen foreground y background,on_stdout/on_stderr,stdin,list,kill,connect(pid, from_seq),send_stdin,close_stdin;CommandHandleconwait, iteración y reconexión;get_metrics()(instantánea procfs). - Ficheros (M3):
sbx.files.read(text/bytes/stream),write,write_filesen un solo stream,list(depth),exists,get_info,remove,rename,make_dir,watch_dirconWatchHandle. - Código (M4):
sbx.run_codesobre kernels Jupyter con estado,Executionconresults(mime bundles y charts de E2B),logs,errorcomo 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) yreconnect_timeout. - Metadatos por sandbox (M6):
Sandbox.create(metadata=...)viaja en elrunHookPayload(4096 caracteres junto aenvs) yraydlo devuelve enHealth:sbx.metadata,SandboxHealth.metadata,SandboxInfo.metadata(None= no leído del agente),Sandbox.get_info(sandbox_id)con la sonda deHealthsobre sandboxesRUNNINGySandbox.list(metadata=...), un filtro en cliente O(n) (unget-microvm+ un JWE + unHealthpor sandboxRUNNING) que nunca despierta un sandbox suspendido. - Shim de E2B (M6):
rayito.e2bconSandbox,AsyncSandboxy los nombres del SDK de E2B 1.x (Execution,Result,CommandHandle,SandboxInfo,SandboxQuery,SandboxPaginator,PtySize(rows, cols), las excepciones dee2b.exceptions, los charts...). Mapeatemplate,timeout(300 s),metadata,envs,request_timeoutysandbox_id; ignora conRayitoCompatWarningapi_key,domain,debug,proxyysecure=False; lanzaUnimplementedErrorparaset_timeout,upload_url/download_url, rangos deget_metrics,connection_config, kernels no Python,list(next_token=),beta_create(auto_pause=...), templates/forkyallow_internet_access=False(medido: un MicroVM sin conector de egress hereda el de la imagen y sigue saliendo a internet,AWS_API_NOTES.mdQ44). AsyncSandboxcon la misma superficie queSandboxsobregrpc.aio, incluidos metadatos y el shim.- Empaquetado: clasificadores, URLs,
py.typed, wheel comprobada (scripts/check_wheel.py,twine check), workflowrelease.ymlcon Trusted Publishing de PyPI y sitio de documentación (docs/site).
Requisitos de imagen¶
run_codenecesitarayito-base≥ 7.0 (sidecar de kernels);ptyy la reconexión traspause()necesitan ≥ 10.0 (M5); los metadatos necesitan la imagen de M6 (unraydque devuelvaHealthResponse.metadata; la aceptación de 0.1.0 corrió sobrerayito-base-e2b1.0, construida del mismo árbol que larayito-basede M6). Sobre una imagen anterior el SDK funciona ymetadatase lee vacío.
Limitaciones conocidas¶
- La vida de un sandbox no se puede extender (
set_timeoutde E2B esUnimplementedError: no existeUpdateMicrovm); 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 sandboxesRUNNINGy 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/rayitohasta 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):
- Registrar el Trusted Publisher en PyPI: proyecto
rayito, repositorioalejandro-cedeno-10/rayito, workflowrelease.yml, environmentpypi. - Crear el environment
pypien GitHub (Settings → Environments). git tag python-v0.1.0 && git push origin python-v0.1.0: el jobbuildconstruye y comprueba la wheel (contenido ytwine check) y verifica que el tag coincide conpyproject.toml; el jobpublishsube a PyPI sin token.workflow_dispatchsólo ensaya elbuild.
[0.0.1] - [0.0.5]¶
Builds internos de los hitos M1-M5, nunca publicados.
Unreleased¶
Corregido¶
rayito setuppone al día la pila de IAM de una versión anterior. Antes reutilizabarayito-m0-iamsi suArtifactBucketcoincidía, aunque se hubiera desplegado con uninfra/iam.yamlmá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ámetroTemplateFingerprint(el sha256 de la plantilla empaquetada, el mismo en los dos SDK) y su salida;setuplo manda al crear o actualizar y planificaupdatesi la pila no lo tiene o lleva otro, con el texto «actualizar a la plantilla infra/iam.yaml de este SDK» ypreviousBucket: nullen--jsoncuando 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 etiquetarayito:sdk-version) nunca vuelve a la plantilla de este SDK: con el mismo bucket se reutiliza («… más nuevo que este SDK: se conserva»,stackSdkVersionen--json) y, si el plan le cambia el bucket, falla con el error nuevostack_newer(salida 2). Al actualizar la pila,setupvuelve a mandar las etiquetas que ya tenía, con las de--tagpor encima, porqueUpdateStacksustituye la lista entera.StackStatusganatags(las deDescribeStacks). Una pila creada con la plantilla de 0.6.1 o anterior se actualiza sin revertirse: la plantilla gana el parámetroLegacyCallerPolicy, que conserva la descripción original deCallerPolicy(IAM no la cambia y CloudFormation sustituía la política, que chocaba con su propio nombre fijo y dejaba la pila enUPDATE_ROLLBACK_COMPLETE), ysetuplo pone atrueen una pila sin la salidaSandboxLauncherPolicyArn.- Un
--token-fileque dejó unrayito agent deployfallido 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-failurese conserva, porque es el único acceso al sandbox que se queda, y un fichero de una ejecución anterior nunca se toca. rayito setup --jsoninforma la versión de cada imagen publicada en su paso. Trasapply, los pasosbaseImageycapsImageconpublishllevan enimageVersionla versión recién publicada (antesnull, aunqueresultsí la tenía). En--dry-runy si la preparación falla, siguen ennull.- 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,s3transferyurllib3a WARNING, comohttpxyhttpcoreen 0.17.0, y descarta los registros por debajo de INFO debotocore.endpoint,botocore.parsers,botocore.auth,botocore.regions,boto3.resources.action,boto3.resources.collection,boto3.dynamodb.table,s3transfer.tasksys3transfer.futuresaunque el código del agente vuelva a bajar el nivel. El SDK TypeScript no pone ningúnloggeren sus clientes del AWS SDK v3. - Una flota dedicada de un solo VM vuelve a su sesión. Con
sessions: "dedicated"ymaxVms: 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 enCapacityErrortras 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
setTimeoutde 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 hastarequestTimeoutMs. - 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, vectoresrestoreSecondsentestdata/agent-fleet/lifetime.json).
Seguridad¶
vitest4.1.12 (dependencia de desarrollo;@vitest/mockerGHSA-qxg6-89mg-rqv8 y GHSA-xp26-3454-mjgp). No cambia el paquete publicado.
0.17.0 - 2026-10-23¶
Añadido¶
rayito setup --caps-imagepublica tambiénrayito-base-caps, la base que usarayito agent deployen MicroVM y que antes había que publicar a mano. Es un cuarto paso del plan (capsImage) que sale del mismorayito-image.zip(una sola descarga) conadditionalOsCapabilities=["ALL"]; el plan y--dry-runmuestran 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 comoskipy no se lee ni se publica esa imagen. Sirayito agent deployen MicroVM no encuentrarayito-base-caps, el error piderayito setup --caps-image. El documento de--jsonganacapsImageArnycapsImageVersionenresult.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()yrayito agent deployguardan, 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ámetroSecureStringestándar de Parameter Store (/rayito/agents/<nombre>/recipe), con nombres de secretos y nunca valores.AgentFleet.connect(nombre)sinprojectla lee y puede crear VMs iguales;useRecipe: falsela ignora;@aws-sdk/client-ssmes 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-recipela apagan. Un fallo al guardarla es un aviso (recipe_access_denied,recipe_not_saved,recipe_too_large), nunca un despliegue fallido. Nueva sentenciaAgentRecipe:CallerPolicyla guarda, la lee y la borra;SandboxLauncherPolicysólo la lee y la borra (escribirla equivale a poder desplegar) eImagePublisherPolicysólo la guarda (AgentRecipeWrite). FleetStatus.scalingSource(explicit,project,recipeodefault).AgentFleet.stop({ keepRecipe })yrayito agent stop --keep-recipe: por defectostop()borra la receta que leyó o guardó.AgentDeployOptions.scaleInAfterMsyrayito agent deploy --scale-in-after SECONDS.- Renovación opcional de la clave de corta duración de Bedrock
(
renewGatewayKeysenAgentFleet,AgentFleet.connectyAgentDeployOptions;--renew-gateway-keys), apagada por defecto, con los peers opcionales@aws/bedrock-token-generator1.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()yapplyGatewaysen el pool.
Cambiado¶
- Incompatible con 0.16:
rayito agent deploy --envrechaza un nombre que parece un secreto, también*_SECRETySECRET, los que tienen un segmentoPASS,PASSWD,PAT,CREDENTIALoCREDENTIALS, y los que llevanAUTHoBEARERcomo último segmento o junto aTOKEN,KEYoHEADER(AUTH,X_AUTH,AUTH_HEADER; sin distinguir mayúsculas; antes pasaban).AUTH_MODE,AUTH_URLoAUTHOR_NAMEsiguen 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 deenvdel proyecto ([env]derayito-agent.tomlo la biblioteca) se guardan tal cual en la receta, así que no pongas enenvnada secreto. rayito setup --caps-imagepublicarayito-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:
SandboxPooladoptaba al arrancar todas las plazas del backend. Con el fichero de estado por defecto derayito 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 sinmaxLifetimeMs/onTimeout, así queraydrechazaba elsetTimeoutque 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 cononTimeout: "kill"ymaxLifetimeMs = 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 ascaleInAfterMs(mientras quede otra no se acorta), siempre por debajo del tope que imponerayd(maxLifetimeMs - 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 por elloggery no se reintenta.PoolConfigganamaxLifetimeMsyonTimeoutopcionales (sólo"kill"). Vectores entestdata/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 loggershttpxyhttpcore, que a nivel INFO escriben la URL de cada petición con su consulta, arrancan en WARNING enserve(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
AttributeErrortras responder400a una línea de petición mal formada; las invocaciones siguientes nunca se vieron afectadas. --create-secretbusca la clave por el preset de la pasarela: una pasarela[gateways.model] preset = "bedrock"leeAWS_BEARER_TOKEN_BEDROCKen vez de buscar una variable por el nombremodel. Conpreset = "custom"sigue mirando el nombre de la pasarela, como en 0.16.--create-secretya no duplica el prefijorayito/: la detección escribía el secreto de la pasarela comorayito/agents/<proyecto>/<proveedor>y, como la pasarela lo lee bajorayito/, el secreto acababa enrayito/rayito/agents/.... Ahora escribeagents/<proyecto>/<proveedor>y el secreto queda enrayito/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; unrayito-agent.tomlcon un nombre propio no cambia.- OpenCode sin fichero de proyecto usa
AGENTS.mdy los permisos deopencode.json(ADR-034 D8). Antes sólorayito-agent.tomllos aplicaba. Ahora la detección lleva elAGENTS.mdde la raíz aagent.instructions_file, y el bloquepermissiondeopencode.jsonuopencode.jsonc(sin comentarios ni comas finales) aagent.permissions.askse aplica comodeny, porque la ejecución es desatendida, y una clave que no se puede aplicar se ignora. Los dos casos avisan conopencode_permission_ignored. Lo que dicerayito-agent.tomlgana, 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 oescalado desconocido: sin proyecto ni receta.AgentFleet.connect(nombre)sinprojectya no falla si Parameter Store no responde (sin endpoint de VPC para SSM, limitación de tasa, un error de KMS): es el avisorecipe_unreadabley la flota de 0.16, yrayito 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 conrenewGatewayKeys: trueexplícito. Si la receta lo pedía y no lo pasas, la flota no renueva y avisarecipe_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). renewGatewayKeysno renueva un secreto compartido: si el secreto de la pasarelabedrocklo lee también una pasarela cuyo upstream no es Bedrock, la opción esInvalidArgumentError(nombra las pasarelas, nunca el secreto). Sólo cuenta como Bedrock un hostbedrock-runtime.<región>.amazonaws.comcon una región de AWS válida; un host comobedrock-runtime.evil.s3.amazonaws.comya no se toma por Bedrock.- Un nombre de flota
.,..o vacío ya no da una ruta de receta (recipe_not_saved). rayito setupinformaartifactUploadedtambién cuando sólo publicarayito-base-caps(una cuenta que viene de 0.16).- La ayuda de
--scale-in-aftersale de la constante del SDK y dice que, sin la bandera, manda el[scaling]del proyecto. rayito agent deploycon--min-vms 0imprimíasandbox_id=undefined(y--jsonperdía la clavesandboxId): una flota sin VMs con servicio no imprimesandbox_idniurly--jsonlos da comonull, 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
ResourceExistsExceptionmientras el anterior seguía pendiente de borrado):secretName(scope, kind)devuelverayito-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 variableid_env,base_url_envyapi_key_env(idEnv,baseUrlEnv,apiKeyEnvenAgentModelOptions; nunca valores), y en MicroVMs Rayito los apunta a la pasarela con el marcador como clave. La detección vebase_url=en el constructor del cliente (openaipasa aopenai-compatible) y los nombres de variable del modelo, y avisa conmodel_id_from_code. El id sigue siendo obligatorio con las pasarelasbedrockygeminiy con los runtimes que construyen el modelo; con unentrypoint,ctx.modelesundefinedy la telemetría omitegen_ai.request.model. --model-id,--model-providery--model-base-urlenrayito agent detectydeploy(DetectOverrides.modelId,modelProvider,modelBaseUrl).--external-modelen AgentCore (AgentCoreOptions.externalModel, propexternalModeldeRayitoAgent): 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íneaexternalModel.- Entrada estructurada y selección de grafo (ADR-033 D3): un grafo de
LangGraph cuyo esquema de entrada no es de mensajes (por ejemplo,
InputStateconticket_id) se invoca coninvoke({ input })ystream({ input })enAgentDeployment,AgentFleetyAgentCoreFleet(sobrecargas: el prompt de siempre o{ input }; tipos nuevosAgentInputRequest,AgentInvocationyJsonObject), y devuelveAgentResult.outputyDone.output. El host lee el esquema conget_input_jsonschema()(getInputJsonSchemaen LangGraph.js), comprueba elinputy pasa la sesión comothread_id; un grafo de mensajes sigue igual. Fallosprotocol_errorconinput_required,invalid_inputuoutput_not_serializable, con su propio mensaje, yInvalidArgumentErrorcon una pista de redespliegue si el host desplegado es anterior. Con varios grafos enlanggraph.json,--graph,[runtime] graphyDetectOverrides.grapheligen uno; sin elegir y sin uno llamadoagent, el error lista los nombres.rayito agent invokeacepta--inputy--input-file(-lee stdin) en lugar dePROMPT. Límite nuevoAGENT_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 })conSecretEnvRefySecretEnvSource, y--secret-env NOMBRE=<secreto>[#clave]y--secret-env-from <secreto>[,prefix=P][,keys=A|B]enrayito agent deploy. En AgentCore el runtime sólo recibeRAYITO_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 consecret_env_*; los secretos se suman aReadableSecretArns(la plantilla no cambia) y el plan lo dice con la líneasecretEnvysecretEnvNames. 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 JSONrayito/agents/<nombre>/envcon los valores de un.envy lo usa comosecret_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.exampleque no reciben valor.- Protocolo
langgraph-api(experimental) para servir unlanggraph.jsonconlanggraph dev:new HttpApp({ protocol: "langgraph-api", assistantId })(y[runtime.app] protocol = "langgraph-api"conassistant_id) hace que el host traduzca cada invocación aPOST /threads/{thread_id}/runs/waitdel LangGraph Platform API, con el hilo derivado de la sesión (la misma sesión sigue el mismo hilo), salud en/ok, el texto deresponse_fieldo del último mensaje y el uso de suusage_metadata. Uninterruptdel grafo esAgentError(protocol_error)condetail_codegraph_interrupted.rayito agent detectydeploycon--protocol langgraph-apisirven así un proyecto conlanggraph.json(kind = "custom", el launcher delanggraph devy el grafo elegido comoassistant_id), y sin la opción la detección no cambia.langgraph deves un servidor de desarrollo en memoria; Rayito no instalalanggraph-cli[inmem](el paquetelanggraph-apiusa la licencia Elastic-2.0). Sin streaming nilanggraph upen esta versión.HttpApp.servedHealthPathyservedInvokePathdicen las rutas a las que llama el host. Coninvoke({ input })el objeto llega tal cual como entrada del grafo y, si el estado final no tiene mensajes, vuelve comoAgentResult.output;--graphelige elassistantId.
Cambiado¶
AgentModel.idpasa destringastring | undefined(AgentModel<Id>: un modelo construido con id sigue siendostring), y con élAgentSpec.effectiveSmallModel; sólo cambia el tipo que se lee, ninguna llamada existente. Un proyecto con id se comporta igual, pero constricttscavisa (TS2322/TS18048) donde el código leeproject.spec.model.idcomostring; estréchalo (if (id !== undefined)).model_requiredse lanza por fin: AgentCore con un proveedorbedrocksin[model] idni--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_requiredya no lleva la pista «(bedrock_models en el SDK)», y unlanggraph.jsoncon varios grafos y ninguno llamadoagentda el texto deambiguous_graph(los nombres de los grafos y--graph) en vez del deambiguous_entrypoint, con el mismo códigoambiguousenruntime.entrypoint. - La detección sólo convierte
openaienopenai-compatiblepor unbaseURLpropio en el constructor del cliente (también dentro deconfiguration;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 avisocustom_base_url). -
Un prompt a un grafo con esquema de entrada propio sin claves
requiredsigue el camino de mensajes, como en 0.15;input_requiredqueda para los grafos que declaran claves obligatorias. -
Un
400a una petición coninputse explica con el cuerpo del host: elinvalid_requestde 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. Coninput, el SDK mide el cuerpo entero de la petición contraAGENT_SERVICE_MAX_REQUEST_BYTES, no sólo elinput, y sólo acepta objetos planos (unDate, unMapo una instancia de clase sonInvalidArgumentError, como undatetimeen Python); con un prompt no cambia nada. new AgentProject(...)con un runtime que construye el modelo (opencode,hermes,deepagentsostrandssinentrypoint) y sinAgentModel.idfalla al construirse, antes de cualquier llamada o de crear el secreto de--create-secrets-from.rayito agent deploy --create-secrets-frompregunta 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 elinputsólo con claves propias (toStringohasOwnPropertyno cuentan como claves) y guarda en la sesión losmessagesde uninputa un grafo de mensajes sin checkpointer.
Seguridad¶
- Con
--external-modelel 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_envnunca 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, sinkms:Decrypt), y un test falla si se amplía. El agente y lo que lance pueden leer su entorno: la guía,security.mdySECURITY.mdlo dicen. - El fichero de
--create-secrets-fromse 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-envsin=o con un nombre que no es de variable da el error ensecret_env, sin repetir lo escrito. En MicroVMs el host recibe tambiénRAYITO_SECRET_ENV_NAMES(sólo nombres) y redacta los valores desecret_enven el diagnóstico de arranque igual que en AgentCore. - Una clave
__proto__de un secreto JSON o desecretEnves una variable más, como en Python, en vez de perderse. - El host lee los valores de
secret_envpor 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()yrayito agent deploy ./mi-agente --target agentcoredespliegan un agente de cero configuración sin Docker local: la imagen arm64 se construye en CodeBuild a partir del export deagentcore, se sube a ECR (repositorio, proyecto y roles en la pilarayito-agentcore-<nombre>) y el runtime se crea o se actualiza sin duplicarlo, con MMDSv2 siempre exigido.rayito agent deploy --target agentcoremuestra el plan, la región, el coste aproximado y la seguridad, y pide confirmación (--yes,--dry-run,--json,--tag K=Vpara las organizaciones que exigen etiquetas).AgentCoreFleet(connect(),invoke(),stream(),status(),stopSession(),delete()) tiene la misma API y las mismas sesiones queAgentFleet;rayito agent invoke|status|stop --target agentcorey las órdenes nuevasrayito agent listyrayito agent deletelo usan. Las opciones de MicroVMs que AgentCore no admite (min_vms,warm_vms,--memory...) danoption_not_supporteden 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 deagentcore.bedrockModelsy leer los secretos deagentcore.readableSecrets. Cuatro clientes de AWS nuevos como peers opcionales (@aws-sdk/client-bedrock-agentcore,-bedrock-agentcore-control,-codebuildy-ecr); el destino por defecto sigue siendo la flota de MicroVMs y no los carga. - El export
agentcorelleva el argumento de buildAGENT_BASE_IMAGEy, para Vercel AI SDK, el ayudante que lee las credenciales del rol del runtime (botocorefijado por hash); elDockerfiley el README del exportdockerno cambian. - OpenCode obtiene el rol del runtime con un perfil
credential_process: el exportagentcorede un agente OpenCode añade el ayudante de botocore, unAWS_CONFIG_FILEcon un perfil cuyocredential_processlo ejecuta (conExpiration, para que el SDK renueve) yAWS_CONFIG_FILEyAWS_PROFILEen la imagen; sin ellos OpenCode no arranca su proveedor de Bedrock. MicroVM, el exportdockery 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, comocdk bootstrap. Lee la cuenta, muestra el plan y el coste aproximado, pide confirmación (--yessin terminal o con--json;--dry-runsólo lee) y termina conrayito doctor. Crea o actualiza la pila de IAMrayito-m0-iam(sóloArtifactBucket), reutiliza el bucket de--bucket,RAYITO_BUCKET, la pila o el bootstrap de CDK y publicarayito-basedesde elrayito-image.zipde la releaserayd-v<versión>, comprobado contra suSHA256SUMS, 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 setupcrea el bucket de artefactos con la pilarayito-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--bucketoRAYITO_BUCKETque no existe se crea con ese nombre.rayito agent deployydeployAgent()encuentran el bucket que creórayito setup(la salidaBucketNamederayito-m0-artifacts, sólo lectura) cuando no hayRAYITO_BUCKETni bucket del bootstrap de CDK.- Constructos de AWS CDK en
rayito/cdk(peers opcionalesaws-cdk-lib^2.261.0yconstructs^10.5.0).RayitoAgentdespliega concdk deployel mismo agente quedeployAgent(ruta, { target: "agentcore" }), con la misma plantillainfra/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()), yCustom::RayitoRuntimeMmdsexige MMDSv2.RayitoMicrovmImagepublica una imagen de Lambda MicroVMs conrayd(y sus tamaños) desdeawait MicrovmImageArtifact.downloadRelease()(comprobado contraSHA256SUMS, con caché para síntesis sin red) ofromFile(), con su propio rol de build y la misma configuración querayito image publish.cdk synthno llama a AWS; la región del stack debe ser concreta. Errores propios enRayitoCdkError; los del plan siguen siendoAgentCoreError. Ningún otro punto de entrada (rayito,rayito/e2b, la CLI, el servidor MCP) importaaws-cdk-lib. Guía:guias/desplegar-con-cdk.md. - La CLI respeta lo que gestiona CloudFormation: un runtime con la marca
rayito.agentcore/1+cdkse lista, se consulta y se invoca como cualquier otro, perorayito agent deploy,rayito agent deleteydeployAgent(ruta, { target: "agentcore" })lo rechazan conname_conflict, yrayito image publishrechaza una imagen con la etiquetarayito:managed-by=rayito-cdk. Los runtimes con la marcarayito.agentcore/1no 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 puentenode_bridge.mjsllevan ahora el soporte de AgentCore (id de sesión y credenciales del rol). Como los dos forman parte del contexto de build, el primerdeploy_agenttras 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. ElDockerfiley el README del exportdockerno cambian; sus ficheros deagent_host/y, en Node,node_bridge.mjssí. - Los errores de
Sandbox.create()(ResourceNotFoundExceptiondeRunMicrovm) y dedeployAgent()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, ejecutarayito 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 deAgentTemplatede 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 condeployAgent: 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 comocustomcon su adaptador. Strands instala conuvcuando el proyecto lo usa, y el host recibequeue_maxyqueue_timeout_secondsenhost.json. La detección de una app HTTP que lee su puerto dePORTahora 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):ServeOptionssumaqueueMaxyqueueTimeoutMs(la cola del host; con 0, el valor por defecto, la llamada que no cabe recibe 429 como hasta ahora) ymaxConcurrencyqueda acotada por las conexiones que admite el MicroVM dememoryMib.AgentProjectsumascaling(ScalingOptions:minVms,maxVms,warmVms, sesionessharedodedicatedyscaleInAfterMs),env(sin secretos),networkconallowOutydenyOut,region,bucket,baseImageVersion,language,frameworkyversions(RuntimeVersions). El ficherorayito-agent.tomladmite las tablas[scaling],[env]y[deploy], las clavesallow_out,deny_out,queue_maxyqueue_timeout_seconds, y en[runtime]las delanguage,framework,python_versionynode_version. Todo es aditivo y los valores por defecto son los de un solo VM. Los errores de lectura suman los códigosambiguous,not_detectedyunsupported. 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 paradeploy_agent. - Host y adaptadores de instalación del agente:
el host del agente sirve una cola FIFO acotada (
queue_maxyqueue_timeout_secondsdehost.json; sin cola por defecto: la llamada que no cabe recibe 429, como hasta ahora) y supervisa las apps de larga vida (http-appy Hermes): una app que muere se relanza con espera creciente,/pingesHealthyBusyy las invocaciones reciben503conRetry-Aftermientras tanto, y agotado el presupuesto el marcador de fallo llevaapp_crashloopcon la cola saneada de su salida de error.new HttpApp({ protocol: "json" })traduce/invocationsal contrato propio de una app (requestFieldyresponseField) y reenvía sus demás rutas;agentcoresigue siendo el valor por defecto de unHttpAppexplícito. Un único puente de Node (node_bridge.mjs, drivernode-callable) ejecuta agentes de Strands (el antiguostrands-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") connew CustomRuntime({ entrypoint: "ruta.ts#exportacion", packageManager }). YCustomRuntimeyStrandsRuntime({ language: "typescript" })aceptanpackageManager(pip,uv,npm,pnpm,yarn),pythonVersionynodeVersion: el plan de instalación (install-plan.ts) baja CPython y Node con sha256 fijado enlimits.jsony 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()yAgentFleet: despliega el agente que ya tienes con una llamada (agent-zero-config):deployAgent(ruta)(ydeploy_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 deRAYITO_BUCKETo el de activos del bootstrap de CDK; nunca crea un bucket) y el access token, construye la imagen sólo si cambió y devuelve unAgentFleet: uno o varios MicroVMs que sirven el agente.ScalingOptionsfijaminVms,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, elsessionIddel resultado es un handlers1.<sandbox>.<sesión>que enruta derecho a su VM sin servidor propio; una flota llena espera en una cola cliente y, al agotarse, lanzaCapacityErrorconcode: "agent_capacity"; un VM que desaparece esSandboxErrorconcode: "agent_vm_lost". Los VMs por encima deminVmsse terminan solos (setTimeout), un VM que deja de contestar se sustituye yAgentFleet.connect()reconstruye la flota en otro proceso.planDeploy()calcula el plan y el coste de lista sin llamar a AWS.AgentDeployOptions,DetectOverridesyPoolConfig.accessTokenson aditivos:AgentProject.deploy()no cambia. Nada que cueste se crea sin pedirlo: un secreto sólo concreateSecretFromEnv: true, la imagen base sólo con un publicador. -
rayito agent detectyrayito 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-artifactcon--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-fileniRAYITO_ACCESS_TOKEN, y los logs del build salen resumidos.invoke,statusystopaceptan 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 (losRUNNINGcon las etiquetas de un despliegue de Rayito, sondeados con unHealthsin token), y si la imagen tiene unRUNNINGque no lo es, tampoco se cuentan los suspendidos, que no se pueden sondear sin reanudarlos:stopnunca termina un sandbox ajeno. La salida de 0.13 deagent deployse conserva (mismas claves, con otras añadidas) y--filesigue 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),HermesRuntimeyCustomRuntimeconHttpAppsuman runtimes aAgentTemplate,AgentyAgentPool, y el tipo de proyectolanggraphcubre 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 nuevosInstallableRuntimeyServableRuntime(conHostConfig,GatewayWiring/DirectWiringeInstallTarget) describen cómo se instala y cómo se sirve un runtime sin tocar el puertoAgentRuntime, así que los runtimes de 0.13 siguen funcionando.AgentTemplateacepta nombres del registro u objetos runtime. -
AgentProjectyAgentDeployment: del ficherorayito-agent.tomla un agente desplegado o exportado:AgentProjectconstruye la imagen, ejecuta una vez, despliega un servicio (rayito.agent-service/1, el contrato HTTP de AgentCore:GET /pingyPOST /invocations, con SSE) o exporta un contexto Docker/AgentCore (arm64, puerto 8080) sin llamar a AWS.AgentDeploymentinvoca, consultaDeploymentStatusy 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 unrayito-agent.toml; sin él, el resto del SDK no cambia. -
AgentProjectyrayito agent: describe un agente en un fichero y construye, ejecuta, despliega o exporta (agent-runtimes-byo): la CLI sumarayito agent init,build,run,deploy,export,invoke,statusystop, conchas sobreAgentProjectyAgentDeploymentcon--json, los valores por defecto del SDK y salida 2 para un fichero de proyecto inválido.initescriberayito-agent.tomly un andamiaje por runtime (Strands, Strands harness, Hermes, LangGraph, deepagents, OpenCode o propio) sin sobrescribir nada sin--force;exportyinitno llaman a AWS, ydeploynunca imprime las cabeceras de acceso. El andamiaje deinitse comparte con la CLI de Python entestdata/cli/agent-scaffold/. Guía nueva, Despliega tu agente, y un adaptador de StrandsSandboxsobre Rayito endocs/examples/strands_rayito_sandbox.py. -
AgentProject.deploy()acepta el access token del sandbox yrayito agent deploy|invoke|status|stopse pueden encadenar entre procesos:deploy({ accessToken })lo pasa aSandbox.create(), y la CLI guarda el token en--token-file(o usaRAYITO_ACCESS_TOKEN) antes de crear el sandbox, igual quesandbox create --detach. Sin ninguno de los dos,deploysale con 2 sin crear nada: antes dejaba un MicroVM que niinvoke, nistatusnistoppodían volver a abrir.invokeystatusleen el token con--token-filey sin él salen con 2;stopya no lo necesita (terminate-microvmpor id). -
AgentDeployment.pause(),resume()yaccessToken, ydeploy({ keepOnFailure })(agent-runtimes-dx):AgentDeploymentsuspende y reanuda el sandbox del despliegue (Sandbox.pause()yresume()) y expone el access token con el que se creó, también el que se generó cuando no se pasóaccessToken, para reconectarse conAgentDeployment.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 unaccessTokenque el llamador conozca y anota el id del sandbox en el error.rayito agent deploy --keep-on-failurehace lo mismo. -
AgentResult.usageReportedyDone.usageReported:falsecuando el agente no informó de tokens (unaHttpAppcuya respuesta no llevausage, un comando conprotocol: "text"o una función que no lo devuelve).usagevale entonces cero porque se desconoce, no porque no se gastara nada, y la CLI imprimetokens=desconocidoyusageReporteden--json. UnaHttpAppinforma deusagesi su respuesta lo trae (input/output,input_tokens/output_tokensoprompt_tokens/completion_tokens). -
HttpApp.hidesOwnRoutesy el aviso derayito agent deploy: conprotocol: "agentcore"u"openai-chat"el despliegue sólo expone/pingy/invocations; si tuhealthPathoinvokePathson otros, esas rutas no se alcanzan desde fuera (404).rayito agent deploylo avisa y apunta aprotocol: "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 listalist-managed-microvm-image-versionsy 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 deployahora despliega una flota, con más llamadas a AWS (agent-zero-config): el mismo comando con un--filede 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 (HeadBucketyGetCallerIdentity, y el de activos del bootstrap de CDK si no pasas--bucketniRAYITO_BUCKET), construye la imagen del proyecto si su contenido cambió (excluye por defecto.git,.env*,.venv,venv,node_modules,__pycache__,*.pycy.rayito, así que un proyecto que los subía a la imagen la reconstruye) y devuelve una flota. Las credenciales necesitan por tantosts:GetCallerIdentity,s3:ListBucketsobre el bucket,lambda-microvms:GetMicrovmImageysecretsmanager:DescribeSecret, además de las de 0.13. Con--no-buildy sin bucket reutiliza la imagen existente sin construir nada. -
ServeOptions({ maxConcurrency })queda acotada por la memoria de la VM: un MicroVM dememoryMibadmite 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 conInvalidArgumentError: bajamaxConcurrencyo subememoryMib. -
AgentTemplate.runtimesyAgentTemplateOptions.runtimespasan dereadonly AgentTemplateRuntime[]areadonly (string | AgentRuntime)[]: quien sólo lee la propiedad comostringdebe tratar también objetos. -
CustomRuntimecorre tu app y tu comando desde el código copiado, con su venv primero en elPATH(agent-runtimes-dx): el directorio de trabajo de unaHttpAppes el delsourcecopiado (/opt/agents/app) y va el primero en suPYTHONPATH, así quepython -m paquete.modulofunciona; conrequirements,pythonypipson los del venv, y sin élpythones el intérprete de la imagen (que sólo traepython3). Loscommandde unCustomRuntimereciben el mismoPATHyPYTHONPATH, y el directorio de trabajo de uncommandno 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_reportedenPOST /invocationsy dejausagesin valor en elstep_finishedde un comando conprotocol: "text"o de una función sinusage. Un cliente de 0.13 que no conoce el campo sigue leyendousage. -
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 unaHttpApp, el final de su salida de error (acotado aAGENT_STARTUP_DIAGNOSTICS_MAX_BYTES, sin valores de entorno ni credenciales);deploy()lo incluye en laSandboxExceptionen vez del nombre de clase opacoAgentFailure. -
rayito agent init --runtime customescribe una app HTTP ylanggraphun grafo de LangGraph a secas:customescribe[runtime.app](conprotocol = "raw", sus rutas/healthe/invoke),requirements,instally unserve.pymínimo;langgraph, uncreate_react_agenten vez decreate_deep_agent. El runtimelanggraphtraduce además el nodoagentdecreate_react_agent(antes sólomodel), 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
0600con--chmod), la imagen de OpenCode instalacurlantes 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 startque muere dejaba al servidor huérfano ocupando el puerto y el relanzado nunca quedaba listo. excludede unAgentProject/AgentTemplate({ appExclude })filtra antes de leer: un.venvexcluido se leía entero y luego se descartaba (y sucacert.pemdisparaba el aviso de ficheros con secretos). Ahora los patrones deexcludese suman a los de.dockerignorey filtran antes de leer ningún fichero.
Obsoleto¶
AgentTemplateRuntime: usastring | 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 yoptional-features.mdllevan pestañas Python y TypeScript, yoptional-features.mdsuma una fila pararayito-mcp. La API de TypeScript sigue siendo sólo asíncrona (ADR-027, D8). -
CLI
rayitoen TypeScript, fases 2, 3 y 5 (ADR-027):doctor(las diez comprobaciones y--efs-vpc-id, con@aws-sdk/client-iamcomo peer opcional para la simulación de IAM),template build|status|logs,agent template build,events deploy|status|destroy|listyevents webhook add|list|remove,domain deploy|status|destroy,dashboard cloudwatcheimage 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 dedoctor, los dashboards de CloudWatch, los contratos puros deimage(nombres, configuración deseada, planes de poda, entradas del zip) y los casos de órdenes por pila se comparten con Python entestdata/. -
CLI
rayitoen TypeScript, fase 4 (ADR-027):sandbox connectysandbox createsin--detachabren 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 ycreatetermina el sandbox pase lo que pase), ysandbox proxyexpone un puerto del guest en127.0.0.1con las mismas garantías que la CLI de Python:Host(421) yOrigin(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-mcpen TypeScript (ADR-027, fase 6): el paquete declara el ejecutablerayito-mcp(npx rayito-mcp;dist/mcp.mjs, sólo ESM, sin tocarexportsni importarse desdeindex) 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 variablesRAYITO_TEMPLATEyRAYITO_MCP_*y un sandbox por proceso creado en la primera herramienta que lo necesita y terminado al salir. stdio por defecto y--httppara streamable HTTP en loopback conHostyOriginexactos.@modelcontextprotocol/server(2.x) yzodson peers opcionales: sin ellosrayitosigue funcionando y el ejecutable explica la ordennpx -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
rayitoen TypeScript, fase 1 (ADR-027): el paquete declara el ejecutablerayito(npx rayito ...;dist/cli.mjs, sólo ESM, sin tocarexportsni importarse desdeindex) con las opciones globales--profile,--region,--json,--verbosey--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 | destroyeimage list | sizes. Salida y documentos--jsonidénticos a los de Python (tabla derichincluida), comprobados con vectores y casos detestdata/cli/que ejecutan las dos CLI. Las demás órdenes (doctor,template,events,domain,dashboard,sandbox connect|proxy,createsin--detacheimage 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 dashboardsirve en127.0.0.1la misma SPA y el mismo contrato/api/v1querayito dashboardde Python, con las mismas defensas (Host/Origin, token de arranque de un solo uso, cookieHttpOnly,Sec-Fetch-Site, CSRF, sólo lectura por defecto, CSP sinunsafe-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 adist/dashboard-static/ypack:checkfalla si el tarball difiere byte a byte. Nuevos vectores compartidos con Python entestdata/dashboard/(api-v1-cases.json,guard-vectors.json,security-headers.json,theme-vectors.json). Dependencia de desarrollo nueva:ajv(valida las respuestas contraapi-v1.schema.json). -
Pruebas locales de la CLI, el servidor MCP y el panel: los escenarios de
tests/sharedcorren tanto contra AWS real como en el e2e local (Docker + Floci), ymake local-e2econstruyerayito-mcppara probarlo por stdio.
Corregido¶
-
rayito doctor: números de cuota como en Python: el resumen humano dequotasimprime los valores conformat(value, "g")de Python (1e+06en vez de1000000), con vectoresgeneralTextcompartidos entestdata/cli/format-vectors.json. -
El reuso de una versión de imagen comparaba objetos por el orden de sus claves: AWS devuelve
hookscon otro orden que el que se envía, así queTemplate.build()reconstruía una imagen idéntica. Ahora la igualdad es estructural y las variables de entorno (que la lista no devuelve) se confirman conGetMicrovmImageVersion, como en Python. -
La CLI escribe los
floatde Python como Python:rayito --json sandbox metrics(cpu_used_pct), el JSON dedoctor(el valor de una cuota y su mínimo) y las duraciones deimage publish|prune|zipllevan su parte decimal (0.0,10.0) en vez de0y10, 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,codeyhardening, espejos de los bloques M1 a M4 de Python) y ampliaciones dem9-e2b,poly,m9-transfer,m9-timeoutym9-egress. Sólo tests: no cambia el SDK ni su API. SinRAYITO_E2E=1se omiten sin llamar a AWS. -
Panel: parámetro
AuthModedel componentedashboard(aws-iampone la API tras una Function URL con firma SigV4; por defectocognito-jwt), espejo del de Python con los mismos textos de coste. -
e2e de sólo lectura de
Quotas,UsageCost(el coste facturado sólo conRAYITO_E2E_BILLED_COST=1) yFleet. -
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-roleyquota-alertsson idénticos a los de Python (comprobado con un fichero de referencia común), con las cifras delimits.ts. -
Panel, fase 3 (ADR-026): componentes de
OptionalStacksdashboard-cloudwatch,dashboard-read-roleyquota-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 enlimits.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 deOptionalStacks(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 enlimits.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) yFleet(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 conUsageCost.billed()y las acciones exigenallowActions: true. Nueva excepciónDashboardErrory constantes nuevas enlimits.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()yevents()son de pago;resumevuelve a facturar cómputo) con el mismo texto en ambos SDK. Guía: Panel de gestión. Template.dnfInstall()yTemplate.npmInstall(packages?, { g }). Atajos del DSL para instalar paquetes del sistema condnf(sin paquetes débiles) y paquetes Node connpm; los nombres se escapan para el shell y se rechazan las listas vacías y los nombres que empiezan por-.dnfInstallynpmInstall({ g: true })corren como root aunque unsetUser()previo dejara otro usuario.npmInstallfalla con el código 127 y un mensaje claro si la imagen no tiene Node.js.aptInstallsigue lanzandoUnimplementedError, ahora apuntando adnfInstall.SandboxHealth.imdsBlocked:truecuandoraydinstaló y verificó el bloqueo de IMDS para los uids del sandbox (imagen con capabilities), comoHealth.imds_blockedde Python;falseen la imagen por defecto y en un agente anterior a M6.sbx.metadata: losmetadatadecreate({ metadata })tal como los devolvió el últimoHealth(copia congelada), comoSandbox.metadatade Python.rayito/e2bexporta los tipos de gráficos (Chart,LineChart,ScatterChart,BarChart,PieChart,BoxAndWhiskerChart,SuperChart,PointData,BarData,PieData,BoxAndWhiskerData),ChartType,ScaleType,ProcessInfo,WriteEntryyWatchHandle, los mismos nombres querayito.e2ben Python.
Cambiado¶
-
La variante de imagen se reconoce con un catálogo cerrado (
parity-image-variants):resolveImageVariantsólo interpretarayito-<variante>[-<talla>]con la variante enbase,base-caps,base-caps-efs,base-polyybase-slim. Una imagen propia que empieza porrayito-(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 aHealth.features, como ya ocurría con un ARN.volumesexigerayito-base-caps-efscuando la variante se conoce. -
Componente
dashboard-cloudwatch:DashboardNamevacío (el valor por defecto) usa el nombre de la pila en lugar del fijorayito; una pila ya desplegada conserva su nombre.
Corregido¶
- Componente
dashboard-cloudwatch: conIncludeOtlpoIncludeQuotalos widgets añadidos se creaban con la región literal${AWS::Region}; la plantilla (la misma que usa Python) resuelve ahora la región conFn::JoinyRef AWS::Region.
0.11.0 - 2026-10-13¶
Cambiado¶
AgentTemplatearranca antes (ai-agent-first-start). Dos cambios en la imagen que construye, medidos en AWS real (AWS_API_NOTES.mdQ155 y Q156):kernelWarmup: falsepor defecto (opción nueva) escribe el marcadorslimdel sidecar, así que el kernel derunCodeya 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 unready_cmdque 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:
AgentTemplateya no precalienta el kernel derunCode(kernelWarmup: falsepor defecto). En una imagen construida con 0.11, la primera celda derunCodeque importe numpy, pandas, matplotlib, scipy o scikit-learn paga esa importación. Para volver al comportamiento de 0.10, construye la imagen connew AgentTemplate({ ..., kernelWarmup: true }).
Corregido¶
pool.take()ypool.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 eraawait pool.take({ waitMs })oawait pool.close()salía con la promesa sin resolver (avisounsettled top-level await, código 13) y dejaba vivos los MicroVM que se estaban calentando. Ahoratake()yclose()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).AgentModeladmiteprovider"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,xaiGatewayylitellmGateway({ upstream, basePath: "/v1" }). OpenCode usa los proveedores nativosopenai,googleyazurea través de la pasarela; deepagents construyeChatOpenAI(use_responses_api=True),ChatGoogleGenerativeAIoAzureChatOpenAI(Responses API, clave enapi-key). Elopencode.jsondeclara 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/v1yOPENCODE_EXPERIMENTAL_WEBSOCKETSqueda apagado; la prueba de humo deAgentTemplateimporta tambiénlangchain_openaiylangchain_google_genai.gen_ai.provider.namevalegcp.geminiyazure.ai.openaipara 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()desbx.agentparan 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: unsleepo un servidor lanzado por el agente sobrevivía y el siguienterun()encontraba el agentebusy. Cada ejecución arranca ahora conkillTree: trueyraydcongela y mata todo su árbol (también lo demonizado); el stream acaba conreason: "timeout"cuando ya no queda nada.abort()y los límites del SDK sólo matan el handle: desaparece la ordenpgrepque corría antes como segundo comando.commands.run()aceptakillTreepara lo mismo en cualquier comando. Necesita elraydde esta versión: con uno anterior el campo se ignora y el timeout yabort()sólo alcanzan al grupo del runtime; reconstruye la imagen. -
deepagents cuenta los tokens de los modelos compatibles con OpenAI. Con
"openai-compatible",ChatOpenAIno pedía el uso en streaming (stream_options.include_usage): cada paso llegaba con 0 tokens,result.usagequedaba a cero yAgentLimits.maxTotalTokensno cortaba nunca. El runner crea ahoraChatOpenAI(stream_usage=True). Medido de verdad, con el egress cerrado, contra la Chat Completions compatible con OpenAI de Amazon Bedrock (bedrock-runtime/openai/v1ybedrock-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) llevasocksio, el extrasocksde httpx.raydexportaALL_PROXY=socks5h://...en el sandbox y, sin él, httpx fallaba al crear el cliente (AgentFailedconImportError) 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 derequirements-deepagents.txt: reconstruye la plantilla de agente.
0.9.1 - 2026-10-07¶
Fixed¶
Sandbox.create({ timeoutMs: 120_000 })sinidleya no lanzaInvalidArgumentError. Desde 0.3.0, cualquiertimeoutMs(omaxLifetimeMscononTimeout) de 300 s o menos fallaba porque el SDK validaba su propia política de idle por defecto contra él. Ahora, sinidle, la auto-suspensión por defecto se desactiva si no cabe (el sandbox termina en su plazo, como en E2B) y, cononTimeout: "pause", baja a 60 s (PAUSE_DEFAULT_IDLE_FALLBACK_SECONDS). Unidleque pasas tú se valida igual que antes.
Documentation¶
- Los e2e de
s3-mountsyrayd-otlpdocumentan como precondición que la política gestionada de su pila (PolicyArn) esté adjunta al rol de ejecución;rayito stack deploysó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 serveresidente), sin periodo de obsolescencia: apenas ganaba unas décimas a la opción C (AWS_API_NOTES.mdQ154) y costaba más memoria y más por plaza. Desaparecen la opciónservedeagentPoolWarmup()y desbx.agent.prepare(),attachdeAgentRunOptions(sbx.agent.run()/stream()) yRunRequest.attach; el script de OpenCode ya no busca ni se engancha a un servidor (--attach) ni relee la vuelta de él, y el spanrayito.agent.rundeja de llevarrayito.agent.attached. La interfazAgentRuntimepierdeabortCommand(ningún runtime lo usaba ya) ywarmupSteps()no lleva argumentos;OPENCODE_SERVE_PORTdesaparece. Migración: quitaserve: trueyattach; para un primer mensaje rápido usaagentPoolWarmup(runtime)(opción C) o el arranque normal (Sandbox.create(...)ysbx.agent.run(...)). Un runtime propio borraabortCommandy el parámetro dewarmupSteps. - Se eliminan
GatewayErroryGatewayErrorOptions(no estarán en 0.9.0).GatewayErrornunca 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 queraydrechaza esSandboxError. Migración: quita el import y cualquierinstanceof GatewayError; si capturabas fallos de la pasarela al crear o refrescar, usainstanceof 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_mismatchyoutput_limit, reservados) dicen lo que pasa de verdad, yreincarnate()lista todo lo que reaplica.
Fixed¶
Sandbox.connect()recuperasbx.gateways: otro proceso ya puede usarsbx.agentsobre un sandbox creado congatewayssin recrearlo.connect()lee una vez elConfigureStatusderayd(nombre, puerto y último error de cada ruta; nunca el upstream ni las cabeceras) si el agente anunciasecret_gatewayy 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. Siraydrechaza el token en esa lectura,connect()no falla: se salta la recuperación y elAuthenticationErrorsale en la primera llamada autenticada, como hasta ahora.
0.8.0 - 2026-10-07¶
Cambios que rompen¶
registerWebhook(eventos de ciclo de vida) rechaza conInvalidArgumentError, antes de cualquier llamada a AWS, una URL cuyo host eslocalhost(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"oruntime: 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 ensrc/agent/assets/deepagents-runner.ts, generado porscripts/gen_agent_assets.py.mcp,rawConfigyattach: truefallan conInvalidArgumentErrorantes de cualquier RPC. - Agente de IA, arranque rápido (
ai-agent-fast-start):AgentTemplate(yrayito agent template builden la CLI de Python) construye la imagen del agente con OpenCode y ripgrep fijados por sha256 y el venv de deepagents con--require-hashes(salvoruntimes: ["opencode"]), todo de root, el manifiestorayito.agent-template/1y, salvoprefetch: false, un demonio que precarga los binarios tras cada restauración del snapshot.PoolConfigganawarmup(pasos que cada plaza corre antes de aparcarse; un paso fallido es un calentamiento fallido),allowInternetAccessynetwork;agentPoolWarmup()da los pasos del agente y, conserve: true, deja unopencode serveresidente con contraseña propia de cada VM al queagent.runse engancha tras eltake(). Coste sólo si construyes la plantilla o pasaswarmup(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 escribeopencode.jsonsin credenciales, lanzaopencode runcon el prompt por stdin, un cerrojo por sandbox y--attacha unopencode serveresidente 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 quesbx.agentdescribirá 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 desbx.gateways),AgentPermissions(sóloallow/deny;question,webfetchywebsearchdenegadas por defecto),SubAgent,McpLocal,McpRemoteyAgentLimits(50 pasos, 600 s, 16 MiB y 1 000 000 de tokens por defecto); los eventos (unión discriminada portype;agentFailed()construye el de fallo),TokenUsageyAgentResult;AgentErrorcon unreasonde una lista cerrada y un mensaje fijo que nunca lleva texto del proveedor; y las pasarelas ya hechasbedrockGateway,anthropicGatewayyopenaiCompatibleGateway, cuyoallowcubre sólo los modelos elegidos.sbx.agentllega en un cambio posterior. - Agente de IA, API pública (
ai-agent-core, design.md §4 y §7): el camposbx.agent(run,stream,prepare).run()corre el agente hasta el final y lanzaAgentErrorsi falla;stream()devuelve unAgentStream(AsyncIterable<AgentEvent>) que nunca lanza por un fallo del agente —el último evento esDoneoAgentFailed— y expone.abort(),.sessionIdy.result(); unAbortSignalenstream()disparaabort().prepare()dispara los pasos de calentamiento del runtime en segundo plano y vuelve enseguida. Aplica la configuración del runtime confiles.writeFiles(una sola vez por sha de configuración), lanza el script del runtime concommands.run({ background: true, stdin: true })y trocea su stdout en eventos;AgentLimits.maxStepsymaxTotalTokenslos impone el propio SDK sobre esos eventos, no el runtime.sbx.agentno manda ningún RPC hasta el primerrun()/stream(). ContracerProvider, cada ejecución abre un spanrayito.agent.runcon atributosgen_ai.*yrayito.agent.*(nunca el prompt, el texto de la respuesta ni argumentos de herramienta). UnAgentRuntimeconcreto (el adaptador de OpenCode) llega en un cambio posterior; mientras tanto,runtimeadmite cualquier objeto que implemente la interfazAgentRuntime.
Fixed¶
sbx.agentcon unopencode serveresidente (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 unagent_failedconreason: "protocol_error"o unDonesin texto aunque el servidor completaba la respuesta. Ahora el script crea la sesión en el servidor, y al salirrunrelee de él los mensajes de esa vuelta y emite las partes que faltaban (sin duplicar las ya emitidas), incluido el error del modelo. UnsessionIdque no tiene forma de id de OpenCode esInvalidArgumentError.AgentTemplateconprefetch: el demonio de precarga leía los binarios nada más restaurar el snapshot y su E/S competía con el arranque, así quecreate()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 enrunner_sha256del manifiesto. Sin él,runtime="deepagents"fallaba siempre en una imagen construida conAgentTemplate(aceptación en AWS,AWS_API_NOTES.mdQ150).sbx.agent:abort()y los límitesmaxSteps/maxTotalTokensparan 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 matarayd); antes unsleepo 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 siguienterun()no lo encuentrabusy.sbx.agent: un timeout deAgentLimitsterminaba conreason="runtime_error"en vez de"timeout".runtime: "deepagents": las herramientas de ficheros escribían una ruta absoluta dentro del workdir (/home/user/xacababa 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: conserve: trueel servidor residente moría al agotartimeoutMs.
Changed¶
- Montajes S3: el
MountErrorde 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 enRAYITO_ALLOWED_MOUNT_BUCKETSy cómo publicar la imagen con él (--env RAYITO_ALLOWED_MOUNT_BUCKETS=<bucket>omake image-publish-caps MOUNT_BUCKETS=<bucket>), sin nombrar el bucket. El texto vive enMOUNT_ERROR_HINTS, igual en los dos SDKs (testdata/s3-mounts/error-hints.json).
Security¶
- Eventos de ciclo de vida (
webhook-url-public-address):registerWebhookrechaza también, conInvalidArgumentErrory antes de cualquier llamada a AWS, un hostlocalhost(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.isBlockedWebhookAddressaplica la misma regla que el deliverer y los tres pasantestdata/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
rayd0.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 claseCustomDomaindespliega una distribución CloudFront, una CloudFront Function de enrutado (cloudfront-js-2.0) y un KeyValueStore (infra/custom-domain.yaml), yregister/unregister/refresh/hostForgestionan las rutas{puerto}-{alias}.<tu-dominio>(hostname HTTPS normal, sin las cabecerasx-aws-proxy-*).register()exigetrafficTokensalvopublic: trueexplícito.deploy({ alternateDomainNames })sustituye el alias comodín por hostnames exactos. Nuevos peers opcionales@aws-sdk/client-cloudfront-keyvaluestorey@aws-sdk/signature-v4a(el plano de datos del KeyValueStore firma con SigV4A); sin instanciarCustomDomainno se importa ninguno.domainenSandbox.create()sigue lanzandoUnimplementedError: la integración congetHost()/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 deniegacloudfront:CreateDistributionpor una SCP, así que nunca se ha servido tráfico por una distribución desplegada con esta plantilla (AWS_API_NOTES.mdQ121, Q140, Q141). La API puede cambiar en una minor. rayito-base-caps-efs(y sus sufijos de tamaño) cuenta como variante caps paramounts,volumesytelemetry(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-efscomo peer opcional cargado sólo en el primer uso) ySandbox.create({ volumes }), que valida la petición (tipos, rutas, variantecapsy un único conector propio enegress: sin él, conINTERNET_EGRESSo con dos conectores esInvalidArgumentError, porque un MicroVM sólo admite un conector de egress,AWS_API_NOTES.md§16 Q131), como mucho 4 volúmenes yexecutionRoleArnantes de cualquier llamada a AWS; resuelve la IP de mount target que falte con unDescribeMountTargetsCommandpor sistema de ficheros antes derun-microvm, manda la secciónefs_volumesen el únicoConfigureSandboxdecreate()(con plazo de 65 s), sólo vuelve con todos los volúmenesmountedy, si uno falla, termina el sandbox (salvokeepOnFailure) y lanzaVolumeMountError(code); sobre una imagen sinamazon-efs-utils,UnimplementedError.reincarnate()la vuelve a mandar ysbx.volumes()da el estado en vivo.EfsVolume(validamountTargetIpcomo IPv4),VolumeStatusy los erroresVolumeError/VolumeMountError/VolumeNotFoundError/VolumePathNotFoundError. El shim de E2B (Volume) hace CRUD real sobrenew E2B({ volumeStore })(volumeIdes el nombre del volumen, el mismo que recibenconnect/getInfo/destroy); sus operaciones de contenido (UnimplementedError("volume.content")) siguen sin plano de datos, ySandbox.create({ volumeMounts })monta connew E2B({ volumeStore, volumeConnectorArn }): ese sandbox sale sólo por el conector del volumen yallowInternetAccess: trueexplícito esInvalidArgumentError. Componenterayito stack {deploy,status,destroy} efs-volumes(infra/efs-volumes.yaml: sistema de ficheros EFS cifrado, un mount target por subred deSubnetIds, 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óloDescribe*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 esFAIL,volumeStore()da unVolumeStoresobre la pila, ydestroy({ deleteFileSystem: true })/deleteFileSystem(id)borran el sistema de ficheros conservado (sólo uno con la etiquetarayito=efs-volumes).deploy({ readOnlyAccessPointArns })(ReadOnlyAccessPointArns) le deniegaClientWritea los access points de sólo lectura: la opciónrodel montaje no basta, porque el usuario del sandbox alcanza el puerto local deefs-proxy(Q133). El hallazgointernet-egressdecheck()explica que un sandbox con volumen sólo tiene internet por la VPC.list/getson eventualmente consistentes, comoDescribeAccessPoints(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):createde un nombre que ya existe reintentagethasta 30 s ydestroyde un access point que el listado aún mostraba pero ya no existe devuelvefalse.
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 colaForwarderFailuresQueue(destinoOnFailurede 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.mdT22). - Eventos: sólo lo que emite
rayd, y reciente. Tras el MAC, el evento debe ser del mismo sandbox que el stream, con unevent_idde 32 hex (nunca un id del reconciliador),kill_reasonsólo enkilledy sólorequest, un ARN demicrovm-imagey unoccurred_at_msde 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 aDELIVERY#<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.registerWebhookrechaza conInvalidArgumentErroruna 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 arayito/stacks/<componente>/<sha256>.zip(dentro del espaciorayito/que protegen las plantillas de IAM, ya no en la raíz del bucket), mandaExpectedBucketOwnercon la cuenta del llamante y, si el objeto ya existe, compara su contenido antes de reutilizarlo (si no coincide, lo sobrescribe conChecksumSHA256y avisa por el logger). infra/iam.yaml: el lanzador ya no puede publicar imágenes. Nuevas políticasSandboxLauncherPolicy(sólo lanzar y manejar sandboxes: la del rol de un servicio en producción) eImagePublisherPolicy(sólo publicar);CallerPolicysigue igual, como su unión. ElDenydel execution role sobre los artefactos de imagen existe ya siempre, no sólo conPersistenceBucket.infra/templates.yaml:RayitoTemplateBuildersólo leerayito/*del bucket de artefactos o de la imagen base, nunca el bucket entero (que puede guardar losHOMEpersistidos y las transferencias).- Secretos de firma de webhooks fuera de
secrets=.RayitoSecretsReaderniegarayito/webhooks/*y el SDK se niega a leer esos secretos porsecrets/SecretCache(InvalidArgumentError).SecretPrefixdeinfra/secrets-access.yamldebe 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 exigeaws:SourceAccount. - Eventos: el código del sandbox ya no puede inundar la pila con
paused/resumed. Esos dos eventos los emiteraydcuando 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 traskilled, ningúncreatedrepetido, ninguna posición repetida o anterior) y limitapaused/resumedcon un cubo por sandbox (20 seguidos, después uno cada 30 s); lo rechazado se cuenta comoinvalid_transitionorate_limitedy 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/resumedquedan documentados como orientativos (SECURITY.mdT22). 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-signatureno 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 emiteEventsLauncherPolicy(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) yEventsWebhookAdminPolicy(registerWebhook/listar/borrar: sólo filasWEBHOOK). 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:RayitoRunAllowedSizesniega también publicar imágenes. Sin ello, una identidad con laCallerPolicyestá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 conimageVersion(SECURITY.mdT27).SECURITY.md: filas T20, T23, T26 y T27, que los cambios de M15 planearon y nunca se aplicaron (infra/otlp-export.yamlya citaba T23).infra/ci-oidc-role.yamlyinfra/README.mdpiden 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 deTestImageArns.SecretGatewayrechaza una ruta deallowqueraydnunca dejaría pasar (sec-rayd-agent-hardening): las rutas deallowsiguen ahora la misma lista de permitidos queraydaplica 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 lanzaInvalidArgumentErrorantes de cualquier llamada, con los mismos vectores compartidos querayd(testdata/secret-gateway/request-paths.json)..dockerignorecon la semántica de Docker enTemplate.build: anclado en la raíz,**como cero o más directorios (así**/.env,**/.gity el resto de los valores dedocker initexcluyen 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/(*.pycsólo excluye los de la raíz; usa**/*.pyc). Unprocess.emitWarningde tipoRayitoContextWarning(con las rutas, nunca el contenido) avisa si el contexto va a empaquetar.env*,.git/,.aws/,.ssh/,*.pemo*.key. Cada fichero del contexto se vuelve a comprobar dentro del contexto y se abre conO_NOFOLLOWjusto antes de leerlo.Template.build(): un rechazo de AWS distinto de la cuota es ahoraBuildError({ reason: "aws_error" })con el resumen saneado como mensaje ycause, 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). Elnamedel error se conserva.StackErrorusa también el resumen saneado comocause.- Credenciales de git:
clone/push/pullconusername/passwordcorren sin hooks ni credential helpers, se niegan (GitAuthError) si la configuración de git reescribe URLs (url.*.insteadOf), y uncloneque falla sin exit de git intenta igualmente quitar las credenciales deorigin; si una restauración falla, elloggerdel 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 yCommandResult.truncated/CommandExitError.truncatedlo indican.commands.run/connectaceptanmaxOutputBytes(0no 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 esInvalidArgumentError. Los generados (32 bytes) no cambian. ProxyToken.jweya no es enumerable:console.log/util.inspectyJSON.stringifyno 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): elrunHookPayloadlleva el bucket y la baseprefixdelS3Prefix(nuncaprefix/name), y unraydcon la corrección rechaza conpermission_deniedcualquier checkpoint o restore fuera de esa base. Con unprefixpor inquilino, el token de un sandbox ya no alcanza elHOMEpersistido de otro aunque compartan execution role. Cambia un uso: un restore o checkpoint explícito hacia otra base desde un sandbox creado conpersist=ahora falla conpermission_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()sinfilename, el access token y las líneas de.dockerignorede 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(salidaOperatorPolicyArndeevents-webhooks): sigue siendo la unión de las tres políticas nuevas durante esta versión. Vincula a cada identidad la suya (EventsLauncherPolicy,EventsReaderPolicyoEventsWebhookAdminPolicy).
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 conUsePreviousValue. Antes, por ejemplo, redesplegars3-mountssin repetirPrefixesvolvía a'*'(todo el bucket),metadata-indexsinTableNamereemplazaba (y borraba) la tabla ysecrets-accesssinKmsKeyArnquitabakms:Decrypt. NuevoparameterChanges()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/domainlanzanUnimplementedErrorcon "todavía no disponible" en vez de "llega en 0.6".- Se exporta
WorkdirStepdesderayito(paridad con Python: ya era miembro de la uniónWireStep). reincarnate()reaplica todas las secciones deConfigureSandbox(m15-reincarnate-configure-replay): hasta ahora sólo reenviabagatewaysy un sucesor perdíamounts,eventsytelemetry.create()guarda sus opciones 0.6 enLaunchOptions.features(relaunchFeatures: todas menossize) yreincarnate()las reenvía (relaunchCreateOptions) alcreate()del sucesor, que las aplica por el mismo camino en su únicoConfigure:eventsderivak_sbxdel nuevosandboxId,mountsespera otra vez amountedytelemetryusa la imagen y la memoria del sucesor. De paso,reincarnate()de un sandbox creado consizeya no reenvía el tamaño resuelto junto al ARN de la imagen (que ya lo lleva), combinación quecreate()rechaza.- Tipos correctos para consumidores CommonJS (
exportsdepackage.json):"rayito"y"rayito/e2b"declaraban untypeshermano deimport/require, que TypeScript elige antes que ambos, así que un proyecto CommonJS conmoduleResolution: node16/nodenextrecibía los.d.mts(ESM) para unrequireque carga el.cjs("Masquerading as ESM" de arethetypeswrong). Ahora cada condición lleva sutypes(import→.d.mts,require→.d.cts), eltypesde nivel superior apunta al.d.ctsdemain, se exporta"./package.json"ypnpm pack:checkrechaza un mapa que vuelva a la forma anterior. Mismos ficheros en tiempo de ejecución.
Documentation¶
- Bloques "Coste y activación" completos para
mounts/S3Mountysize/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.mdy las notas de 0.6.0 ya no describen como pendientes funciones publicadas en 0.6.0. DynamoDbIndexenseñ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 ycheck-dts-cost-blockslo exige.- Metadatos del paquete:
package.jsondeclarahomepage(el sitio de documentación) ybugs; el README del paquete termina con la licencia, elNOTICEincluido y la nota de marcas (proyecto independiente, no afiliado a E2B). ElNOTICEconserva 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
OptionalStackyConfigureSandbox(v06-foundations, M15 foundations, ADR-015/ADR-016, opcional y apagado por defecto): la claseOptionalStacks(deploy/status/destroy/components) sobre un catálogo de nueve componentes (metadata-indexysecrets-access, migrados sin cambios de comportamiento; los otros siete son stubs hasta su propia función), con@aws-sdk/client-cloudformationcomo peer opcional; las siete opciones 0.6 deSandbox.create()(mounts,volumes,size,events,telemetry,gateways,domain) existen ya enSandboxCreateOptionsy lanzanUnimplementedErrornombrando el cambio que las trae mientras sigan siendo un stub, antes derun-microvm; en cuanto una deja de serlo (mounts, ver más abajo),create()ejecuta susconfigureSectionsjusto tras el primerHealth(configure-base.ts:requireCapabilities,buildConfigureRequest,checkConfigureResponse), dentro del mismo camino que ya termina el sandbox ante cualquier fallo anterior aagentReady. 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 desderayito) en el guest conmount-s3/FUSE, sólo sobrerayito-base-caps(requireCapsForlo exige antes derun-microvmcuando la imagen se nombra directamente; sobre un ARN opaco la decisión se difiere aHealth.featurestras/run, que termina el sandbox si falta la capacidad).mountsacepta un objeto literal (comovolumes) o unMap.create()no vuelve hasta que cada montaje está montado: sondeaConfigureStatus(como mucho 15 s) y, si uno falla o no se asienta, termina el sandbox y lanzaMountError.sbx.mounts()da el estado en vivo de cada montaje ("pending"/"mounted"/"failed",ConfigureStatusen cada llamada); una sección rechazada o un montaje fallido lanzaMountErrorcon uncodecerrado (network,iam_denied,not_found,not_allowed,invalid_path,helper_missing,timeout).raydlanzamount-s3como el usuario dedicadorayito-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 IAMRayitoS3MountAccessdel componenteOptionalStacks3-mounts(infra/s3-mounts.yaml, pideCAPABILITY_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 enRAYITO_ALLOWED_MOUNT_BUCKETSde 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 conprocess.emitWarning(..., { type: "RayitoCompatWarning" })si no encaja exacto) y antepone el sufijo de imagen (rayito-base-4gb) antes de resolver el ARN;sizecon un template dado por ARN, o por encima del máximo publicado, esInvalidArgumentErrorantes de cualquier llamada a AWS.getInfo()confirma, con una única llamada cacheada aGetMicrovmImageVersionpor versión de imagen,SandboxInfo.baselineMemoryMibybaselineCpu(vCPU medido exactamente para los cinco tamaños del catálogo);cpuCount/memoryMbsiguen siendo lo que el guest reporta de verdad. El shimrayito/e2breporta ese mismo baseline enSandboxInfo.cpuCount/memoryMBcuandosizese usó (como E2B reporta lo declarado por el template), la vista real del guest si no. Guardarraíles de coste opcionalsizes-guard(RayitoRunAllowedSizes: un Deny delambda:RunMicrovmfuera de los ARN de imagen permitidos, efectivo aunque la identidad ya tenga elmicrovm-image:*de laCallerPolicyestándar;rayito image publish --sizes/--envyrayito image sizesson sólo CLI Python). Sinsize, el comportamiento sigue siendo exactamente el de 0.5.x. LifecycleEvents(m15-events-webhooks, opcional y apagado por defecto): despliegainfra/events-webhooks.yaml(deploy/status/destroy, componenteevents-webhooksdeOptionalStacks), 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-dynamodby@aws-sdk/client-secrets-managercomo peers opcionales. Los errores de AWS llegan comoWebhookErrorcon sólo el código (awsCode).eventsenSandbox.create()valida el tipo y queloggingllegue a CloudWatch antes de lanzar y, trasrun-microvm, manda la clave del sandbox (k_sbx, derivada de la clave del stack y elsandboxId; unGetSecretValuepor instancia deLifecycleEvents) en el mismoConfigureSandboxquemounts/gateways/telemetry(LifecycleEventsSectionFactory); sin la pila desplegada, sinlifecycleEventsen el agente o con la sección rechazada, termina el sandbox (salvokeepOnFailure).eventspasa a tiparseLifecycleEventsenSandboxCreateOptions. Aceptado en AWS real (AWS_API_NOTES.mdQ105–Q108); el.gen.tslleva 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 derayda CloudWatch (m15-rayd-otlp, ADR-021, opcional y apagado por defecto):TelemetryExport/OtlpAuthnuevos (OtlpAuth.executionRole(), exigerayito-base-caps;OtlpAuth.bearer(secretName), experimental, funciona enrayito-base; el nombre se resuelve bajorayito/por la mismaSecretCachequesecrets). Se envía como una sección deConfigureSandboxtras/run; una imagen cuyoraydno exporta termina el sandbox (salvokeepOnFailure) y lanzaUnimplementedError. ContracerProvider, cada RPC del handle lleva además eltraceparentdel span de esa misma llamada haciarayd; sin él, ninguna cabecera nueva. El token deOtlpAuth.bearer(...)es una API key de CloudWatch Metrics.sbx.getTelemetryStatus()leeConfigureStatus, como una llamada explícita aparte degetHealth(). Sintelemetry, ningún comportamiento cambia frente a 0.5.x.- Templates declarativos (
m15-templates, ADR-022, opcional y apagado por defecto):Templatecompila un DSL (igual alTemplatede E2B v2) a un Dockerfile y un zip deterministas sobre una imagenrayito-base/rayito-base-capsya publicada;Template.build()/buildInBackground()/getBuildStatus()/exists()suben el artefacto por hash de contenido y llaman acreate/update-microvm-image, reutilizando una versión idéntica en vez de reconstruir. Un build fallido se explica conBuildError(step/command/exitCode/logTaildel log de BuildKit, oreason: "ready_client_error"|"ready_server_error"si falló elreadyCmd), sin repetir nada.fromImage/fromTemplate/fromDockerfile/fromGcpRegistry/aptInstalllanzanUnimplementedError(documentados en ADR-022). El shimrayito/e2b'sTemplateya construye de verdad, con la firma de E2B ({ alias, skipCache, memoryMB, cpuCount }) ynew E2B({ bucket });BuildError/TemplateErrordel shim pasan a ser alias de las clases nativas (igual patrón queNotEnoughSpaceError/FileUploadError).setStartCmd()necesita una imagen base conrayd0.6. La imagen compuesta hereda la configuración de la base (additionalOsCapabilitiesincluida);skipCache()equivale aforce: true; la cuota de builds de AWS llega comoreason: "build_quota". El núcleo de build vive ensrc/images/gateway.ts. Sin llamar aTemplate.build(), el SDK no importa estáticamente@aws-sdk/client-s3ni 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 IAMRayitoTemplateBuilder, $0 en reposo, que no puede sobrescribir las imágenes base publicadas. Aceptación en AWS real:pipInstall()compila apython3 -m pip install --no-cache-dir --break-system-packages(rayito-base no tienepipen elPATH); la versión gestionada que se hereda se envía como1, no como el eco1.0quecreate-microvm-imagerechaza; la política concedeCreateMicrovmImagesobre*(AWS no la autoriza por ARN) ylambda:PassNetworkConnectorsobre los conectores gestionados, y suDenycubreUpdateMicrovmImagesobre las bases (AWS_API_NOTES.mdQ114-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 suallowcubre, dentro de su límite de peticiones por minuto, inyectando cada cabecera vaultada (resuelta con la mismaSecretCachequesecrets, nunca antes deConfigure) y eliminando primero cualquier cabecera del mismo nombre que el sandbox intente poner.sbx.gateways.get("nombre")?.urlda la URL de loopback;refresh()rota el secreto sin recrear el sandbox (relee Secrets Manager aunque laSecretCacheno haya vencido, conserva el puerto y lanza siraydrechaza la sección). Tambiénpool.take({ gateways }). Cualquier fallo al configurarla trasrun-microvm(imagen anterior a 0.6.0, flag ausente, secreto que falta, sección rechazada) termina el VM salvokeepOnFailure. Singateways, ningún cliente Secrets Manager nuevo se construye y no se manda ningúnConfigureSandbox.
Fixed¶
OptionalStacks.deploy("events-webhooks", { artifactBucket })ya no exige repetirparameters.ArtifactBucket:StackArtifact.bucketParameterKeypasa a la plantilla el mismo bucket al que se sube el código, y un valor distinto esInvalidArgumentErrorantes 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):tracerProviderenSandbox.create()(también conpool),connect()(ambas formas) y las variantes estáticaskill/pause/resume; instrumentacommands.run,runCode,files.*ykill/pause/resumede instancia con spansrayito.*deSpanKind.CLIENT.@opentelemetry/apies una peerDependency opcional importada sólo conimport type(se borra en el build: ningún require/import en tiempo de ejecución, comprobado porpack:check), ySpanKind.CLIENT/SpanStatusCode.ERRORson 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 sumessage. $0 de AWS; el coste (si lo hay) es el del backend de exportación del llamante. Fuera de alcance: propagacióntraceparent/tracestatehaciarayd, 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ónindexenSandbox.create(),Sandbox.list(),Sandbox.paginate()yPoolConfig.create()escribe una fila inmutable (PutItemCommandcondicional) trasrun-microvm; conmetadataeindex,list()/paginate()unenlist-microvmsconBatchGetItemCommandpor página y filtran también sandboxesSUSPENDEDsin ninguna sonda. Nuevos erroresSandboxIndexErroreIndexWriteError.@aws-sdk/client-dynamodbes una peerDependency opcional que sólo se carga conloadOptionalPeeral usar el índice. Coste: ~1 WRU por sandbox creado y 0,5 RRU por candidato listado.reincarnate()conservaindex: el sucesor escribe su propia fila. - Shim de E2B:
Sandbox.list({ query: { metadata, state: ["paused"] }, index })ynew E2B({ index }). - Secretos sobre AWS Secrets Manager (
m13-secrets, opcional y apagado por defecto, ADR-014):new SecretStore({ region, credentials, prefix, kmsKeyId })concreate/update/getInfo/exists/list/destroy, ynew SecretCache({ ttlSeconds: 300 })(una promesa en vuelo por clave; los aciertos no llaman a AWS;refresh()/invalidate();toJSON/inspectnunca muestran valores).@aws-sdk/client-secrets-manageres una peerDependency opcional que sólo se carga conloadOptionalPeeral usar la función. Coste: $0,40/secreto-mes hastadestroy+ $0,05/10 000 llamadas. secrets/secretCacheenSandbox.create()(también conpool),Sandbox.connect(),sbx.connect(),pool.take()y, por llamada, encommands.run,pty.create,runCode(contextos Python) ycreateCodeContext: entregan el valor como variable de entorno por losenvsque ya viajan arayd, nunca en elrunHookPayload,metadata, logs ni errores. La primera vez emiten unRayitoCompatWarning(process.emitWarning): el valor es visible para el código del sandbox.SecretError/SecretNotFoundErrorenrayitoy enrayito/e2b.- Shim de E2B:
Secret(estáticoscreate/update/getInfo/list/exists/destroy/fill/iamToken),SecretPaginatory el tipoSecretInfosobreSecretStore, con los nombres dee2b2.51.0;new E2B({ region }).Secretusa esa región.
Changed¶
- El motivo de
UnimplementedErrordelist(query.state=paused, query.metadata)enrayito/e2bnombra ahora la opciónindex. rayito/e2bSecretyE2B(...).Secretya no lanzanUnimplementedError.
Fixed¶
SecretStore.destroy()(ySecret.destroyderayito/e2b) devolvíatruepara un nombre que nunca existió, porque AWS acepta elDeleteSecretforzado de un nombre inexistente sinResourceNotFoundException(aceptación de 0.5.0 en AWS real). Ahora haceDescribeSecretantes y devuelvefalsesin borrar si no existe (o ya estaba programado para borrarse, o lo borró otro a la vez), como documenta y como E2B. Usasecretsmanager:DescribeSecret, ya incluido enRayitoSecretsAdmin.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_MSensrc/secrets/store.ts) con ±25 % de jitter.docs/site/docs/secrets.mdavisa de que unloggerdelSecretsManagerClientdel SDK v3 imprime los valores (Rayito nunca lo hace) y de quelist()es eventualmente consistente (~3–5 s).
0.4.0 - 2026-09-30¶
Cambios que rompen¶
UnimplementedErrores ahora el único tipo para "algo que este sandbox no puede dar": unUnimplementedde gRPC (un RPC o un kernel derunCode/createCodeContextausente en la imagen) ya no esInvalidArgumentError/SandboxErrorcongrpcCode: Code.Unimplemented, sinoUnimplementedError, fuera de esa jerarquía.LifecycleUnsupportedError(un plazo lógico contra un agente anterior a M9) pasa a ser subclase deUnimplementedError, no deInvalidArgumentErrorniSandboxError; sufeaturenombra la llamada que pidió el plazo (connect({ timeoutMs })también cuando el agente respondeUnimplementedalSetTimeoutdeconnect) y sureasonusa los mismos textos que el SDK Python. Elreasonde un kernel ausente es el mensaje derayd(que ya nombrarayito-base-poly), sin la pista de publicar una imagen actual.getMetricsHistorycontra una imagen anterior a M9 ya lanzabaUnimplementedError; sólo cambia suerror.cause, deInvalidArgumentErroraUnimplementedError. Migración: cambiacatch (error) { if (error instanceof InvalidArgumentError && error.grpcCode === Code.Unimplemented) ... }ocatch (error) { if (error instanceof SandboxError) ... }alrededor de esos RPCs porerror instanceof UnimplementedError(LifecycleUnsupportedErrorsigue distinguiéndose con su propioinstanceof).Sandbox.probedInfo(elstaticnativo que usaba internamenterayito/e2b) se elimina derayito: 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'sSandbox.getInfohace exactamente lo mismo); quien lo usara pasa aSandbox.getInfo(sandboxId)derayito/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 conprocess.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/.npmrcnuevo (ignore-scripts=true): ningúnpnpm installlocal o de CI ejecuta scripts de ciclo de vida de dependencias..github/workflows/release.yml: el jobtypescriptse parte entypescript-build(sinenvironmentniid-token,pnpm install --frozen-lockfile --ignore-scripts) ytypescript-publish(con el token OIDC de npm, sin checkout nipnpm, que verifica consha256sum -cque el.tgzdescargado es el que produjotypescript-buildantes denpm 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.filesysbx.native.filesdel shim de E2B comparten ahora un únicoTransferClient(y por tanto una sola caché de soporte de transferencias y un solo cliente S3 por sandbox): elFilesystemnativo acepta un segundo parámetro internosharedWithque reutiliza elTransferClientdelFilesystemnativo del que el shim parte, en vez de construir uno nuevo sobre el mismoSandboxCore. Sin cambio de comportamiento para quien sólo usesbx.files;sbx.native.filesdeja de duplicar la sonda y los clientes S3 (.d.tspublicado: sólo gana un parámetro de constructor opcional).- El puerto
ControlPlanedeclara ahorareadonly 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.awsClientSettingsOfsigue exportada con el mismo nombre y la misma firma; sin cambio de comportamiento.
Internal¶
sandbox/core.tsya no depende desandbox/commands.ts: la clasificación de fallos de stream (STREAM_PROBE_TIMEOUT_MS,streamFailureError) vive en el nuevosandbox/stream-errors.ts, sin ciclo concore.tsni concommands.ts;commands.tsreexporta ambos nombres, así que ningún import existente cambia. Sin cambio de comportamiento.e2b/compat.ts(el mapeo puro que refleja_compat.py) absorbehistoryImageError,lifecycleImageError,LIFECYCLE_IMAGE_REASONy la nuevametricsHistoryOrSnapshot, antes en la clase IOe2b/sandbox.ts;Sandbox.getMetricsySandbox.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íarolldown-plugin-dts):pnpm lint typecheck build test pack:checkverdes y los.d.mts/.d.ctsgenerados 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 (Canceledcon el prefijohttp/2 stream closed, ya distinguido de unAbortSignal/Canceledpropio porisStreamReset/isOwnAbortentransport/errors.ts, sin cambio de comportamiento): un stream de comandos en vivo reconecta conConnect(pid, from_seq), agota el mismo presupuesto de tres reintentos que unUnavailable, ydisconnect()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 elCANCELLED "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/e2bytsc --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(antesRateLimitError): uncatchque distinguía el disco lleno porRateLimitErrordeja de verlo. Enrayito/e2bse exporta también comoNotEnoughSpaceError.
Added¶
- Transferencias por URLs prefirmadas de S3 (
m9-file-transfer, ADR-010):Sandbox.create/connect({ transfer })con unS3Staging(bucketDNS sin puntos,prefix,region,maxExpiresIn,thresholdBytes,multipartThresholdBytes) o las variablesRAYITO_TRANSFER_BUCKET/_PREFIX/_REGION(nulllo desactiva), y el gettersandbox.transfer.files.uploadUrl(path, { user, expiresIn, maxBytes, form })→UploadTicket(la URL comotoString(),method,headers,fields,wait({ timeoutMs }),status(),cancel(); de un solo uso) yfiles.downloadUrl(path, { user, expiresIn, filename })→DownloadLink(una foto consizeysha256);sandbox.uploadUrl/sandbox.downloadUrl(path, { user, useSignatureExpiration })devuelven la URL comostring(la forma de E2B). El SDK firma con tus credenciales (SigV4, host virtual regional,requestChecksumCalculation: "WHEN_REQUIRED");raydno guarda ninguna. Las URLs nunca se registran, serializan (toJSONlanza) ni imprimen. - Con
transferconfigurado,files.write/writeFilesde lo que mide>= thresholdBytes(y de todoReadableStream, subido en streaming con@aws-sdk/lib-storage) yfiles.readde un fichero así van por S3 con el sha256 comprobado; sintransferel camino gRPC no cambia. Los cuatro paquetes de S3 se cargan conimport()sólo al usarlos. files.read(path, { gzip, streamIdleTimeoutMs, format: "blob" })yfiles.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,gzipal escribir y las transferencias exigen un agente M9: en uno anterior lanzanUnimplementedErrorantes de enviar nada.- Errores
TransferError(code,reason),FileUploadErroryDiskFullError; los códigos deStreamErrorfailed_precondition,resource_exhausted,unavailableycancelled. -
Los clientes S3 de las transferencias heredan del plano de control sus
credentialsy suproxy(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,timeoutMses un plazo lógico queraydhace cumplir aunque el cliente muera.sandbox.setTimeout(timeoutMs)y el estáticoSandbox.setTimeout(sandboxId, timeoutMs, { accessToken })(EXACT),sandbox.connect({ timeoutMs })ySandbox.connect(id, { timeoutMs })(AT_LEAST);SandboxLifecycleengetHealth()/getInfo();LifecycleUnsupportedErrorcontra 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;UnimplementedErroren una imagen anterior, con el mismo motivo que Python y el error gRPC encause),SandboxMetrics.memCacheBytes,SandboxHealth.cpuCountymemoryTotalBytes,Sandbox.paginate({ limit, nextToken, order, startedAfter })→SandboxListPaginatorylist({ metadata, startedAfter, order }). - Política de egress (
m9-egress-policy, ADR-012):Sandbox.create({ network, allowInternetAccess }),updateNetwork()(instancia y estático),getNetwork(),ALL_TRAFFIC,EgressEnforcementySandboxHealth.egressEnforcement; sólo enrayito-base-caps, en otra imagen el SDK termina el VM y lanzaUnimplementedError. runCode(code, { language: "typescript" })(aliasts) y"javascript"(js) con el kernel de Deno derayito-base-poly(m9-deno-kernels).sandbox.git(Git,GitAuthError,GitUpstreamError),extraHeadersentransportyproxyen 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 defectoSandbox), conE2B,ConnectionConfig, estáticoskill/getInfo/getFullInfo/isRunning/connect/pause/setTimeout/getMetrics/list/updateNetwork,getHostsíncrono,trafficAccessToken,NotEnoughSpaceError,ServiceBusyErroryUnimplementedErrorexplícito para lo que no tiene primitiva.pack:checkexige los cuatrodist/e2b.*. Como en Python,getMetrics(instancia con rango y estático) contra una imagen anterior a M9 ylistconmetadatay un estadopausedlanzanUnimplementedErrorcon el motivo de Python; elgetMetricsestático sin token esAuthenticationError(diferencia documentada endocs/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
runCodetardaba ~315 s en salir tras su última línea (~65 s concommands.run). En@connectrpc/connect2.x abortar un server-stream sin volver a leerlo no limpia el timer (con ref) de sutimeoutMs, y el SDK abortaba así todo stream ya terminado (tras elEndEvent) 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"conidle.autoResume, si este cliente suspendió el sandbox al vencer y la suspensión real duró menos de 2 s (el vigilante deraydno la reconoce como congelación), el sandbox volvíaexpiredy la siguiente llamada fallaba consandbox_timeout. Ahora el SDK aplica la regla de E2B (max(timeout, 300 s), acotada al tope menos 5 s) con unSetTimeouten el primersandbox_timeouttras reanudar, como en Python. EseSetTimeoutva por la unaria que reconecta (unUnavailabletransitorio se reintenta), la marca de un solo uso sólo se consume cuando laresumeGenerationya avanzó y elSetTimeoutrespondió, y los callers concurrentes comparten una sola promesa de reapertura (un únicoSetTimeout). sandbox.getInfo()sólo releeHealthcuando el sandbox estáRUNNINGcon plazo lógico gestionado (ADR-011): antes sondeaba cualquier sandboxRUNNING, y cadaHealthes tráfico de entrada que reinicia el contador de idle de la plataforma, así que sondeargetInfo()impedía la auto-suspensión.metadatay los hechos del guest salen del últimoHealthsin RPC extra.- La readiness de
create(),connect()yresume()exige además unHealthconsandboxId: el proxy deja pasarHealthantes de queraydreciba/run, y eseHealth(kernel del snapshot sin rotar) daba por listo un sandbox cuya rotación de/runponíakernelReady=falsejusto después (AWS_API_NOTES.md Q78), como en Python. requestTimeoutMsacota también la pata S3 de una escritura enrutada y de una lectura enrutadabytes/text/blob(aborta la subida o elGetObject, borra el objeto de staging y lanzaTimeoutError), como en Python.signalllega asetTimeout,connect,isRunning,getHealth,getMetrics,getMetricsHistory,getNetworkyupdateNetwork(de instancia y estáticos) y a los mismos métodos derayito/e2b.rayito/e2b:Sandbox.getInfo(id)sondea unHealthsobre un sandboxRUNNING(endAtes el plazo lógico, conmetadataylifecycle); unhttpsPortsno vacío esUnimplementedError(regla QE2, como Python);network/allowInternetAccesssiguen D10 (política leída sea cual sea suenforcement, respaldo enINTERNET_EGRESS) ygetInfo()sólo absorbeUnimplementedErrordeGetNetwork.SandboxInfoganaingress/egress.- Un
nextTokenconstartedAtMsnegativo esInvalidArgumentError. SandboxInfoganametadata(los decreate({ metadata })leídos del últimoHealth;undefinedsi no se leyeron), como en Python:sandbox.getInfo()los trae y, enrayito/e2b,(await Sandbox.connect(id)).getInfo().metadataya no sale vacío.rayito/e2b:runCode/createCodeContextsiguen el contrato de kernels de Python:r,javay el resto sonUnimplementedErrorsin tocar el agente, y elUnimplementedde una imagen sin el kernel también (con el error nativo encause);pythonviaja sinlanguage.rayito/e2b: unonEventdefiles.watchDiro unonDatadepty.create/pty.connectasíncrono que rechaza se registra como aviso en vez de quedar como una promesa rechazada sin manejar (que termina Node).UnimplementedErroracepta{ cause }como cuarto argumento.
Security¶
- El access token ya no aparece al inspeccionar (
util.inspect,console.log), serializar ni hacer spread deLaunchPlan, del núcleo del sandbox ni de unSlotRecorddel pool, ni elrunHookPayload(con losenvs) deLaunchRequest: son propiedades no enumerables, como elrepr=Falsede Python. JsonFilePoolBackendescribe 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 conopen(temp, "wx", 0o600)sobre un nombre aleatorio, se fija el modo conhandle.chmod()antes de escribir y se renombra al destino. La lectura usaO_NOFOLLOW. Sin esto la promesa de0600de T14 no era cierta.- Los errores de AWS ya no exponen la petición firmada: el
causede los errores que traduce el plano de control (translateAwsError) era el error crudo del SDK v3, cuyo$responsearrastra la petición HTTP y los buffers del socket;util.inspect,console.erroro el "Serialized Error" de vitest imprimíanauthorization: AWS4-HMAC-SHA256 Credential=ASIA…yx-amz-security-token. Ahora elcausees un resumen (sanitizeAwsError,src/aws/sanitize.ts) con sóloname,code, el mensaje redactado,$faulty$metadata.{httpStatusCode, requestId, extendedRequestId, attempts}. Los errores de S3 de las transferencias (translateS3Error), que no llevabancause, ganan el mismo resumen sin mensaje (para conservar elrequestId). El mensaje de unInvalidSignatureException/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ámetroX-Amz-*de una URL prefirmada o id de clave de acceso.statusCode,awsCode,retryAfteryquotaCodeno 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
HOMEen S3 (m7-s3-persistence, ADR-009):S3Prefix({ bucket, prefix, name, region }),Sandbox.create({ persist, persistTimeoutMs })(exigeexecutionRoleArn; connamerestaura ensandbox.lastRestore),Sandbox.connect(id, { persist }),sandbox.persist,checkpointFiles({ target, exclude, timeoutMs, onProgress })→CheckpointResult,restoreFiles({ source, timeoutMs, onProgress })→RestoreResult(NotFoundErrorsin checkpoint) yreincarnate({ exclude, persistTimeoutMs }); nuevosPersistenceError(code),CheckpointProgress,RestoreProgress,LaunchOptions,DEFAULT_PERSIST_TIMEOUT_MS;create({ pool })rechazapersist. runCode(code, { language })ycreateCodeContext({ language })aceptanpython,bashyjavascript(aliasjs, sin distinguir mayúsculas;m7-poly-kernels):languageselecciona el contexto por defecto de ese kernel (default-bash), creado por el agente en la primera celda, y es excluyente concontext(InvalidArgumentError). El kernelbashsólo lo trae la variante de imagenrayito-base-poly;javascriptes un nombre reservado que hoy ninguna imagen trae (AWS_API_NOTES.mdQ57). Un kernel que la imagen no trae esInvalidArgumentError(Unimplemented) conrayito-base-polyen el mensaje;envspor 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 concreate(), asentados con una celda trivial, aparcados conpause({ wait: true })y entregados portake()conresumeMicrovmexplícito y el token de la plaza (singetMicrovmen la toma); fallback acreate()sin plaza; relleno con backoff 1 s → 60 s por los token buckets compartidos; reciclado antes del muro de 8 h y reconciliación conlistMicrovms; 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 endocs/site/docs/pool.md. metadataycpuTimeLimitenSandbox.create()(el mismorunHookPayloadque Python:metadataylimits.cpu_seconds), ysandbox.launchInfo(laSandboxInfocon la que se abrió el handle).- Un endpoint con puerto explícito (
host:puerto) manda sobretransport.port(losraydfalsos por plaza de los tests).
Changed¶
- Licencia MIT → Apache-2.0 (
"license": "Apache-2.0"enpackage.json,LICENSEcon el texto Apache-2.0 yNOTICEincluidos en el tarball;pnpm pack:checklos 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 deHealthhastaagentReadyykernelReady),connect,kill,list,getInfo,isRunning,getHost(port)con las cabeceras del proxy,getHealth,getMetrics;idle(auto-suspensión a los 300 s por defecto);timeoutMscomo vida máxima del MicroVM (tope 8 h);await usingpara terminar al salir del bloque. - Comandos:
sbx.commands.runen foreground y background,onStdout/onStderr,stdin,list,kill,connect(pid, { fromSeq }),sendStdin,closeStdin;CommandHandleconwait,kill,disconnect, iteraciónfor awaity reconexión. - Ficheros:
sbx.files.read(text/bytes/stream),write,writeFilesen un solo stream,list({ depth }),exists,getInfo,remove,rename,makeDir,watchDirconWatchHandle. - Código:
sbx.runCodesobre kernels Jupyter con estado,Executionconresults(mime bundles y charts de E2B),logsyerrorcomo 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) yreconnectTimeoutMs. - Red y rol:
executionRoleArn(ninguno por defecto),ingress/egresscon nombres gestionados (ALL_INGRESS,INTERNET_EGRESS, ...) o ARNs de conectores propios;getHealth()exponeagentVersion,resumeGeneration,clockOffsetMsykernelStateLost. - Empaquetado: ESM + CJS con
tsdown, tipos.d.mts/.d.cts,exportsmap, tarball comprobado (scripts/pack-check.mjs),limits.tsgenerado desdelimits.json(scripts/gen_limits.py).
Requisitos de imagen¶
runCodenecesitarayito-base≥ 7.0 (sidecar de kernels);ptyy la reconexión traspause()necesitan ≥ 10.0 (M5). La aceptación de 0.0.5 corrió sobrerayito-base16.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 })), sincpuTimeLimitni campos de endurecimiento enHealth: 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¶
ctutils0.4.4 enCargo.lock(RUSTSEC-2026-0385: máscaras y comparacionesu128que no eran de tiempo constante; dependencia transitiva de la criptografía derayd).tokio1.53.3 (RUSTSEC-2026-0386: los constructores seguros deAsyncFdaceptaban cualquierAsRawFdsin garantizar la seguridad de E/S). La PTY registra suOwnedFdconAsyncFd::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_bashteclea 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 unsleep 60, en vez de 1,5 s de reloj. El fin de la terminal (end_of_terminal) se extrae desupervisecon 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¶
rustls0.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 fallarcargo deny. Sólo cambianCargo.toml(el pin exacto del workspace) yCargo.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
SendSignalde un proceso conStartRequest.kill_treealcanzan a todo su árbol. Hasta ahora sólo llegaban al grupo del proceso (killpg), y un descendiente que hicierasetsido 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,falsepor defecto) el proceso es child subreaper de sus descendientes; el timeout mandaSIGTERMa 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; elEndEventllega después. UnSIGKILLcongela 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):raydenlaza estáticamente ~220 crates (MIT, Apache-2.0, BSD, ISC, Unicode-3.0, Zlib y el CC0 denotify) cuyas licencias piden que el aviso viaje con el binario.THIRD_PARTY_LICENSES.mdlo generamake licensescon cargo-about 0.9.2 (about.toml, misma lista quedeny.toml) desdeCargo.lockcada 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 quecargo treecompila enraydo si lista uno que el.dep-v0del binario no registra (scripts/check_third_party_licenses.py).LICENSE,NOTICEyTHIRD_PARTY_LICENSES.mdviajan enlicenses/derayito-image.zip, en/usr/share/doc/rayd/de la imagen y como assets de la release, ySHA256SUMSse firma (SHA256SUMS.sigstore.json), con lo que la firma cubre también los avisos y el SBOM.
Fixed¶
- Los tests de
adapters::exec_postureque 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, elclose_rangede uno marcabaFD_CLOEXECel descriptor de otro entre suopeny su aserción, ya_posture_inheriting_a_slot_seals_only_above_itfallaba 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 deefs-proxyy la decisión de/resume, todo puro) y el adaptador realEfsUtilsMounter(mount -t efs -o tls,iam,accesspoint, medido en AWS real,AWS_API_NOTES.md§16 Q128), activo sólo en una imagen conamazon-efs-utilsyCAP_SYS_ADMIN(en las demásHealth.features.efs_volumesesfalsey la sección respondeUNSUPPORTED). Monta sobre un directorio de root y enlaza el resultado a la ruta pedida sin seguir enlaces simbólicos (recorrido compartido conmounts=,adapters::mountpoint); termina elefs-proxyde cada volumen al desmontar y en/terminate(umountno lo para, Q128); en/resumeremonta 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 enConfigureStatus; Q129); en/suspendvacía cada volumen con plazo y lo marcadegraded/flush_timeoutsi no termina (Q130).proto/rayito/v1/efs_volumes.protodocumenta las clases nuevas (invalid_path,credentials_expired,flush_timeout,stale,unreachable,gone).
Changed¶
EfsUtilsMounterpasaAWS_REGION(la región del MicroVM) al entorno demount -t efs:amazon-efs-utils3.1.3 la lee antes que suefs-utils.conf, así que una imagen sin región horneada monta en cualquier región. SinAWS_REGION, el helper sigue con sus propios respaldos.image/Dockerfile: capa condicional deamazon-efs-utils-3.1.3-1.amzn2023(sólo con el marcadorefs_variantde--with-efs), que rehace el enlace de/usr/bin/python3a 3.12;scripts/check_pins.pyclava su NEVRA y ve losdnf installdentro deif …; 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 deFilesystemService(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 enproc,sysfsodevptsse 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,
raydsó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 deallowa través de un upstream que quite parámetros;o decodifique dos veces. Una ruta deallowque ninguna petición pueda cumplir se rechaza enConfigure(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óloPATH, un binario absoluto, el sellado de descriptores y el restablecimiento de señales que ya tenían los procesos del usuario;mount-s3gana el mismo sellado por encima de su descriptor FUSE. - Un
/rundesde un proceso del sandbox ya no consume el/rundel arranque (RAYD-08):raydlee el uid dueño del socket del llamante y rechaza (200sandbox_origin, contado enhook_anomalies) un/runde un uid del sandbox, así que elstart_cmdde una plantilla, que corre antes de que llegue el/runde 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
Hostdel proxy de egress (RAYD-06): sólo cubre la forma absolutahttp://; un túnelCONNECT/SOCKS5 a un nombre permitido en el 80 comparte el riesgo residual de IPs compartidas que ya tenía el 443 (SECURITY.mdT17). - Los puertos de
raydtienen 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 deraydabriendo conexiones ociosas. Los hooks cortan una cabecera que no llega en 10 s y cierran la conexión tras cada respuesta, y unacceptque falla por falta de descriptores espera 100 ms en vez de reintentar en bucle (SECURITY.mdT7). Pendiente de aceptación en AWS real. raydcomprueba 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í querayd(root) sólo registra un kernel que/procconfirma 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 cadakillpg, que nunca va a los grupos 0 y 1 ni al suyo propio. Un pid que no pasa se descarta (kernel_pid_rejected) (SECURITY.mdT12).- 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.mdT1). - Los hooks de ciclo de vida comprueban quién abrió la conexión
(
sec-sandbox-isolation, C-01):raydbusca el extremo cliente de cada conexión al puerto de hooks en las tablas de sockets del kernel. Un/terminateo un/validateque llega desde un proceso del sandbox (uid 1000-65535) responde 200peer_refusedsin hacer nada y cuenta enhook_anomalies; un/suspendo un/resumedesde el sandbox se sigue aceptando pero también cuenta. Ningún hook responde nunca un no-2xx por esta comprobación. /validatey/readyya no actúan tras el/run(C-02, C-03): un/validateposterior al/runresponde 200validate_skippedsin reiniciar el contextodefaultdel kernel ni ejecutar la celda de validación, y tanto él como un/readytardío quedan enhook_auditcomo 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 elstream_gate) se niega por sí solo fuera de la fase de build, aunque se llegue a él sin pasar por el hook.CheckpointyRestorequedan dentro del ámbito que liga el/run(C-07): si elrunHookPayloadtrae el bloquepersist(bucket y base del prefijo, lo manda el SDK desdecreate(persist=)),raydrespondePERMISSION_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
upstreamque 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
raydse construyen sin credenciales y no se pueden reemplazar desde la release.release.ymlparte el jobraydenrayd-build(sólo lectura),rayd-sign(token OIDC, sin checkout ni herramientas de build, firma lo quesha256sum -cconfirma que salió del build) yrayd-upload(environmentreleasecon 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é enmain. - 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 tagrayd-v*(docs/site/docs/verify.md). - Firmar
raydespera la aprobación del mantenedor.rayd-signcorre en el environmentreleasey sólo cuando el run publica: sin aprobación no hay token OIDC ni certificado de Sigstore con la identidadrelease.yml@refs/tags/rayd-v<versión>, y un ensayo (dry_run) ya no firma. Antes, un ensayo lanzado desde un tagrayd-v*firmaba con la identidad exacta que verifican los usuarios sin pasar por ningún revisor.verify.mddice ahora qué prueba la firma y qué no. - La receta recomendada de
rayito-image.zipverifica antes de publicar: descargaSHA256SUMS, comprueba el bundle concosign verify-bloby la suma, y sólo después llama arayito 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.lockdesfasado; pip-audit y cfn-lint salen de requisitos con--hashen vez deuvx.
0.6.1 - 2026-10-04¶
Fixed¶
rayd(PID 1) recoge los zombis huérfanos (rayd-orphan-reaper, Q80): un demonio con doblefork(omount-s3en modo daemon) ya no deja procesos<defunct>colgados derayddurante toda la vida del sandbox. Todo hijo derayd(procesos, PTYs, el sidecar de kernels,mount-s3, sondas internas) se lanza por un únicoChildRegistrypor proceso que lo registra con su pid y su hora de arranque antes de que ninguna pasada de recogida pueda verlo; el reaper, en cadaSIGCHLDy cada 5 s, sólo hacewaitpidsobre los zombis reasignados araydque no son suyos, así que los códigos de salida de comandos, PTYs, kernels ymount-s3nunca se pierden. Sólo se activa como PID 1 (o child subreaper).clippy.tomlprohíbe lanzar procesos fuera deChildRegistry::spawn.
0.6.0 - 2026-10-03¶
Added¶
ConfigureServicey los seis slots de función 0.6 (v06-foundations, M15 foundations, ADR-015): el servicio gRPCConfigure/ConfigureStatus, el orden de aplicación fijo (eventos → telemetría → pasarela → montajes S3 → volúmenes EFS),Health.features(AgentFeatures, campo 16,configure=truey el restofalsehasta que su propia función construye un adaptador real) yLifecycleParticipantpara que una función futura participe en/suspend//readysin 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 enmain.rs(ver MILESTONES.md M15). El registro de.protopasa a descubrirse por glob (crates/rayito-proto/build.rs). Sin ninguna sección deConfigureSandbox, el comportamiento es idéntico al de 0.5.x.- Un único
FeatureSetpor proceso y participantes acotados (v06-foundations§15, ADR-015):maincomparte unArc<FeatureSet>entreConfigureService,Health.features(derivado desupported()de cada slot) y los hooks (grpc::router_with_features)./suspend,/resumey/terminatellaman aon_suspend/on_resume/on_terminatede 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_mountsfeature slot (m15-s3-mounts, ADR-017):rayd_core::s3_mount(S3Mount,MountErrorClassincl.InvalidPath,validate_mounts/parse_allowed_buckets, portsFuseDevice/FuseDaemon) andrayd_core::mount_path(the absolute/canonical/allowed-roots/ no-overlap/max-count check, shared with a futureefs_volumes, re-run here before anything else so a non-SDK or buggy client can never steermount(2)outside/mnt///home/user/). The Linux adapters (adapters::fuse_device: the mountpoint is walked from/one component at a time withO_PATH|O_DIRECTORY|O_NOFOLLOW(missing ones created withmkdirat), any symlink isinvalid_path, andmount(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)withallow_otherplus a realprobe_ready(a bounded, killablestatsubprocess as the guest uid);adapters::mount_s3:mount-s3 --allow-other --uid 1000 --gid 1000(without--allow-otherMountpoint answers only its own uid: every guest access wasEACCES, AWS_API_NOTES.md Q101) run as the dedicatedrayito-mountuser, uid 990, with a from-scratch environment — no credential ever in argv or env, SEC-3 — its pid registered in the sharedChildRegistry, 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 realfeatures::s3_mounts::S3MountsFeature:apply()reportsSECTION_CODE_PENDINGimmediately and settles each mount toMounted/Failedin 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 stillPending;/resume's probe forces the same relaunch for a daemon that survived the snapshot but whose FUSE connection did not.supported()/Health.features.s3_mountsrequireCAP_SYS_ADMINin rayd's effective set (onlyrayito-base-capshas it; the binary,/dev/fuseand therayito-mountuser alone ship in every variant) instead of a literaltrue.Health.features.root_egressis now derived generically fromFeatureSettoo (a newConfigurableFeature::root_egress_class(), reported only for asupported()slot), so later features add nothing togrpc/health.rs.s3_mounts.protonow carries real fields (S3Mount,S3MountState,S3MountPhase). With noS3MountsConfigsection sent, behaviour is unchanged from 0.5.x: no/dev/fuseopen, nomount-s3spawn.- Eventos de ciclo de vida firmados (
m15-events-webhooks, ADR-020):raydemitecreated/paused/resumed/killed{reason: request}como una línearayito.event.v1 <b64url(json)> <b64url(hmac-sha256)>en su propio stdout, sólo cuandoConfigureSandboxtrae unaLifecycleEventsConfigcon clave (la deriva y empuja el SDK;raydnunca ve el secreto del stack). Cola acotada y no bloqueante;pausedykilledesperan a que la línea salga a stdout (barreraflushen la misma cola) dentro de la cuota de/suspendy del tope de/terminate. Si la fuente aleatoria falla, el evento se descarta y se cuenta (random_unavailable). El estado vive en elFeatureSetúnico del proceso. Sinevents=, cero líneas y cero coste (el participante de esta función comprueba su propio estado antes de hacer nada, y no espera ningúnflushsi no encoló nada). - Exportador OTLP/HTTP a CloudWatch (
m15-rayd-otlp, ADR-021):raydexporta 7 gauges de CPU, memoria y disco cadainterval_s(15-300 s) sobre eltelemetry_exportdeConfigureSandbox, 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/suspendcon un vaciado de hasta 2 s que nunca pierde puntos aunque el hook lo corte, y/resume(suon_resume, bajo el tope por participante de los hooks) rehace el pool de conexiones sin esperar envíos previos. Firma SigV4 conaws-sigv4(ya enCargo.lock), corrigiendo el reloj del guest con la cabeceraDatede AWS; persistencia y funciones 0.6 comparten un único proveedor de credenciales IMDS. Leetraceparententrante y lo registra comotrace_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(yroot_egress) sólo sontrueconAWS_REGIONconocido. Sintelemetry=,raydno 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 slottemplate_start: al arrancar,raydlee/etc/rayito/template.json(adapters/fs_template_spec.rs; ausente o inválido = arranque de 0.5.x) y, si existe, antes del/readydel build lanzastart_cmdcomo proceso gestionado (visible encommands.list) y sondeaready_cmdcon/bin/sh -c(adapters/shell_ready_probe.rs);/readyresponde 503 hasta queready_cmdsale con 0 y falla al agotarse su plazo.Health.features.template_startpasa atrue. - Pasarela de secretos en loopback (
m15-secrets-gateway, M15, ADR-023): el slotsecret_gatewaydeja de serUnsupported. Un listeneraxumpor ruta deSecretGatewayConfig(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 alupstreamfijo de la ruta por un cliente HTTPS compartido (GatewayUpstream: raíz de confianza del SO,FilteringResolverpara que unupstreamnunca 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, nuncahost/content-length/ hop-by-hop:invalid_header_name/duplicate_header_name), igual que el nombre de ruta (invalid_route_name) y elupstream(invalid_upstream_host). UnConfigureque 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 unCONNECT_TIMEOUT(10 s) oRESPONSE_HEAD_TIMEOUT(600 s) esupstream_timeout; cualquier otro fallo tras conectar esupstream_error.Health.features.secret_gatewaysale delFeatureSetconstruido: si el slot degrada aUnsupported(sin raíz de confianza), no se anuncia. Sin ningúnConfigureconsecret_gateway,raydno 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¶
/suspendya no puede colgarse ensync(2): el paso 4 del checklist llamaba async(2)sin plazo, y un montaje de red o FUSE colgado (o un disco muy lento) lo dejaba en estadoD; un/suspendque no responde a tiempo hace que AWS termine elMicroVM. Ahora se hace unsyncfs(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 deSuspendBudget(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/suspendmientras siga colgado, y el plazo vencido se registra sólo con contadores (nunca rutas). La regla vive enrayd_core::suspend_sync; las syscalls, enadapters::bounded_sync.
0.4.0 - 2026-09-30¶
Cambios que rompen¶
LifecycleService.SetTimeoutcontimeout_ms=0: la regla del timeout es sólo del dominio, que juzga primero la fase. Un sandbox sin plazo respondeFAILED_PRECONDITION lifecycle_unmanaged(antesINVALID_ARGUMENT "timeout_ms must be positive") y uno terminandoFAILED_PRECONDITION sandbox_timeout; con plazo activo, 0 ms da el mismoINVALID_ARGUMENTque 1-999 ms. Migración: ninguna para los SDK (validan >= 1 s antes del RPC); un cliente gRPC propio debe tratarlifecycle_unmanagedcomo antes trataba elINVALID_ARGUMENT.- Los mensajes de error del agente (status gRPC,
StreamError.messagey motivo del/runrechazado) pasan al español;timeout below 1 ses ahorael 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 enrayd_core::wire_tokens. Migración: quien compare mensajes por texto debe usar el código gRPC o esos tokens.
Changed¶
RegistryError::Full/Unknownse convierten enTransferError(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 arayd-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 deSystemTimea milisegundos Unix (clock::unix_millis, reexportada desdemetrics) sustituye la copia privada del hilo del plazo. - Egress (refactor interno, sin cambio de comportamiento): los casos de uso
de egress de
/runy/resumepasan del adaptador de hooks aNetworkManager(on_run/on_resume); el estado instalado (política, plan, slot, guardia DNS) vive enrayd-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 deNetworkErrora código gRPC es exhaustivo (status_class); la tabla y prioridad de IMDS son constantes tipadas con su invariante de orden;ConnectFailurey sus respuestas HTTP/SOCKS pasan arayd-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 enrayd-core(RequestRetries); el plan de una exportación lleva la URL de cada parte y unUploadPartcorrecto lleva siempre suETag(PutOutcome::PartStored).
Security¶
- Imagen: las dos capas
pip install -rdeimage/Dockerfile(el stack científico principal y la variantepoly) llevan ahora--require-hashes --no-deps --only-binary=:all:.kernel-sidecar/requirements.txtyrequirements-poly.txtse regeneraron conuv pip compile --generate-hashessin cambiar ninguna versión (diff denombre==versiónvací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 enscripts/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 derayd. El runner no se congelaba porrayd: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 (nitimeoutni 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 endocs/research/2026-10-ci-x86-freeze.md.
0.3.2 - 2026-09-29¶
Fixed¶
-
Imagen:
e2b/dataconvierte los valores ausentes (None,NaN,NaT,pd.NA) ennull. UnDataFramecon unNaTjunto 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 reglasip ruleinstaladas 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. /runmarca 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 aSIG_DFLy vacían la máscara antes deexec: unSIGHUPignorado por el padre ya no impide que el job en primer plano muera con la shell. ci.yml(jobbuild) yrelease.yml(jobrayd) compilanraydcon el mismo--remap-path-prefixquemake build(REMAP_CONFIGdel Makefile): el binario publicado ya no incrusta las rutas del runner (/home/runner/...) deCARGO_HOMEni del directorio de compilación. Un paso nuevo en ambos jobs falla el build sistringsencuentra/home/runneren 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,WatchTransferyCancelTransfer;raydmueve 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/Readcon gzip opcional (rayito-compress: gzip) y metadatos por fichero como xattrsuser.rayito.*.LifecycleService.SetTimeoutyHealthResponse.lifecycle(campo 12) (m9-server-timeout, ADR-011): el plazo lógico del bloquelifecycledelrunHookPayload, vigilado por el hilorayd-timeout; al vencer en modokill, streams cerrados consandbox_timeout,SIGTERM/SIGKILLa todos los grupos y salida con código 124; la capatimeout_gaterespondeFAILED_PRECONDITION sandbox_timeouta todo RPC salvoHealthySetTimeout.HealthService.MetricsHistory,MetricsResponse.mem_cache_bytesyHealthResponse.cpu_count/memory_total_bytes(campos 14 y 15) (m9-sandbox-observability): muestreo procfs cada 5 s sólo enrunning/resumed, anillo de 5 760 muestras.NetworkService(UpdateNetwork,GetNetwork) yHealthResponse.egress_enforcement(campo 13) (m9-egress-policy, ADR-012): política de egress en el guest conCAP_NET_ADMIN(tablas de rutas poruidrange 1000-65535, cambio atómico, verificación), proxy local HTTP CONNECT/SOCKS5 en127.0.0.1con guardia contra IMDS y las direcciones propias del guest, cadena al SOCKS5 del operador, deny-all en/runconnetwork.enforcey re-verificación en/resume.language = "typescript"enCodeService(m9-deno-kernels): el catálogo del agente conoce los cuatro nombres; en una imagen sin el kernel,UNIMPLEMENTEDnombrandorayito-base-poly.
Fixed¶
- Un
Writeen un directorio observado llega comoWRITE(m9-e2b-v2-surface): el rename del temporal.rayito-tmp-*sobre el destino se emparejaba como unRENAMEsinentry, y el código portado de E2B que esperaWRITEtrasfiles.write(el ejemplo de docs.e2b.dev) no veía nada.WatchTranslatorempareja las dos mitades del rename por su cookie de inotify (RawWatchEvent.cookie) y emite un únicoWRITEdel destino, conentrysi se pidióinclude_entry. Losmv/files.renamecorrientes siguen siendoRENAME. - El plazo en milisegundos ya no oscila:
deadline_unix_msycap_unix_ms(enHealth.lifecycley en el detalle deSetTimeoutmá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). Healthno diceagent_readymientras se instala el deny-all de/run(egress_settling): la plataforma deja pasarHealthantes del 200 de/run, y connetwork.enforceel SDK podía dar por listo un sandbox cuya política aún no estaba aplicada (el primeripde un guest recién restaurado tarda segundos).agent_readyespera a que la política quede publicada, verificada o no; una imagen sinCAP_NET_ADMINqueda lista tras/runcomo 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_restartsni 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.ipnunca 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+/resumeforjado, la gracia de 30 s podía reabrirse una y otra vez y, en modokill, 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
Hosten la forma absolutahttp://(m9-egress-policy): conservaba elHostdel cliente, así que un nombre permitido podía servir de fachada para otro host virtual de la misma IP. Ahora elHostes la autoridad comprobada (RFC 9112 §3.2.2) y las líneas plegadas (obs-fold) se rechazan con400.CONNECTy 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, conIpv6Addr::to_ipv4como la política de URLs de transferencia): antes sólo::ffff:a.b.c.d, y::169.254.169.254o::127.0.0.1pasaban la guardia.::y::1siguen siendo IPv6. Healthya no puede quedarse sinagent_readypara siempre: si la tarea del deny-all de/runentraba en pánico,egress_settlingno se limpiaba nunca; ahora lo limpia un guardián al terminar la tarea de cualquier modo. Además cada invocación deiptiene un tope de 5 s (IP_COMMAND_TIMEOUT, por encima de los 2,76 s medidos para el primeriptras un restore): pasado, el hijo se mata y se recoge y la llamada falla comotimed out, en vez de retener el lock del gestor de egress.
Dependencies¶
zlib-rsaparece enCargo.locky en el SBOM, pero no se enlaza (m9-file-transfer, ADR-010): la featuregzipdetonic0.14.6 pideflate2con sus features por defecto, y la feature débilruntime_detection(zlib-rs?/std) basta para que Cargo anote la dependencia opcionalzlib-rsen el lockfile aunque nadie la active. El workspace ya fijaflate2condefault-features = falseyrust_backend, pero las features son aditivas y no pueden quitar eldefaultque pidetonic, así que el lock no cambia. El binario enlaza sólominiz_oxide(cargo tree -i zlib-rsno encuentra la crate en el grafo resuelto).
Security¶
user=sólo acepta cuentas sin privilegio (auditoría interna, fila C-05):UserPolicy::authorize_identityera una lista negra de uid 0, así que un alias de uid 0, una cuenta de sistema con uid < 1000 —fuera deluidrange 1000-65535con el que M6 agujerea IMDS— o cualquier miembro del gruporootpasaban la puerta. Ahora la comprobación es positiva (uid >= 1000 && gid >= 1000y sin el grupo 0) y devuelvePrivilegedAccount. La puerta duplicada depersistencedesaparece: 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:
openptydevuelve master y slave heredables yraydsólo los marcaba close-on-exec unas instrucciones después; un spawn concurrente (otro PTY, un proceso o el sidecar) que hicieraforken 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 listaba0 1 142 145 2 255 3). Ahora elPreExecPlancomún a procesos, shells PTY y sidecar marca close-on-exec todo descriptor >= 3 en el hijo justo antes deexec(close_range(3, ~0U, CLOSE_RANGE_CLOEXEC), con un bucle acotado defcntl(F_SETFD)hasta elNOFILEblando, máximo 4096, si el kernel es anterior a 5.11), después de questdhaya colocado stdio en 0/1/2. Cubre también los descriptores queraydhereda 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.CheckpointyRestore(m7-s3-persistence, ADR-009): tar.gz delHOMEdel usuario (lista de exclusión fija,exclude≤ 64, lectura bajoFsIdentityGuard) subido as3://<bucket>/<key_prefix>/(home.tar.gzen partes de 8 MiB +manifest.jsonv1) y restaurado sin salir delHOME(sólo regular/dir/symlink, modo0o777, sha256 verificado), con las credenciales IMDSv2 del execution role como root (--persistence-credentials imds|default,imdspor defecto), una operación por sandbox (FAILED_PRECONDITION),progress≤ 1/s,KeepAlivecada 30 s y cierresuspendingen/suspendcon abort del multipart. Nuevo módulorayd-core::persistence, adaptadoresS3ObjectStore(aws-sdk-s31.148.0 +aws-config1.12.0,rustls+aws-lc-rscompilados conzig cc) yTarHomeArchiver(tar0.4.46 +flate21.1.10rust_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,bashojavascriptseleccionan el contexto por defecto de ese kernel (default,default-<language>), queraydcrea perezosamente en la primera celda bajo un lock por lenguaje (nunca antes de/run; cuenta para el tope de 8;DestroyContextpermitido y recreación en la siguiente celda).CreateContextRequest.languageacepta los tres nombres. Hoy sólorayito-base-polytrae el kernelbash;javascriptes un nombre reservado que ninguna imagen trae (AWS_API_NOTES.mdQ57). Nuevos estados:INVALID_ARGUMENTparalanguagejunto acontext_idy paraenvspor ejecución en un contexto no Python;UNIMPLEMENTED(mensaje conrayito-base-poly) para un lenguaje conocido que la imagen no trae, segúnready.languagesdel sidecar.- Protocolo del sidecar (v1, aditivo):
languageencreate_context,languagesenready(ausente = sólo Python) yskippeden la respuesta dereseed(contextos no Python, logueado como cuarto contador).ListContextsinforma el lenguaje real de cada contexto.
Changed¶
- Licencia MIT → Apache-2.0 (
license = "Apache-2.0"en[workspace.package], heredada por los tres crates;LICENSEjunto al crate).
Compatibilidad¶
Health.agent_versiones0.2.0. Los SDKs 0.2.0 (Python y TypeScript) exigenagent_version≥ 0.2.0 (Checkpoint/RestoreyExecuteRequest.language):rayito doctorlo evalúa con la tabla dedocs/site/docs/limits.md; sobre unrayd0.1.0persist=respondeUNIMPLEMENTEDylanguage=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 enMILESTONES.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 sinx-access-token:agent_ready,kernel_ready,agent_version,uptime,sandbox_id) yMetrics; hooks/ready,/validate,/run,/terminatebajo/aws/lambda-microvms/runtime/v1/; el secreto del sandbox llega como hash enrunHookPayloady 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 conoutput_truncated, máximo 256 procesos/PTYs;HealthService.Metricsdesde procfs.- M3 — ficheros:
FilesystemService(Readen stream,Writeen stream,Stat,ListDir,MakeDir,Move,Remove,WatchDir) con lista de denegación sobre la ruta canónica,..rechazado,setfsuid/setfsgidpor operación,O_NOFOLLOW, escrituras a temporal confchown. - M4 — código:
CodeService(CreateContext,Execute,Reattach,ListContexts,DestroyContext,RestartContext) sobre el kernel sidecar Python (hijo derayd, JSON lines por stdio; ADR-002): reinicio del sidecar con backoff,/runreinicia 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) conopenpty+setsid+TIOCSCTTYcomo uid 1000 (ADR-005); hooks/suspend(cierra streams consuspending,quiesce+sync, no destruye nada) y/resume(resume_generation, sonda de kernels, reseed derandom/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/suspendestancado (Health.hook_anomalies),RLIMIT_CPUopcional por proceso (limits.cpu_secondsdel payload), presupuesto de salida de 128 MiB por sandbox, reserva de disco de 256 MiB (disk_full),metadatadel payload devuelto enHealth.
Compatibilidad¶
Health.agent_versiones0.1.0(elCARGO_PKG_VERSIONdel crate). Los SDKs 0.1.0 (Python) y 0.0.5 (TypeScript) exigen una imagenrayito-base≥ 10.0 (la primera con esteraydy 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ó sobrerayito-base16.0 yrayito-base-caps6.0).- Sin cambio de imagen
agent_versionno cambia: las versiones de imagen son números de build opacos de AWS, anotados enMILESTONES.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 aplicarayito doctores sóloagent_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.