Skip to main content

rayito (SDK Python)

Cliente Python de Rayito: sandboxes de ejecución para agentes de IA sobre AWS Lambda MicroVMs, dentro de tu propia cuenta. La ergonomía del SDK de E2B sin que el código de tus clientes salga de tu cuenta y sin clúster que operar.

from rayito import Sandbox

with Sandbox.create(template="rayito-base", timeout=900) as sbx:
    print(sbx.run_code("x = 40; x + 2").text)  # "42"

Documentación: docs/site (quickstart, conceptos, referencia de API, seguridad, límites, compatibilidad con E2B y modelo de costes; make docs la construye). Cambios por versión: CHANGELOG.md.

Novedades de 0.3.0 (M9)

Paridad con E2B 2.x; exige una imagen publicada con el rayd de M9 y está aceptada contra AWS real (2026-09-24). Cada bloque enlaza su página de docs/site/docs/; qué imagen necesita cada cosa, en images.md.

import requests
from rayito import ALL_TRAFFIC, AsyncSandbox, S3Staging, Sandbox

# Plazo del servidor (lifecycle.md): rayd lo impone aunque tu proceso muera
sbx = Sandbox.create(timeout=600, max_lifetime=7200, on_timeout="kill")
sbx.set_timeout(1800)  # exacto, hasta max_lifetime
sbx.connect(timeout=900)  # sólo alarga

# Historial de métricas y listado (observability.md)
samples = sbx.get_metrics_history(max_points=60)
pages = Sandbox.paginate(limit=20, order="desc")
first, cursor = pages.next_items(), pages.next_token

# Git (git.md)
sbx.git.clone("https://github.com/octo/demo.git", path="/home/user/demo", depth=1)
print(sbx.git.status("/home/user/demo").current_branch)
sbx.kill()

# Transferencias por S3 (files.md): URLs firmadas con tus credenciales
with Sandbox.create(transfer=S3Staging("amzn-s3-demo-bucket")) as sbx:
    ticket = sbx.files.upload_url("/home/user/in.csv", expires_in=900)
    requests.put(ticket, data=b"a,b\n1,2\n", headers=ticket.headers).raise_for_status()
    ticket.wait()
    link = sbx.files.download_url("/home/user/in.csv")
    sbx.files.write("/home/user/log.txt", "texto", gzip=True, metadata={"origen": "ci"})

# JavaScript y TypeScript con Deno (kernels.md), sólo rayito-base-poly
with Sandbox.create("rayito-base-poly") as sbx:
    print(sbx.run_code("const x: number = 40 + 2; x", language="typescript").text)

# Red saliente de E2B (network.md), sólo rayito-base-caps
with Sandbox.create(
    "rayito-base-caps", network={"deny_out": [ALL_TRAFFIC], "allow_out": ["api.example.com"]}
) as sbx:
    sbx.update_network({"deny_out": [ALL_TRAFFIC]})


# Todo lo anterior también en asyncio
async def main() -> None:
    async with await AsyncSandbox.create(timeout=600, max_lifetime=3600) as sbx:
        await sbx.set_timeout(1200)
        print(len(await sbx.get_metrics_history()))
# CLI (cli.md): create/connect/exec/metrics; el token nunca por argv
rayito sandbox create rayito-base --detach --token-file ~/.rayito/demo.token
rayito sandbox exec microvm-<id> --token-file ~/.rayito/demo.token -- python3 -c 'print(42)'

Si vienes de E2B, from rayito.e2b import Sandbox (shim 2.x, "Shim de E2B" más abajo, e2b-compat.md y e2b-parity.md). Cambios que rompen: CHANGELOG.md ("Changed (shim)").

Instalación

pip install rayito        # o: uv add rayito

Python ≥ 3.11; dependencias de runtime grpcio, protobuf y boto3.

Credenciales

El SDK usa las credenciales de AWS de la sesión de boto3 (perfil, variables de entorno o rol de la máquina); no hay API key.

