Changelog¶
Cada componente se versiona por separado, con la misma serie MAJOR.MINOR:
el SDK de Python, el de TypeScript y el agente rayd (la imagen). Qué
versión de rayd necesita cada SDK: Compatibilidad.
El formato sigue Keep a Changelog
y el versionado, SemVer.
Unreleased¶
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; en una imagen nueva, la primera celda derun_codeque importe el stack científico paga la importación (kernel_warmup=Truelo evita).
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¶
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; en una imagen nueva, la primera celda derunCodeque importe el stack científico paga la importación (kernelWarmup: truelo evita).
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¶
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.