Kernels¶
run_code ejecuta cada celda en un kernel de Jupyter con estado dentro del
MicroVM. El kernel se elige por celda con language; el contrato es el mismo para todos (ExecuteRequest.language,
CreateContextRequest.language) y la imagen decide qué kernels existen.
Lenguajes e imágenes¶
language |
Kernel | rayito-base |
rayito-base-poly |
|---|---|---|---|
python (por defecto) |
ipykernel con el stack científico, calentado antes de /ready |
sí | sí |
bash |
bash_kernel 0.10.0 (pexpect sobre bash), arranque perezoso |
UNIMPLEMENTED |
sí |
javascript (alias js) |
kernel Jupyter de Deno 2.9.7, arranque perezoso | UNIMPLEMENTED |
sí |
typescript (alias ts) |
el mismo binario de Deno con su kernelspec TypeScript, arranque perezoso | UNIMPLEMENTED |
sí |
Los nombres se normalizan en el SDK (Bash, JS, TypeScript, ts valen);
en el cable sólo viajan python, bash, javascript y typescript.
Cualquier otro (r, java, tsx) es InvalidArgumentException /
InvalidArgumentError antes de llamar al agente; en el shim rayito.e2b es
UnimplementedError, como en E2B para lo que no existe. En una imagen que no
trae el kernel el agente responde UNIMPLEMENTED nombrando
rayito-base-poly (ver abajo).
Arranque perezoso y latencia de la primera celda¶
rayito-base-poly no calienta ni bash ni Deno antes de /ready: el
snapshot sigue conteniendo sólo el kernel Python caliente, así el
kernel_ready y el tamaño de memoria de la variante son los de
rayito-base. La primera celda de cada lenguaje crea su contexto por defecto
(default-bash, default-javascript, default-typescript; el agente arranca
el kernel bajo un lock por lenguaje, así dos celdas concurrentes arrancan un
solo kernel) y paga ese arranque dentro de su propio timeout; el deadline
del stream (timeout + 15 s) ya lo cubre. Las cifras de bash medidas contra
AWS real están en AWS_API_NOTES.md Q57 (kernel_ready_s de la variante y
latencia de la primera y la segunda celda bash). Las de Deno están en Q77,
medidas en rayito-base-poly 5.0: la primera celda typescript o
javascript de un sandbox nuevo tarda 0,5-0,75 s (incluye arrancar
Deno) y las siguientes ≈ 0,1 s. Justo después de publicar la imagen, las
primeras VMs llegaron a tardar 10-16 s en esa primera celda (lectura en frío
del binario de Deno); si esa espera importa, lanza una celda trivial
(run_code("0", language="typescript")) al crear el sandbox. Cada kernel Deno
ocupa ≈ 200 MiB de RSS. javascript y typescript son dos kernels (dos
procesos Deno) si usas los dos; cada uno cuenta para el tope de 8 contextos,
así que elige uno.
Ejemplos¶
from rayito import Sandbox
with Sandbox.create(template="rayito-base-poly") as sbx:
out = sbx.run_code("echo hi", language="bash")
print("".join(out.logs.stdout)) # "hi\n"
ctx = sbx.create_code_context(language="bash", envs={"MODE": "dev"})
print("".join(sbx.run_code("echo $MODE", context=ctx).logs.stdout)) # "dev\n"
print([c.id for c in sbx.list_code_contexts()]) # ["default", "default-bash", "ctx-…"]
import { Sandbox } from "rayito";
const sbx = await Sandbox.create({ template: "rayito-base-poly" });
const out = await sbx.runCode("echo hi", { language: "bash" });
console.log(out.logs.stdout.join("")); // "hi\n"
const ctx = await sbx.createCodeContext({ language: "bash" });
console.log((await sbx.runCode("echo $HOME", { context: ctx })).logs.stdout.join(""));
await sbx.kill();
Reglas:
languageycontextson excluyentes (INVALID_ARGUMENT): un contexto ya tiene su lenguaje;languagesólo selecciona el contexto por defecto de ese kernel.envspor ejecución sólo en contextos Python (INVALID_ARGUMENTen el resto: las celdas silenciosas que fijan y restauran variables son código Python). Losenvsdecreate_code_contextsí valen para cualquier lenguaje: son el entorno del proceso del kernel.default-bashes un contexto normal: cuenta para el tope de 8, se puede reiniciar y destruir (remove_code_context("default-bash")); la siguiente celdabashlo vuelve a crear. Sólodefault(Python) está protegido.list_code_contexts()informa el lenguaje real de cada contexto.
JavaScript y TypeScript con Deno¶
from rayito import Sandbox
with Sandbox.create(template="rayito-base-poly") as sbx:
print(sbx.run_code("const x: number = 40 + 2; x", language="typescript").text) # "42"
print(sbx.run_code("x + 1", language="ts").text) # "43"
print(sbx.run_code("let y = [1, 2, 3].map((n) => n * 2); y", language="js").text)
out = sbx.run_code('Deno.jupyter.html`<b>hola</b>`', language="typescript")
print(out.results[0].html) # "<b>hola</b>"
ctx = sbx.create_code_context(language="typescript", envs={"MODE": "dev"})
print(sbx.run_code('Deno.env.get("MODE")', context=ctx).text) # el valor de MODE
sbx.run_code('import { camelCase } from "npm:lodash-es@4"; camelCase("hola mundo")',
language="typescript")
import asyncio
from rayito import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create(template="rayito-base-poly") as sbx:
out = await sbx.run_code("const x: number = 40 + 2; x", language="typescript")
print(out.text) # "42"
ctx = await sbx.create_code_context(language="javascript")
print((await sbx.run_code("[1, 2, 3].length", context=ctx)).text) # "3"
asyncio.run(main())
import { Sandbox } from "rayito";
await using sbx = await Sandbox.create({ template: "rayito-base-poly" });
console.log((await sbx.runCode("const x: number = 40 + 2; x", { language: "typescript" })).text);
console.log((await sbx.runCode("x + 1", { language: "ts" })).text);
const ctx = await sbx.createCodeContext({ language: "javascript" });
console.log((await sbx.runCode("[1, 2, 3].map((n) => n * 2)", { context: ctx })).text);
- Por qué Deno.
ijavascriptno instala enal2023-minimalARM64 sin compilador (Q57, abajo); Deno es un único binario enlazado contra glibc que trae su propio ZeroMQ y un kernel Jupyter (deno jupyter), y sirve los dos lenguajes (Q61). - El pin.
image/Dockerfiledescargadeno-aarch64-unknown-linux-gnu.zipde la releasev2.9.7con un sha256 fijado y comprobado consha256sum -c(scripts/check_pins.pylo exige,SECURITY.mdT10) y lo deja en/opt/rayito/deno/deno, de root. Subir Deno es cambiarDENO_VERSIONyDENO_SHA256(el.sha256sumoficial de la release), republicarrayito-base-polyy repetir su e2e; Dependabot no lo sigue. - TCP de loopback. Deno no sabe abrir endpoints
ipc, así que sus kernels escuchan en127.0.0.1con la clave HMAC del connection file (ADR-013 deARCHITECTURE.md); Python y bash siguen enipc. Consecuencia aceptada: un proceso del propio sandbox puede leer las salidas de las celdas Deno (SECURITY.mdT12). - Interrupción. El
timeoutinterrumpe con un mensajeinterrupt_requestdel protocolo (Deno no reenvíaSIGINT); si el kernel no queda idle en 5 s, el agente reinicia el contexto. Una celda Deno interrumpida no emite unerrorpropio: el agente sintetizaExecutionTimeoutigual que con Python. NO_COLORy ANSI. Los kernelspecs arrancan Deno conNO_COLOR=1,DENO_NO_UPDATE_CHECK=1y unDENO_DIRbajo elHOME; además el sidecar quita las secuencias ANSI deltext/plainde los resultados Deno (defensa en profundidad), así.textes42y no\x1b[33m42\x1b[39m.- Permisos. Deno corre con todos los permisos (
allow_all), igual que el kernel de Python como uid 1000:Deno.env,Deno.readTextFile, red. - Imports
npm:,jsr:yhttps:descargan código por el mismo egress que unpip install(con la misma política de red saliente). envspor ejecución sólo valen en Python: en Deno usa losenvsdecreate_code_context.
Lo que sigue siendo sólo Python¶
- Formateadores
e2b/chartye2b/data, la configuración de IPython y los scripts de arranque: un kernel bash emitestreamyerrory el agente los mapea alogs/errorcon las mismas reglas. - El calentamiento antes de
/readyy la rotación del kernel en/run. - El reseed de
random/numpy.randomtras/resume: los contextos no Python se listan comoskipped(su estado aleatorio es el del propio proceso).
Timeouts y errores en bash¶
El timeout sigue la regla genérica: al vencer el agente interrumpe el
kernel (SIGINT, que bash_kernel reenvía a su bash) y, si no queda idle
en 5 s, reinicia el contexto; en ambos casos la Execution trae
error.name == "ExecutionTimeout" y la siguiente celda funciona (medido en
Q57: sleep 30 con timeout=2 vuelve a los 5,8 s por la rama de reinicio,
porque el SIGINT no deja idle a bash_kernel en 5 s). Un comando que sale
con código distinto de cero termina con el error que emite bash_kernel:
nombre vacío y el código de salida como value (false →
ExecutionError(name='', value='1')); la salida sigue llegando por
logs.stdout/logs.stderr.
UNIMPLEMENTED en rayito-base¶
En una imagen sin el kernel, run_code("echo hi", language="bash") falla
antes de crear nada con UNIMPLEMENTED y un mensaje que nombra
rayito-base-poly; un kernel ausente es UnimplementedError tanto en Python
como en TypeScript (0.4.0: ya no InvalidArgumentException/
InvalidArgumentError, porque una feature ausente nunca es un error de
argumento). El agente sabe qué kernels hay porque el sidecar lo anuncia en
ready (languages, según los kernelspecs que pudo instalar al arrancar).
Por qué no ijavascript¶
El diseño original preveía ijavascript sobre Node 20 (dnf install nodejs20).
La prueba del 2026-09-16 (Q57) lo descartó: ijavascript@5.2.1 depende de
jmp@2, que sólo acepta zeromq@5, y zeromq@5.3.1 no publica binarios
precompilados para linux-arm64 ni para Node 20 (ABI 115: sus prebuilds
llegan hasta node.abi108, sólo darwin-x64, linux-x64 y win32); npm
cae en node-gyp rebuild, que muere con not found: make porque
al2023-minimal no trae compiladores. Desde 0.3.0, javascript y
typescript los sirve Deno, que no necesita nada de eso.
R y Java¶
Siguen fuera (SPEC.md §4), con números medidos: R por conda-forge
(r-base + r-irkernel) ocupa 1,4 GB instalado, y R-core por dnf 127 MB
más cairo, pango, harfbuzz, tk y fuentes; Java serían 262 MB de Corretto 21
headless más IJava, sin mantenimiento (una release desde 2023, no compila en
JDK 17/18).
Publicar la variante poly¶
Mismo Dockerfile, un marcador dentro del zip (kernel-sidecar/kernels_variant
con poly) que activa una capa condicional: instala los pines de
kernel-sidecar/requirements-poly.txt (bash_kernel y sus dependencias,
pip check), comprueba import bash_kernel como user, descarga y verifica
Deno 2.9.7 y comprueba deno --version como user. rayito-base no lleva
el marcador y no ejecuta nada de esa capa.
# Linux / macOS
make image-publish-poly BUCKET=<bucket> # publica rayito-base-poly
# Windows (sin make; toolchain de CONTRIBUTING.md §3)
cargo zigbuild --release --locked --target aarch64-unknown-linux-musl -p rayd
cp target/aarch64-unknown-linux-musl/release/rayd image/rayd
python scripts/copy_sidecar.py kernel-sidecar image/kernel-sidecar
python scripts/image_zip.py image image/rayito-image-poly.zip --variant poly
uv run --project clients/python python scripts/publish_image.py \
--artifact image/rayito-image-poly.zip --variant poly \
--base-image-version 1 --bucket <bucket>
publish_image.py se niega a publicar un zip cuyo marcador no coincida con
--variant. Los tamaños medidos de snapshotBuild de rayito-base-poly y
de rayito-base reconstruida con el mismo Dockerfile (dentro de la banda
de ±20 MB de memoria / ±10 MB de código respecto a 17.0) están en Q57.
Agentes antiguos¶
Agentes anteriores a M7 (rayd < 0.2.0)
ExecuteRequest.language es un campo proto3 opcional: un rayd anterior a
0.2.0 lo ignora y ejecuta la celda en el kernel Python del contexto por defecto
sin avisar. Usa una imagen 0.2.0 o posterior antes de
confiar en language; la tabla de compatibilidad SDK/agente se publica con
cada release (docs/RELEASING.md).
Agentes anteriores a M9 (rayd < 0.3.0)
Un rayd anterior a 0.3.0 no conoce typescript: responde INVALID_ARGUMENT
("language must be one of python, bash, javascript") y los SDKs lo entregan
como InvalidArgumentException / InvalidArgumentError (también a través del
shim). javascript contra ese agente sigue siendo UNIMPLEMENTED en toda
imagen. Publica rayito-base-poly desde una release 0.3.0 o posterior antes de usar JS/TS.