export AWS_PROFILE=<perfil> AWS_REGION=us-east-1
export RAYITO_TEMPLATE=rayito-base            # nombre o ARN de la imagen

RAYITO_TEMPLATE evita pasar template= en cada create(). Cada sandbox arranca desde una versión de la imagen rayito-base (construida desde image/Dockerfile, publicada con make image-publish). El SDK 0.3 necesita una imagen publicada con el agente 0.3 (M9): sobre una anterior, las funciones de M9 lanzan UnimplementedError; rayito doctor lo comprueba.

CLI

El extra rayito[cli] instala el comando rayito (docs/site/docs/cli.md):

pip install "rayito[cli]"
rayito doctor --template rayito-base     # diez comprobaciones de la cuenta; --launch prueba un sandbox
rayito sandbox list                      # MicroVMs vivos; también info, kill, logs
rayito image publish --artifact image/rayito-image.zip --base-image-version 1 --bucket <bucket>

Crear y destruir

from rayito import IdlePolicy, Sandbox

sbx = Sandbox.create(
    timeout=1800,  # vida máxima (running + suspended), tope 8 h
    idle=IdlePolicy(max_idle_seconds=600),  # auto-suspensión; None la desactiva
    envs={"APP_ENV": "dev"},
    execution_role_arn=None,  # sin rol no hay credenciales dentro
)
print(sbx.sandbox_id, sbx.access_token)  # guárdalos para connect()
sbx.kill()  # terminate-microvm (o usa `with`)

again = Sandbox.connect(sandbox_id, access_token=token)  # desde otro proceso

Plazo del servidor (M9, ADR-011, exige una imagen M9): con max_lifetime u on_timeout, timeout es un plazo lógico que rayd hace cumplir aunque tu proceso muera, y max_lifetime (≤ 28 800 s, running + suspendido) el tope fijo de la plataforma.

sbx = Sandbox.create(timeout=600, max_lifetime=7200, on_timeout="kill")
sbx.set_timeout(1800)  # SetTimeout EXACT: alarga o acorta, hasta max_lifetime
print(sbx.get_info().expires_at)  # el plazo lógico
Sandbox.connect(sbx.sandbox_id, access_token=sbx.access_token, timeout=900)  # sólo alarga

Con on_timeout="pause" (necesita idle) el sandbox se suspende al vencer en vez de terminar. Contra una imagen anterior a M9 pedir un ciclo de vida es LifecycleUnsupportedException (subclase de UnimplementedError, la misma feature ausente que cualquier otra) y el VM se termina.

Comandos

from rayito import CommandExitException, Sandbox

with Sandbox.create() as sbx:
    result = sbx.commands.run("echo hola")  # foreground
    print(result.stdout, result.exit_code)  # "hola\n" 0

    sbx.commands.run("ls /", on_stdout=lambda line: print(line, end=""))

    server = sbx.commands.run("python3 -m http.server 3000", background=True, timeout=None)
    print(server.pid, [p.pid for p in sbx.commands.list()])
    server.kill()

    try:
        sbx.commands.run("exit 3")
    except CommandExitException as error:  # exit != 0 es excepción
        print(error.exit_code, error.stderr)

Ficheros

from rayito import Sandbox, WriteEntry

with Sandbox.create() as sbx:
    info = sbx.files.write("/home/user/data.csv", "a,b\n1,2\n")
    print(info.size, sbx.files.read("/home/user/data.csv"))
    raw = sbx.files.read("/home/user/data.csv", format="bytes")

    sbx.files.write_files(
        [WriteEntry("/home/user/a.txt", "a"), WriteEntry("/home/user/b.txt", b"b")]
    )
    print([entry.name for entry in sbx.files.list("/home/user")])

    with sbx.files.watch_dir("/home/user", on_event=print) as watch:
        sbx.commands.run("touch /home/user/new.txt")
        print(watch.get_new_events())

URLs de S3 y ficheros grandes (M9, ADR-010): el SDK firma con tus credenciales y rayd mueve los bytes; hace falta un bucket de transferencias (S3Staging o RAYITO_TRANSFER_BUCKET). Detalle, IAM y bucket en docs/site/docs/files.md.

import requests
from rayito import S3Staging, Sandbox

with Sandbox.create(transfer=S3Staging("amzn-s3-demo-bucket")) as sbx:
    ticket = sbx.files.upload_url("/home/user/in.csv", expires_in=900)
    requests.put(ticket, data=open("in.csv", "rb"), headers=ticket.headers)
    ticket.wait()  # o deja que la barrera espere
    link = sbx.files.download_url("/home/user/in.csv")  # una foto del fichero
    sbx.files.write("/home/user/big.bin", open("big.bin", "rb"))  # > 8 MiB: por S3
    sbx.files.write("/home/user/log.txt", "texto", gzip=True, metadata={"origen": "ci"})

run_code

with Sandbox.create() as sbx:
    sbx.run_code("x = 42")
    print(sbx.run_code("x").text)  # "42": el kernel tiene estado

    plot = sbx.run_code("import matplotlib.pyplot as plt; plt.plot([1, 2, 3]); plt.show()")
    print(plot.results[0].formats())  # ["png", "chart"]
    png_base64 = plot.results[0].png

    failed = sbx.run_code("1/0")
    print(failed.error.name)  # "ZeroDivisionError", nunca excepción

    ctx = sbx.create_code_context(cwd="/tmp")
    print(sbx.run_code("x", context=ctx).error.name)  # "NameError": otro kernel

    # Kernel bash (variante de imagen rayito-base-poly): arranca en la primera celda
    print("".join(sbx.run_code("echo hi", language="bash").logs.stdout))  # "hi\n"

language acepta python, bash, javascript (alias js) y typescript (alias ts); los tres últimos viven en rayito-base-poly (JavaScript y TypeScript con el kernel de Deno, M9). En rayito-base cualquier kernel distinto de Python es UnimplementedError, con un mensaje que nombra rayito-base-poly (docs/site/docs/kernels.md).

PTY

from rayito import PtySize, Sandbox

with Sandbox.create() as sbx:
    pty = sbx.pty.create(size=PtySize(cols=120, rows=40), on_data=print, timeout=None)
    pty.send_input("echo hola\n")
    for _, _, data in pty:  # (None, None, bytes) por chunk
        if b"hola\r\n" in data:
            break
    pty.resize(PtySize(cols=80, rows=24))
    pty.kill()

pause / resume

with Sandbox.create() as sbx:
    sbx.run_code("x = 42")
    sbx.pause()  # suspend-microvm: todo sigue vivo al otro lado
    sbx.resume()  # resume-microvm + Health con la generación nueva
    print(sbx.run_code("x").text)  # "42": el kernel conservó su estado
    print(sbx.get_health().resume_generation)  # 1

Los handles, watches y run_code que estaban abiertos se reenganchan solos la próxima vez que se leen; los unarios cortados se reintentan una vez; reconnect_timeout (60 s) acota cuánto espera el SDK al agente una vez que el MicroVM vuelve a RUNNING. Leer un handle en background nunca despierta un sandbox suspendido.

get_host

import httpx

with Sandbox.create() as sbx:
    sbx.commands.run("python3 -m http.server 3000", background=True, timeout=None)
    host = sbx.get_host(3000)  # un str: f"https://{host}" funciona
    response = httpx.get(f"https://{host}/", headers=host.headers)  # cabeceras del proxy de AWS

Metadatos

sbx = Sandbox.create(metadata={"owner": "ana", "run": "42"})
print(sbx.metadata)  # {"owner": "ana", "run": "42"}
print(sbx.get_info().metadata)
print(Sandbox.get_info(sbx.sandbox_id).metadata)  # desde otro proceso: un JWE + un Health

for item in Sandbox.list(metadata={"run": "42"}):
    print(item.sandbox_id, item.metadata)

metadata viaja en el runHookPayload (4096 caracteres junto a envs), es inmutable y no es secreto (cualquier principal que pueda acuñar un JWE para el sandbox lo lee por Health). Sandbox.list(metadata=...) filtra en cliente y es O(n): por cada sandbox RUNNING hace get-microvm + create-microvm-auth-token + un Health (≈ 0,5-1 s por sandbox) y cada sonda cuenta como tráfico para su política de idle; nunca sondea un sandbox suspendido (states sólo admite RUNNING). Filtra por template antes si tienes muchos.

Listado reanudable e historial de métricas (M9):

from datetime import datetime, timedelta, timezone

pages = Sandbox.paginate(limit=20, order="desc")
first = pages.next_items()
more = Sandbox.paginate(next_token=pages.next_token).next_items() if pages.has_next else []

with Sandbox.create() as sbx:
    since = datetime.now(timezone.utc) - timedelta(minutes=10)
    for sample in sbx.get_metrics_history(start=since, max_points=60):  # una muestra cada 5 s
        print(sample.timestamp, sample.cpu_used_pct, sample.mem_used_bytes)

order se calcula en cliente (recorre todas las páginas antes del primer item); el historial tiene un hueco mientras el sandbox está suspendido.

Secretos (opcional, con coste)

Apagado por defecto: sólo secrets=/secret_cache= o SecretStore(...) lo encienden (sin ellos no se construye ningún cliente de Secrets Manager). Guarda secretos en AWS Secrets Manager de tu cuenta ($0,40/secreto-mes hasta destroy + $0,05/10 000 llamadas) y los entrega como variables de entorno con una caché (TTL 300 s: una lectura por secreto y TTL, nunca una por comando). El código del sandbox puede leer un secreto inyectado: para código no confiable, sólo tokens de vida corta y mínimo privilegio.

import os

from rayito import Sandbox, SecretStore

SecretStore(region="us-east-1").create("openai", os.environ["OPENAI_API_KEY"])
sbx = Sandbox.create(secrets={"OPENAI_API_KEY": "openai"})
sbx.commands.run("python agent.py")

IAM, reglas de la caché y el shim Secret de E2B: Secretos.

Índice de metadatos (opcional, con coste)

Apagado por defecto: sólo index=DynamoDbIndex(...) lo enciende (sin él no se construye ningún cliente de DynamoDB). Copia la metadata (no secreta) de cada sandbox en una tabla DynamoDB de tu cuenta (infra/metadata-index.yaml, on-demand, $0 en reposo; ~1 WRU por sandbox creado y 0,5 RRU por candidato listado) para filtrar por metadatos también sandboxes en pausa, sin despertarlos.

from rayito import DynamoDbIndex, Sandbox

idx = DynamoDbIndex("rayito-sandboxes")
sbx = Sandbox.create(metadata={"user": "42"}, index=idx)
sbx.pause()
paused = list(Sandbox.list(metadata={"user": "42"}, states=["SUSPENDED"], index=idx))

Reglas, IAM y cómo apagarlo: Observabilidad.

Shim de E2B

# antes
from e2b_code_interpreter import Sandbox

# después
from rayito.e2b import Sandbox

with Sandbox.create(timeout=300, metadata={"run": "42"}) as sbx:
    print(sbx.run_code("1 + 1").text)
    print(sbx.commands.run("echo hi").stdout)
    sbx.set_timeout(600)  # SetTimeout: rayd mueve el plazo (tope max_lifetime, 3600 s por defecto)

rayito.e2b re-exporta los nombres del SDK de E2B 2.x (Sandbox, AsyncSandbox, E2B, ConnectionConfig, Execution, CommandHandle, SandboxInfo, SandboxQuery, SandboxPaginator, PtySize(rows, cols), Git, las excepciones...), sigue los valores por defecto de E2B (timeout=300 como plazo lógico con on_timeout='kill', sin auto-pausa, endpoint público y salida a internet) y exige una imagen M9. Mapea set_timeout, connect(timeout), lifecycle, beta_create(auto_pause=True), upload_url/download_url, get_metrics(start, end), list(next_token=), allow_internet_access=False y network (en rayito-base-caps), update_network, git y run_code(language="typescript"); ignora con RayitoCompatWarning api_key, domain, debug y secure=False; y lanza UnimplementedError (un NotImplementedError, nunca SandboxException) para lo que Lambda MicroVMs no puede hacer: fork, snapshots, pause(keep_memory=False), network.rules, MCP, iam, volúmenes, secretos, templates y kernels R/Java. El nativo queda en sbx.native. Tabla completa: docs/site/docs/e2b-compat.md.

AsyncSandbox

import asyncio
from rayito import AsyncSandbox


async def main() -> None:
    async with await AsyncSandbox.create(metadata={"run": "42"}) as sbx:
        print((await sbx.run_code("1 + 1")).text)
        print((await sbx.commands.run("echo hola")).stdout)
        await sbx.files.write("/home/user/a.txt", "a")
        print(await AsyncSandbox.list(metadata={"run": "42"}))


asyncio.run(main())

La misma superficie que Sandbox (incluido rayito.e2b.AsyncSandbox) sobre grpc.aio; las llamadas al plano de control van por asyncio.to_thread.

Límites y costes (medidos)

Concepto Valor
Vida máxima de un sandbox 28 800 s (8 h) running + suspended, no se puede extender
runHookPayload (envs + metadata) 4096 caracteres
Conexiones concurrentes por MicroVM (1 vCPU) 8; el SDK usa ≤ 2 canales
Cómputo a 2 GB / 1 vCPU $0.126/h
Ciclo suspend + resume a 2 GB ≈ $0.011 (≈ 5 min de cómputo)
Storage por versión de imagen ≈ $0.037/semana (mínimo una semana)
Arranque en frío hasta agent_ready / pause() 2.36 s / ≈ 1.4 s
Escritura / lectura de ficheros a 2 GB 0.65 MB/s / 6.71 MB/s
list(metadata=) ≈ 0.5-1 s por sandbox RUNNING

Fuentes: AWS_API_NOTES.md §11-§12, §16 y MILESTONES.md; detalle en docs/site/docs/limits.md y docs/site/docs/cost.md.

Desarrollo

cd clients/python
uv sync
uv run pytest tests/unit
uv run ruff check . && uv run ruff format --check .
uv run mypy src tests
uv build && python ../../scripts/check_wheel.py dist/*.whl && uvx twine==7.0.0 check dist/*
RAYITO_E2E=1 RAYITO_TEMPLATE=<arn-o-nombre> uv run pytest tests/e2e -m e2e -v -s

Metadata

Release files for rayito 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rayito 0.6.0
File Size Uploaded Provenance
rayito-0.6.0.tar.gz 533.4 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for rayito 0.6.0
File Interpreter ABI Platform
rayito-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.2 MB

Release files / rayito-0.6.0.tar.gz

Download URL rayito-0.6.0.tar.gz
Size 533.4 kB
Tags Source
SHA-256 checksum
How to use checksums
902b29b41fefd0a96c472d4bad0d51df89271126570a5667cc83110c05c4bd25
BLAKE2b-256 checksum
How to use checksums
627659bb78e77d8b57ca8ea49f11942954206132e0ecd497e5c28aa867d4e6ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / rayito-0.6.0-py3-none-any.whl

Download URL rayito-0.6.0-py3-none-any.whl
Size 646.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4c7df6652b3f84a9f6b74dce6125b2c32140f2d38b347c04985d53d1830a78e1
BLAKE2b-256 checksum
How to use checksums
4e562683b97a579a40f269a747bfc24963ebac7635a2d62e6422027c9e039bad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Security for rayito 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for rayito-0.6.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page