Saltar a contenido

Subclientes

Los objetos que cuelgan de un sandbox (sbx.commands, sbx.files, sbx.pty, sbx.git, sbx.agent), sus handles y el paginador de Sandbox.paginate(). Las variantes asíncronas (AsyncCommandHandle, AsyncWatchHandle, AsyncUploadTicket, AsyncPtyHandle, AsyncGit, AsyncAgentStream, AsyncSandboxListPaginator) tienen la misma superficie con corrutinas.

Comandos

Commands(sandbox: Sandbox)

Comandos del sandbox (ProcessService).

run(cmd: str, *, background: bool = False, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, on_stdout: OutputCallback | None = None, on_stderr: OutputCallback | None = None, stdin: bool = False, timeout: float | None = DEFAULT_COMMAND_TIMEOUT_SECONDS, request_timeout: float | None = None, tag: str | None = None, secrets: Mapping[str, str | SecretRef] | None = None, max_output_bytes: int = COMMAND_OUTPUT_MAX_BYTES, kill_tree: bool = False) -> CommandResult | CommandHandle

run(
    cmd: str,
    *,
    background: Literal[False] = False,
    envs: Mapping[str, str] | None = None,
    user: str | None = None,
    cwd: str | None = None,
    on_stdout: OutputCallback | None = None,
    on_stderr: OutputCallback | None = None,
    stdin: bool = False,
    timeout: float | None = DEFAULT_COMMAND_TIMEOUT_SECONDS,
    request_timeout: float | None = None,
    tag: str | None = None,
    secrets: Mapping[str, str | SecretRef] | None = None,
    max_output_bytes: int = COMMAND_OUTPUT_MAX_BYTES,
    kill_tree: bool = False,
) -> CommandResult
run(
    cmd: str,
    *,
    background: Literal[True],
    envs: Mapping[str, str] | None = None,
    user: str | None = None,
    cwd: str | None = None,
    on_stdout: OutputCallback | None = None,
    on_stderr: OutputCallback | None = None,
    stdin: bool = False,
    timeout: float | None = DEFAULT_COMMAND_TIMEOUT_SECONDS,
    request_timeout: float | None = None,
    tag: str | None = None,
    secrets: Mapping[str, str | SecretRef] | None = None,
    max_output_bytes: int = COMMAND_OUTPUT_MAX_BYTES,
    kill_tree: bool = False,
) -> CommandHandle
run(
    cmd: str,
    *,
    background: bool,
    envs: Mapping[str, str] | None = None,
    user: str | None = None,
    cwd: str | None = None,
    on_stdout: OutputCallback | None = None,
    on_stderr: OutputCallback | None = None,
    stdin: bool = False,
    timeout: float | None = DEFAULT_COMMAND_TIMEOUT_SECONDS,
    request_timeout: float | None = None,
    tag: str | None = None,
    secrets: Mapping[str, str | SecretRef] | None = None,
    max_output_bytes: int = COMMAND_OUTPUT_MAX_BYTES,
    kill_tree: bool = False,
) -> CommandResult | CommandHandle

Ejecuta cmd con /bin/bash -l -c como user (uid 1000 por defecto).

timeout lo impone el agente sobre el reloj corrido (SIGTERM al vencer, SIGKILL 5 s después; una pausa no lo consume) y por defecto son 60 s; None o 0 lo desactivan. En foreground el resultado llega con la salida y un exit distinto de cero es CommandExitException; el timeout es TimeoutException. La salida guardada (la del resultado y la del handle) está acotada a max_output_bytes por descriptor (64 MiB por defecto): lo más antiguo se descarta y truncated lo indica; 0 no guarda nada. on_stdout/on_stderr reciben siempre todo. En background devuelve un CommandHandle que consume el stream cuando se itera o se llama a wait(): mientras nadie lo lee, el agente aplica backpressure y, tras 30 s con el canal lleno, cierra ese suscriptor con output_truncated. Un stream abierto cuenta como actividad para la política de idle del MicroVM. request_timeout acota los unarios que el handle haga después (kill, send_stdin, close_stdin).

kill_tree=True hace que el timeout y kill() alcancen también a los descendientes que salieron del grupo del proceso (setsid, un doble fork): el agente lo hace child subreaper de ellos, manda el SIGTERM del timeout a todo el árbol y, tras la gracia, congela y mata a los que queden; el fin del comando llega cuando ya no queda ninguno. Con False (por defecto) sólo se señala el grupo, como en E2B. Lo que el comando deja vivo al terminar por sí mismo no se toca.

Coste y activación

Activa: secrets={"ENV": "nombre" | SecretRef} inyecta secretos de Secrets Manager como variables de entorno de este comando (más los del handle; una clave que también esté en envs es InvalidArgumentException). Recursos y llamadas AWS: secretsmanager:GetSecretValue sólo en un fallo de SecretCache (TTL 300 s): tres comandos seguidos = una lectura. Ningún recurso nuevo. Coste aproximado: $0,05 por 10 000 llamadas (us-east-1, 2026-09-30). IAM: secretsmanager:GetSecretValue en las credenciales del llamante. Cómo apagarla: secrets=None (por defecto). Ejemplo: sbx.commands.run("python agent.py", secrets={"OPENAI_API_KEY": "openai"})

Con tracer_provider= en create()/connect(), cada llamada abre un span rayito.commands.run (nunca con el texto del comando, sus argumentos ni sus envs): rayito.commands.background siempre, y en foreground rayito.commands.exit_code al terminar (también cuando sale con un código distinto de cero). En segundo plano el span se cierra en cuanto start devuelve el handle, no cuando el proceso termina.

connect(pid: int, *, from_seq: int = 0, on_stdout: OutputCallback | None = None, on_stderr: OutputCallback | None = None, timeout: float | None = None, request_timeout: float | None = None, max_output_bytes: int = COMMAND_OUTPUT_MAX_BYTES) -> CommandHandle

Se engancha a un proceso vivo o terminado hace menos de 30 s.

from_seq=0 entrega sólo salida nueva; N reenvía primero lo retenido con seq >= N (último MiB). Un seq ya descartado o un pid desconocido es NotFoundException. timeout es aquí el deadline gRPC del stream (no hay timeout de servidor en Connect). max_output_bytes acota la salida guardada como en run.

list(*, request_timeout: float | None = None) -> list[ProcessInfo]

Procesos y PTY vivos (los terminados desaparecen aunque sigan conectables).

kill(pid: int, *, request_timeout: float | None = None) -> bool

SIGKILL al grupo del proceso (o de la PTY) y, si se lanzó con kill_tree=True, antes a todo su árbol. False si el pid no existe o ya terminó.

send_stdin(pid: int, data: str | bytes, *, request_timeout: float | None = None) -> None

Escribe en el stdin de un proceso lanzado con stdin=True (str va en UTF-8). Un pid de PTY es InvalidArgumentException: usa pty.send_input.

close_stdin(pid: int, *, request_timeout: float | None = None) -> None

EOF en el stdin del proceso; idempotente.

CommandHandle(*, commands: Commands, call: Any, progress: CommandProgress, request_timeout: float | None, deadline_at: float | None = None, foreground: bool = False)

Un proceso en background o enganchado con connect.

Consume el stream perezosamente en el hilo que itera o llama a wait(); disconnect() cancela el stream sin tocar el proceso, que sigue vivo y listado hasta que termine o alguien lo mate. Si el agente cierra el stream por un /suspend o el proxy lo corta, la siguiente lectura espera al agente y se reengancha con Connect(pid, from_seq=last_seq + 1); reconnects cuenta esas veces. Leer un handle en background nunca despierta un sandbox suspendido: la lectura se bloquea (sin consumir reconnect_timeout) hasta el resume(), el auto-resume que provoque otra llamada o el fin del MicroVM.

last_seq: int property

Último seq recibido; connect(pid, from_seq=last_seq + 1) reanuda sin huecos.

reconnects: int property

Veces que el handle se reenganchó tras un suspend/resume o un corte.

wait(on_pty: Callable[[bytes], Any] | None = None, on_stdout: Callable[[str], Any] | None = None, on_stderr: Callable[[str], Any] | None = None) -> CommandResult

Consume el stream hasta el EndEvent. Exit distinto de cero → CommandExitException; timeout del agente → TimeoutException; output_truncated → SandboxException. Idempotente.

on_stdout/on_stderr (texto) y on_pty (bytes de una PTY) reciben cada chunk que esta llamada consume, después de los callbacks de run(); lo ya leído antes (iterando) no se repite.

disconnect() -> None

Cancela el stream; el proceso sigue corriendo en el sandbox. Un handle desconectado nunca reconecta.

AsyncCommandHandle(*, commands: AsyncCommands, call: Any, progress: CommandProgress, request_timeout: float | None, deadline_at: float | None = None, foreground: bool = False)

CommandHandle sobre grpc.aio: await wait(), async for y await kill()/send_stdin()/close_stdin(); disconnect() es síncrono. Reconecta como CommandHandle tras un suspend/resume.

wait(on_pty: Callable[[bytes], Any] | None = None, on_stdout: Callable[[str], Any] | None = None, on_stderr: Callable[[str], Any] | None = None) -> CommandResult async

Como CommandHandle.wait; los callbacks pueden ser síncronos o async (su resultado se espera si es awaitable antes del siguiente chunk).

Ficheros

Filesystem(sandbox: Sandbox)

Ficheros del sandbox (FilesystemService).

read(path: str, *, format: str = 'text', gzip: bool = False, stream_idle_timeout: float | None = None, user: str | None = None, request_timeout: float | None = None) -> str | bytes | Iterator[bytes]

read(
    path: str,
    *,
    format: Literal["text"] = "text",
    gzip: bool = False,
    stream_idle_timeout: float | None = None,
    user: str | None = None,
    request_timeout: float | None = None,
) -> str
read(
    path: str,
    *,
    format: Literal["bytes"],
    gzip: bool = False,
    stream_idle_timeout: float | None = None,
    user: str | None = None,
    request_timeout: float | None = None,
) -> bytes
read(
    path: str,
    *,
    format: Literal["stream"],
    gzip: bool = False,
    stream_idle_timeout: float | None = None,
    user: str | None = None,
    request_timeout: float | None = None,
) -> Iterator[bytes]

Lee un fichero regular: text (UTF-8 estricto), bytes o stream (chunks de hasta 256 KiB tal como llegan). Hace Stat primero: un directorio o un symlink es InvalidArgumentException sin abrir el stream, y el deadline del Read es 60 s + 1 s por MB del tamaño salvo request_timeout.

gzip=True pide la respuesta comprimida (inocuo en una imagen anterior, que responde sin comprimir). stream_idle_timeout (s) cancela la lectura si el siguiente chunk tarda más y levanta TimeoutException. Con transfer=S3Staging(...) un fichero de al menos threshold_bytes se exporta a S3 y se descarga con tus credenciales (verificando su sha256); gzip no aplica ahí.

Con tracer_provider= abre rayito.files.read (nunca con path ni el contenido como atributo); en format="text"/"bytes", rayito.files.bytes al terminar.

write(path: str, data: str | bytes | IO[bytes] | IO[str], *, user: str | None = None, mode: int | None = None, gzip: bool = False, metadata: Mapping[str, str] | None = None, use_octet_stream: bool = False, request_timeout: float | None = None) -> EntryInfo

Escribe data (str en UTF-8, bytes o fichero abierto) de forma atómica, creando los padres y con mode (0o644 por defecto). Los padres que falten se crean; el propietario es user. gzip, metadata y use_octet_stream como en write_files.

Con tracer_provider= abre rayito.files.write (nunca con path ni data), con rayito.files.bytes al terminar.

write_files(files: Sequence[WriteEntry], *, user: str | None = None, gzip: bool = False, metadata: Mapping[str, str] | None = None, use_octet_stream: bool = False, request_timeout: float | None = None) -> list[EntryInfo]

N ficheros en un stream Write; devuelve sus EntryInfo en orden. Cada fichero se compromete de forma atómica por separado: si el stream falla a mitad, los ya escritos quedan y el resto no existe. Deadline 60 s + 1 s por MB del total salvo request_timeout.

gzip=True comprime el stream (grpc-encoding: gzip). metadata (claves token de HTTP, se guardan en minúsculas) se aplica a cada fichero y sustituye el conjunto entero; se valida antes de cualquier RPC. Ambos exigen un agente 0.3.0 o posterior (UnimplementedError si no). use_octet_stream se acepta y no tiene efecto (gRPC no usa formularios). Con transfer=S3Staging(...), cada fichero de al menos threshold_bytes (o un stream binario no buscable) se sube a S3 con tus credenciales y rayd lo importa; gzip no aplica ahí.

Con tracer_provider= abre rayito.files.write_files (nunca las rutas ni los datos), con rayito.files.count y rayito.files.bytes al terminar.

upload_url(path: str, *, user: str | None = None, expires_in: int = TRANSFER_DEFAULT_EXPIRES_IN_SECONDS, max_bytes: int | None = None, form: bool = False, request_timeout: float | None = None) -> UploadTicket

Una URL de subida a S3 firmada con tus credenciales, con la importación a path ya armada en el sandbox. Es un str: requests.put(ticket, data=f, headers=ticket.headers) basta; con form=True es un formulario POST (ticket.fields más el fichero en file) que S3 limita a max_bytes. De un solo uso; expires_in se topa en min(expires_in, transfer.max_expires_in, 604800). El fichero aparece de forma asíncrona: read, get_info, list, un comando o una celda esperan a la importación, y ticket.wait() la espera explícitamente. Sin transfer=S3Staging(...) ni RAYITO_TRANSFER_BUCKET levanta UnimplementedError.

download_url(path: str, *, user: str | None = None, expires_in: int = TRANSFER_DEFAULT_EXPIRES_IN_SECONDS, filename: str | None = None, request_timeout: float | None = None) -> DownloadLink

Exporta path tal como está ahora a S3 y devuelve una URL de descarga firmada con tus credenciales (un str con size, sha256 y expires_at); urlopen(link) basta y admite Range. Un fichero inexistente levanta FileNotFoundException al llamar; un directorio o un symlink, InvalidArgumentException. filename es el nombre de Content-Disposition (por defecto el de path).

list(path: str, *, depth: int = 1, user: str | None = None, request_timeout: float | None = None) -> list[EntryInfo]

Entradas hasta depth niveles (1 = sólo hijos directos; 0 vale 1) en preorden, ordenadas por nombre en cada directorio, sin seguir symlinks. Más de 10 000 entradas es RateLimitException.

Con tracer_provider= abre rayito.files.list (nunca path), con rayito.files.count al terminar.

exists(path: str, *, user: str | None = None, request_timeout: float | None = None) -> bool

False sólo si Stat responde NOT_FOUND; otros errores se propagan.

Con tracer_provider= abre rayito.files.exists (nunca path).

get_info(path: str, *, user: str | None = None, request_timeout: float | None = None) -> EntryInfo

Stat sin seguir symlinks (type is FileType.SYMLINK con symlink_target).

Con tracer_provider= abre rayito.files.get_info (nunca path).

remove(path: str, *, recursive: bool = True, user: str | None = None, request_timeout: float | None = None) -> None

Borra un fichero, symlink (sólo el enlace) o directorio; con recursive=False un directorio no vacío es InvalidArgumentException.

Con tracer_provider= abre rayito.files.remove (nunca path).

rename(old_path: str, new_path: str, *, user: str | None = None, request_timeout: float | None = None) -> EntryInfo

rename(2): reemplaza un fichero regular existente; un destino que es un directorio no vacío es InvalidArgumentException.

Con tracer_provider= abre rayito.files.rename (nunca las rutas).

make_dir(path: str, *, user: str | None = None, request_timeout: float | None = None) -> bool

Crea el directorio y sus padres (0o755). True si lo creó, False si ya existía; si existe algo que no es un directorio, InvalidArgumentException.

Con tracer_provider= abre rayito.files.make_dir (nunca path).

watch_dir(path: str, *, on_event: EventCallback | None = None, on_exit: ExitCallback | None = None, recursive: bool = False, include_entry: bool = False, user: str | None = None, timeout: float | None = 0, request_timeout: float | None = None) -> WatchHandle

Observa un directorio (inotify, sin debounce). Bloquea hasta WatchStarted y devuelve un WatchHandle que consume el stream en un hilo daemon: con on_event cada evento va al callback; sin él se acumulan para get_new_events(). timeout (> 0) es el deadline gRPC del stream y termina en TimeoutException; 0/None es ilimitado. request_timeout se acepta por uniformidad y no aplica al stream. Un files.write en el directorio aparece como un único WRITE.

WatchHandle(*, filesystem: Filesystem, call: Any, path: str, state: WatchState, on_exit: ExitCallback | None, request: filesystem_pb2.WatchDirRequest | None = None, deadline_at: float | None = None)

Un watch_dir vivo. stop() cancela el stream, espera al hilo (≤ 5 s) y es idempotente; Sandbox.close() lo llama por cada handle vivo. Tras un suspend/resume (o un corte del proxy) el hilo consumidor espera al agente y vuelve a emitir WatchDir sin llamar a on_exit; reconnects cuenta esas veces.

is_running: bool property

False tras stop() o cuando el stream terminó por su cuenta.

get_new_events() -> list[FilesystemEvent]

Eventos no entregados desde la última llamada. Si el stream terminó con error lo levanta una sola vez; tras un stop() limpio devuelve listas vacías.

AsyncWatchHandle(*, filesystem: AsyncFilesystem, call: Any, path: str, state: WatchState, on_exit: ExitCallback | None, request: filesystem_pb2.WatchDirRequest | None = None, deadline_at: float | None = None)

WatchHandle sobre grpc.aio: await get_new_events(), await stop() y async with.

UploadTicket

Bases: UploadTicketBase

Resultado de files.upload_url: la importación ya está armada en rayd antes de que la URL exista para el caller, y es de un solo uso.

wait() sigue la transferencia hasta su final y devuelve el EntryInfo escrito (o levanta la excepción mapeada); con timeout agotado levanta TimeoutException y el ticket sigue armado. status() es una foto y cancel() la cancela (idempotente).

AsyncUploadTicket

Bases: UploadTicketBase

UploadTicket de AsyncSandbox: wait(), status() y cancel() son corrutinas.

Terminal (PTY)

Pty(sandbox: Sandbox)

Terminales interactivas del sandbox (PtyService).

create(*, size: PtySize | None = None, user: str | None = None, cwd: str | None = None, envs: Mapping[str, str] | None = None, shell: str | None = None, on_data: PtyDataCallback | None = None, timeout: float | None = DEFAULT_PTY_TIMEOUT_SECONDS, request_timeout: float | None = None, secrets: Mapping[str, str | SecretRef] | None = None) -> PtyHandle

Abre una terminal con el shell de login de user (uid 1000 por defecto) como <shell> -i -l, TERM=xterm-256color y LANG/ LC_ALL=C.UTF-8 (los envs del request ganan). size es 80x24 si se omite; shell debe ser una ruta absoluta.

timeout (60 s por defecto, como en E2B) lo impone el agente sobre el reloj corrido: al vencer manda SIGTERM al grupo y wait() termina en TimeoutException. Para un shell que deba sobrevivir, timeout=None. on_data recibe cada chunk de bytes de la terminal mientras el handle se itera o se espera; el SDK nunca los loguea.

Coste y activación

Activa: secrets= inyecta secretos como variables de entorno del shell (más los del handle). Recursos y llamadas AWS: secretsmanager:GetSecretValue sólo en un fallo de SecretCache (TTL 300 s). Coste aproximado: $0,05 por 10 000 llamadas (us-east-1, 2026-09-30). IAM: secretsmanager:GetSecretValue en las credenciales del llamante. Cómo apagarla: secrets=None (por defecto). Ejemplo: term = sbx.pty.create(secrets={"GITHUB_TOKEN": "gh"})

connect(pid: int, *, from_seq: int = 0, on_data: PtyDataCallback | None = None, timeout: float | None = None, request_timeout: float | None = None) -> PtyHandle

Se reengancha a una PTY viva o terminada hace menos de 30 s. from_seq=0 entrega sólo salida nueva; N reenvía lo retenido con seq >= N. timeout es el deadline gRPC de este stream.

send_input(pid: int, data: str | bytes, *, request_timeout: float | None = None) -> None

Escribe en la terminal (str va en UTF-8; un \n final ejecuta la línea). Una PTY terminada es NotFoundException; el pid de un proceso normal es InvalidArgumentException.

resize(pid: int, size: PtySize, *, request_timeout: float | None = None) -> None

TIOCSWINSZ sobre la terminal; el shell recibe SIGWINCH.

kill(pid: int, *, request_timeout: float | None = None) -> bool

SIGKILL al grupo de la terminal. False si el pid no existe o ya terminó.

PtyHandle(*, pty: Pty, call: Any, progress: CommandProgress, request_timeout: float | None, deadline_at: float | None = None)

Bases: CommandHandle

Una terminal abierta con pty.create o pty.connect.

Es un CommandHandle (pid, last_seq, stdout con la salida de la terminal decodificada, stderr vacío, exit_code, error, wait(), disconnect(), reconnects) cuya iteración entrega (None, None, bytes) por mensaje y cuyo kill() es PtyService.Kill. send_input escribe en la terminal y resize cambia su tamaño.

AsyncPtyHandle(*, pty: AsyncPty, call: Any, progress: CommandProgress, request_timeout: float | None, deadline_at: float | None = None)

Bases: AsyncCommandHandle

PtyHandle sobre grpc.aio: async for entrega (None, None, bytes).

Git

Git(commands: Commands)

Operaciones git en el sandbox con la superficie de E2B 2.51 (sandbox.git). E2B marca su módulo como obsoleto a favor de commands.run(); aquí se mantiene por paridad.

timeout=None (por defecto) es sin plazo de servidor. Un fallo de autenticación contra un remoto es GitAuthException, un push/pull sin upstream GitUpstreamException y cualquier otro fallo el CommandExitException del comando, sin la contraseña cuando la llamada llevaba credenciales.

clone(url: str, path: str | None = None, branch: str | None = None, depth: int | None = None, username: str | None = None, password: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None, dangerously_store_credentials: bool = False) -> CommandResult

git clone. Con username y password clona desde la URL con las credenciales y, salvo dangerously_store_credentials=True, deja origin apuntando a la URL sin ellas.

remote_add(path: str, name: str, url: str, fetch: bool = False, overwrite: bool = False, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

git remote add [-f]; con overwrite cae a remote set-url si el remoto ya existe.

remote_get(path: str, name: str, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> str | None

La URL del remoto, o None si no existe.

add(path: str, files: Sequence[str] | None = None, all: bool = True, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

git add -- <files>; sin files, -A (o . con all=False).

reset(path: str, mode: GitResetMode | None = None, target: str | None = None, paths: Sequence[str] | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

Un mode fuera de soft/mixed/hard/merge/keep es InvalidArgumentException sin ejecutar nada.

restore(path: str, paths: Sequence[str], staged: bool | None = None, worktree: bool | None = None, source: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

Sin staged ni worktree restaura el árbol de trabajo; staged=True solo saca del índice. Una lista vacía es InvalidArgumentException sin ejecutar nada.

push(path: str, remote: str | None = None, branch: str | None = None, set_upstream: bool = True, username: str | None = None, password: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

git push. Con credenciales: remote get-url, remote set-url con la URL con credenciales, el push y, pase lo que pase, remote set-url con la URL original.

pull(path: str, remote: str | None = None, branch: str | None = None, username: str | None = None, password: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

git pull. Sin remote ni branch exige una rama con upstream (GitUpstreamException si no la hay); las credenciales se inyectan y se retiran como en push.

set_config(key: str, value: str, scope: str = 'global', path: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

git config --global|--local|--system; scope="local" necesita path.

get_config(key: str, scope: str = 'global', path: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> str | None

El valor, o None si la clave no está en ese scope.

dangerously_authenticate(username: str, password: str, host: str = 'github.com', protocol: str = 'https', envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

Autentica git para todo el sandbox con el helper store: credential.helper=store global y git credential approve.

Las credenciales quedan en ~/.git-credentials del usuario del sandbox y son visibles para cualquier código que corra en él, igual que en E2B. Usa tokens de vida corta. El SDK nunca las loguea.

configure_user(name: str, email: str, scope: str = 'global', path: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult

user.name y user.email en el scope pedido.

AsyncGit(commands: AsyncCommands)

Git como corrutinas (la clase Git del SDK async de E2B 2.51). E2B marca su módulo como obsoleto a favor de commands.run(); aquí se mantiene por paridad.

timeout=None (por defecto) es sin plazo de servidor. Un fallo de autenticación contra un remoto es GitAuthException, un push/pull sin upstream GitUpstreamException y cualquier otro fallo el CommandExitException del comando, sin la contraseña cuando la llamada llevaba credenciales.

clone(url: str, path: str | None = None, branch: str | None = None, depth: int | None = None, username: str | None = None, password: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None, dangerously_store_credentials: bool = False) -> CommandResult async

git clone. Con username y password clona desde la URL con las credenciales y, salvo dangerously_store_credentials=True, deja origin apuntando a la URL sin ellas.

remote_add(path: str, name: str, url: str, fetch: bool = False, overwrite: bool = False, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

git remote add [-f]; con overwrite cae a remote set-url si el remoto ya existe.

remote_get(path: str, name: str, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> str | None async

La URL del remoto, o None si no existe.

add(path: str, files: Sequence[str] | None = None, all: bool = True, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

git add -- <files>; sin files, -A (o . con all=False).

reset(path: str, mode: GitResetMode | None = None, target: str | None = None, paths: Sequence[str] | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

Un mode fuera de soft/mixed/hard/merge/keep es InvalidArgumentException sin ejecutar nada.

restore(path: str, paths: Sequence[str], staged: bool | None = None, worktree: bool | None = None, source: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

Sin staged ni worktree restaura el árbol de trabajo; staged=True solo saca del índice. Una lista vacía es InvalidArgumentException sin ejecutar nada.

push(path: str, remote: str | None = None, branch: str | None = None, set_upstream: bool = True, username: str | None = None, password: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

git push. Con credenciales: remote get-url, remote set-url con la URL con credenciales, el push y, pase lo que pase, remote set-url con la URL original.

pull(path: str, remote: str | None = None, branch: str | None = None, username: str | None = None, password: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

git pull. Sin remote ni branch exige una rama con upstream (GitUpstreamException si no la hay); las credenciales se inyectan y se retiran como en push.

set_config(key: str, value: str, scope: str = 'global', path: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

git config --global|--local|--system; scope="local" necesita path.

get_config(key: str, scope: str = 'global', path: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> str | None async

El valor, o None si la clave no está en ese scope.

dangerously_authenticate(username: str, password: str, host: str = 'github.com', protocol: str = 'https', envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

Autentica git para todo el sandbox con el helper store: credential.helper=store global y git credential approve.

Las credenciales quedan en ~/.git-credentials del usuario del sandbox y son visibles para cualquier código que corra en él, igual que en E2B. Usa tokens de vida corta. El SDK nunca las loguea.

configure_user(name: str, email: str, scope: str = 'global', path: str | None = None, envs: Mapping[str, str] | None = None, user: str | None = None, cwd: str | None = None, timeout: float | None = None, request_timeout: float | None = None) -> CommandResult async

user.name y user.email en el scope pedido.

GitStatus(current_branch: str | None, upstream: str | None, ahead: int, behind: int, detached: bool, file_status: list[GitFileStatus] = list()) dataclass

El estado de un repositorio tal como lo modela E2B.

GitFileStatus(name: str, status: str, index_status: str, working_tree_status: str, staged: bool, renamed_from: str | None = None) dataclass

Una entrada de git status --porcelain=1: status normalizado (modified, added, untracked, conflict...) y los dos caracteres crudos del índice y del árbol de trabajo.

GitBranches(branches: list[str] = list(), current_branch: str | None = None) dataclass

GitResetMode es el alias Literal["soft", "mixed", "hard", "merge", "keep"] del mode= de git.reset().

Agente de IA (sbx.agent)

run() corre el agente hasta el final (lanza AgentException si falla); stream() devuelve un AgentStream iterable que nunca lanza por un fallo del agente; prepare() dispara el calentamiento del runtime en segundo plano.

Agent(sandbox: AgentSandbox)

El agente de IA de este sandbox (ai-agent-core, ADR-025). Una instancia por Sandbox, creada en su constructor (como .commands/.files): tocarla no hace ninguna llamada; el coste empieza con el primer run()/stream().

Coste y activación

Activa: llamar a run(), stream() o prepare(). No hace falta ninguna opción nueva: basta con usar sbx.agent. Recursos y llamadas AWS: ninguno nuevo de Rayito. El propio runtime llama al modelo (Bedrock Converse/ConverseStream, o la API que exponga la pasarela) a través de la pasarela ya configurada con gateways= en create(); sin ella, run()/stream() fallan con InvalidArgumentException antes de cualquier llamada. El sandbox ya factura por segundo, con o sin agente. Coste aproximado: depende del modelo y de los pasos, no de Rayito. Guía con precios de lista (us-east-1, consultados 2026-10-06, https://aws.amazon.com/bedrock/pricing y https://aws.amazon.com/lambda/pricing/): 10 pasos de Haiku 4.5 regional (15k tokens de entrada + 400 de salida por paso) ≈ $0,187 sin caché / $0,083 con caché de prompts; 5 minutos de MicroVM de 2 GB ≈ $0,0119. El modelo cuesta entre 7x y 21x la VM (cost.md, "Coste de un agente"). IAM: ninguno adicional a lo que ya pide la pasarela (SecretGateway, secretsmanager:GetSecretValue). Cómo apagarla: no llames a sbx.agent.run()/stream()/prepare(). Ejemplo: model_id = "us.anthropic.claude-haiku-4-5-20251001-v1:0" with Sandbox.create( "rayito-agent", allow_internet_access=False, gateways={"bedrock": bedrock_gateway( "bedrock-key", region="us-east-1", models=[model_id], )}, ) as sbx: result = sbx.agent.run( "lista los ficheros de /home/user", spec=AgentSpec(model=AgentModel( provider="bedrock", id=model_id, gateway="bedrock", region="us-east-1", )), )

run(prompt: str, *, spec: AgentSpec, runtime: str | AgentRuntime = DEFAULT_AGENT_RUNTIME, session_id: str | None = None, model: str | None = None, limits: AgentLimits | None = None, workdir: str = DEFAULT_AGENT_WORKDIR, reasoning: bool = False) -> AgentResult

Corre el agente hasta que termina y devuelve su AgentResult. Una ejecución fallida lanza AgentException (el último evento era AgentFailed); un fallo de sandbox o de transporte (SandboxException, TimeoutException) se propaga tal cual.

stream(prompt: str, *, spec: AgentSpec, runtime: str | AgentRuntime = DEFAULT_AGENT_RUNTIME, session_id: str | None = None, model: str | None = None, limits: AgentLimits | None = None, workdir: str = DEFAULT_AGENT_WORKDIR, reasoning: bool = False) -> AgentStream

Como run(), pero devuelve un AgentStream iterable que nunca lanza por un fallo del agente: el último evento es Done o AgentFailed. Un fallo de sandbox o de transporte sí se propaga, al construir el stream o al iterarlo.

prepare(*, runtime: str | AgentRuntime = DEFAULT_AGENT_RUNTIME) -> None

Dispara los pasos de calentamiento del runtime (warmup_steps) en segundo plano y vuelve enseguida: no espera a ninguno. Un paso con background=True corre sin plazo: es un proceso que debe seguir vivo y moriría al agotarlo.

AgentStream(*, sandbox: AgentSandbox, handle: CommandHandle, runtime: AgentRuntime, state: RuntimeState, limits: AgentLimits, session_id: str | None, span_context: AbstractContextManager[Any], span: Any)

Iterable de AgentEvent sobre un CommandHandle en background: cada chunk de stdout se troceaba en líneas, cada línea se traduce con el adaptador del runtime y cada evento pasa por LimitTracker antes de entregarse. Nunca lanza por un fallo del agente; result() sí.

abort() -> None

Aborta la ejecución: para los procesos que lanzó el runtime y mata el proceso. Puede llamarse desde otro hilo mientras otro consume el stream.

result() -> AgentResult

Consume el resto del stream y devuelve su AgentResult. AgentFailed se convierte en AgentException.

AsyncAgentStream(*, sandbox: AgentSandbox, handle: AsyncCommandHandle, runtime: AgentRuntime, state: RuntimeState, limits: AgentLimits, session_id: str | None, span_context: AbstractContextManager[Any], span: Any)

Espejo asíncrono de sandbox_sync.agent.AgentStream: implementa AsyncIterable[AgentEvent]. Cancelar la tarea que corre __anext__ (o un async with del que se sale por cancelación) dispara abort().

abort() -> None async

Para los procesos que lanzó el runtime y mata el proceso.

Especificación

AgentSpec(model: AgentModel, small_model: str | None = None, instructions: str | None = None, mcp: Mapping[str, McpServer] = dict(), permissions: AgentPermissions = AgentPermissions(), agents: Mapping[str, SubAgent] = dict(), raw_config: Mapping[str, object] | None = None, runtime_version: str | None = None) dataclass

La configuración estática de un agente: modelo, instrucciones, servidores MCP, permisos y subagentes. Construirlo no llama a nada.

small_model (títulos, resúmenes) es por defecto model.id, así que el runtime nunca elige por su cuenta un modelo que la cuenta quizá no tenga. instructions se escribe en un AGENTS.md propio bajo AGENT_STATE_DIR, nunca sobre el del directorio de trabajo (que OpenCode también lee). raw_config se fusiona en profundidad al final y no puede tocar RESERVED_CONFIG_KEYS. runtime_version sólo se valida como texto: hoy no se compara con el manifiesto de la plantilla y nunca produce runtime_version_mismatch.

gateway_names() -> frozenset[str]

Las pasarelas que el agente necesita: la del modelo y la de cada McpRemote.

require_gateways(available: Collection[str]) -> None

Falla con InvalidArgumentException si alguna pasarela de gateway_names() no está en available (los nombres de sbx.gateways). Un nombre de pasarela nunca es secreto.

AgentModel(provider: ModelProvider, id: str, gateway: str, region: str | None = None, base_path: str = '', prompt_caching: bool = True) dataclass

El modelo que usa el agente y la pasarela por la que llega a él.

gateway nombra una entrada de sbx.gateways (un SecretGateway ya aplicado, por ejemplo con bedrock_gateway(...)); si el sandbox no la tiene, sbx.agent.run() falla con InvalidArgumentException antes de cualquier RPC. region es obligatoria con provider="bedrock". base_path sólo se usa con "openai-compatible" ("/v1") y es el del preset: "/api/v1" con openrouter_gateway, "/openai/v1" con groq_gateway, "/v1" con mistral_gateway y litellm_gateway, "" con deepseek_gateway. "openai" va con openai_gateway o xai_gateway, "google" con gemini_gateway y "azure" con azure_openai_gateway; esos tres fijan su ruta en el adaptador. prompt_caching=True deja que el runtime marque puntos de caché (Bedrock cachePoint, Anthropic cache_control): con dos o más pasos abarata la entrada repetida.

AgentLimits(max_steps: int = DEFAULT_AGENT_MAX_STEPS, timeout_seconds: float = DEFAULT_AGENT_TIMEOUT_SECONDS, max_output_bytes: int = DEFAULT_AGENT_MAX_OUTPUT_BYTES, max_total_tokens: int | None = DEFAULT_AGENT_MAX_TOTAL_TOKENS) dataclass

Topes duros de una ejecución, impuestos por el SDK (no por el runtime): max_steps aborta con max_steps al empezar el paso max_steps + 1; max_total_tokens se comprueba tras cada paso, así que puede pasarse en un paso (None lo desactiva); timeout_seconds y max_output_bytes son los del comando que corre el runtime. Los valores por defecto acotan el coste de una ejecución (design.md de ai-agent-core, D4).

AgentPermissions(default: PermissionAction = 'allow', tools: Mapping[str, ToolPermission] = dict()) dataclass

Qué herramientas puede usar el agente. default aplica a toda herramienta sin entrada propia; tools asigna "allow"/"deny" a una herramienta ("edit": "deny") o a patrones de su argumento ("bash": {"git *": "allow", "*": "deny"}). "ask" no existe: la ejecución es desatendida.

No es una frontera de seguridad (SECURITY.md T29): la frontera es la MicroVM con el egress cerrado. effective_tools() añade DEFAULT_DENIED_TOOLS por debajo de las entradas del llamante.

effective_tools() -> Mapping[str, ToolPermission]

DEFAULT_DENIED_TOOLS en "deny" y encima las entradas de tools (una entrada del llamante gana).

SubAgent(description: str, instructions: str, model: str | None = None, permissions: AgentPermissions | None = None) dataclass

Un subagente que el agente principal puede invocar (OpenCode mode: subagent, deepagents subagents=). model es un id del mismo proveedor y pasarela que AgentSpec.model; None hereda el suyo.

McpLocal(command: Sequence[str], envs: Mapping[str, str] = dict(), timeout_seconds: float | None = None) dataclass

Un servidor MCP que el runtime arranca dentro del sandbox como proceso hijo (command en forma de lista, sin shell). envs se escribe en la configuración del runtime, legible por uid 1000: nunca pongas ahí una credencial, usa una pasarela.

McpRemote(gateway: str, path: str = '/') dataclass

Un servidor MCP remoto alcanzado a través de la pasarela gateway (la URL sale de sbx.gateways[gateway].url + path). No hay headers a propósito: la credencial sólo la pone la pasarela.

McpServer es el alias McpLocal | McpRemote.

DeepAgents(entrypoint: str | None = None)

AgentRuntime de deepagents (runtime=DeepAgents(...) o runtime="deepagents").

Sin entrypoint, el runner construye create_deep_agent(model, system_prompt=instructions, subagents, backend=LocalShellBackend( root_dir=workdir), middleware=ctx.middleware). Con entrypoint= "pkg.mod:build", importa ese módulo desde el directorio de trabajo y llama a build(ctx) con un RunnerContext(model, instructions, subagents, backend, middleware, workdir); debe devolver un grafo compilado. Si no pasa ctx.middleware a su grafo, se pierden los permisos de AgentSpec.

El modelo siempre llega por la pasarela (ChatBedrockConverse, ChatAnthropic o ChatOpenAI con la URL de la pasarela y un marcador como clave). deepagents marca puntos de caché de prompts para Claude (Bedrock y Anthropic); AgentModel(prompt_caching=False) lo apaga.

command(request: RunRequest) -> RunCommand

El script (cerrojo, comprobación del runner, exec del Python del venv) y la petición JSON por stdin.

parse_line(line: bytes, state: RuntimeState) -> Sequence[AgentEvent]

Una línea del protocolo v1 a cero o un evento. Lo que no es JSON, otra versión o un tipo desconocido se ignora y se cuenta.

finish(state: RuntimeState, exit_code: int) -> Done | AgentFailed

Done sólo con salida 0, el done del runner y una sesión.

template_steps() -> Sequence[TemplateStep]

Vacío: la plantilla de agente (ai-agent-fast-start) declara sus propios pasos.

warmup_steps() -> Sequence[WarmupStep]

Carga deepagents y langchain en la caché de páginas.

Pasarelas del modelo

Construyen el SecretGateway que nombra AgentModel.gateway.

bedrock_gateway(secret: str | SecretRef, *, region: str, models: Sequence[str], rate_per_minute: int = 0) -> SecretGateway

Pasarela hacia Bedrock (Converse/ConverseStream) para los models dados (ids o perfiles de inferencia, como us.anthropic.claude-haiku-4-5-20251001-v1:0). secret nombra un secreto con Bearer <clave de API de Bedrock>; una clave de corta duración se rota con sbx.gateways.refresh().

anthropic_gateway(secret: str | SecretRef, *, rate_per_minute: int = 0) -> SecretGateway

Pasarela hacia la Messages API de Anthropic. secret nombra un secreto con la clave de API (x-api-key).

openai_compatible_gateway(secret: str | SecretRef, *, upstream: str, base_path: str = '', rate_per_minute: int = 0) -> SecretGateway

Pasarela hacia una API compatible con OpenAI (POST <base_path>/chat/completions). upstream es https://host; base_path es el mismo que AgentModel.base_path ("/v1"). secret nombra un secreto con Bearer <clave>.

Resultado y eventos

AgentEvent es el alias de la unión cerrada de eventos que da agent.stream(): TextDelta | Text | Reasoning | ToolCall | StepStarted | StepFinished | AgentFailed | Done.

AgentResult(session_id: str, text: str, steps: int, usage: TokenUsage, exit_code: int, tool_calls: tuple[ToolCall, ...] = (), dropped_lines: int = 0) dataclass

El resultado de sbx.agent.run(). text es el último Text del asistente; dropped_lines cuenta las líneas del runtime descartadas (tipo desconocido o más largas que MAX_AGENT_EVENT_LINE_BYTES).

TokenUsage(input: int = 0, output: int = 0, reasoning: int = 0, cache_read: int = 0, cache_write: int = 0) dataclass

Tokens de un paso o de una ejecución. input excluye los leídos de caché (cache_read) y los escritos en ella (cache_write), como Bedrock y Anthropic los facturan por separado; total suma los cinco. El SDK no da un coste: los precios dependen de región y perfil.

TextDelta(text: str) dataclass

Un trozo de texto en curso (sólo deepagents; OpenCode emite Text entero).

Text(text: str) dataclass

Un bloque de texto completo del asistente.

Reasoning(text: str) dataclass

Razonamiento del modelo; sólo con run(..., reasoning=True).

ToolCall(call_id: str, name: str, status: ToolCallStatus, input: Mapping[str, object] | None = None, output: str | None = None, output_truncated: bool = False) dataclass

Una herramienta terminada. output llega recortado a MAX_TOOL_OUTPUT_PREVIEW_BYTES (output_truncated).

StepStarted(index: int) dataclass

Empieza el paso index (desde 1): una llamada al modelo.

StepFinished(index: int, usage: TokenUsage, finish_reason: str | None = None) dataclass

Termina el paso index con sus tokens.

AgentFailed(reason: AgentFailureReason, exit_code: int | None = None, detail_code: str | None = None, session_id: str | None = None) dataclass

Último evento de una ejecución fallida. reason es de AGENT_FAILURE_REASONS; detail_code se descarta si no es un identificador corto.

to_exception(usage: TokenUsage | None = None) -> AgentException

La AgentException que sbx.agent.run() lanza por este evento.

Done(session_id: str, exit_code: int, usage: TokenUsage) dataclass

Último evento de una ejecución correcta, con los tokens acumulados.

Imagen y arranque rápido

AgentTemplate construye una vez la imagen con el runtime (Templates de agente); con ella, el arranque normal es Sandbox.create(...) + sbx.agent.run(...). agent_pool_warmup y los pools calientes son opcionales: ¿Qué uso?.

AgentTemplate(name: str = DEFAULT_AGENT_TEMPLATE_NAME, base: str = DEFAULT_AGENT_TEMPLATE_BASE, runtimes: tuple[str, ...] = AGENT_TEMPLATE_RUNTIMES, prefetch: bool = True, kernel_warmup: bool = False, memory_mib: int = AGENT_MIN_MEMORY_MIB, base_version: str | None = None) dataclass

Plantilla de imagen con los runtimes del agente de IA.

runtimes elige qué se instala ("opencode", "deepagents"); prefetch=True hornea el demonio que precarga los binarios en la caché de páginas tras cada restauración del snapshot. kernel_warmup=False (por defecto) apaga el calentamiento del kernel de run_code (numpy, pandas, matplotlib, scipy y scikit-learn importados en cada arranque del kernel, también en la rotación que create() espera): medido en AWS (AWS_API_NOTES.md Q155), create() vuelve 4,4 s antes (p50) y el snapshot de memoria pesa 248 MB menos, con el mismo primer token del agente; a cambio, la primera celda de run_code que importe ese stack paga la importación. kernel_warmup=True deja el kernel como en la imagen base. memory_mib por debajo de AGENT_MIN_MEMORY_MIB (2048, el RSS medido de OpenCode) es InvalidArgumentException.

Coste y activación

Activa: AgentTemplate(...).build(bucket=...) o rayito agent template build. Recursos y llamadas AWS: los de Template.build (un build de imagen, s3:PutObject del contexto, una versión de imagen nueva); nada si no se construye. Coste aproximado: build de 260-320 s (medido en AWS, 2026-10-07 y 2026-10-09); la versión almacenada ≈ 3,0 GB (código 2,10 + memoria 0,86 + disco 0,03; memoria 0,92 con kernel_warmup=True) por $0,08/GB-mes, con el mínimo de una semana ≈ $0,056/semana (≈ $0,24/mes) por versión, y cada lanzamiento lee el snapshot de memoria (≈ 0,86 GB por $0,00155/GB ≈ $0,0013). Estimación con precios de lista de Lambda MicroVMs, us-east-1, consultados 2026-10-06 (https://aws.amazon.com/lambda/pricing/). IAM: la política RayitoTemplateBuilder (la misma que Template.build). Cómo apagarla: no la construyas; borra sus versiones con rayito image. Ejemplo: AgentTemplate(runtimes=("opencode",)).build(bucket="amzn-s3-demo-bucket")

manifest() -> dict[str, object]

El manifiesto rayito.agent-template/1 que se hornea en AGENT_TEMPLATE_MANIFEST_PATH: versiones y hashes de lo instalado, protocolo y las rutas que precarga el demonio.

context_files() -> dict[str, bytes]

Los ficheros del contexto de build (nombre relativo -> bytes).

to_template(template_class: type[Template] = Template) -> Template

El Template equivalente (puro: no lee ni escribe nada).

build(*, bucket: str, force: bool = False, timeout: float = DEFAULT_BUILD_TIMEOUT_SECONDS, on_build_logs: Callable[[str], None] | None = None, region: str | None = None, session: Any = None) -> BuildInfo

Construye la imagen name con Template.build (ver el bloque de coste de la clase).

AsyncAgentTemplate(name: str = DEFAULT_AGENT_TEMPLATE_NAME, base: str = DEFAULT_AGENT_TEMPLATE_BASE, runtimes: tuple[str, ...] = AGENT_TEMPLATE_RUNTIMES, prefetch: bool = True, kernel_warmup: bool = False, memory_mib: int = AGENT_MIN_MEMORY_MIB, base_version: str | None = None) dataclass

Bases: AgentTemplate

AgentTemplate con build en versión async (AsyncTemplate).

agent_pool_warmup(runtime: str | AgentRuntime = DEFAULT_AGENT_RUNTIME) -> tuple[WarmupStep, ...]

Los pasos de PoolConfig(warmup=...) para un pool de agentes: cargan el runtime en la caché de páginas antes de aparcar cada plaza.

Un pool sólo compensa si llegan muchas conversaciones nuevas cuyo primer mensaje tiene que ser rápido: los turnos de una misma conversación de menos de 8 h van mejor en una sola VM pausada entre turnos (pause() y connect(), o la auto-suspensión de IdlePolicy), y más allá de 8 h, con persist=. Guía "Agente en el sandbox", "¿Qué uso?".

Coste y activación

Activa: PoolConfig(warmup=agent_pool_warmup(...)). Recursos y llamadas AWS: ninguna llamada nueva; cada plaza corre los pasos antes de su pause() y su snapshot crece con la caché. Coste aproximado: ≈ $0,0054 por ciclo de reciclado (≈ 103 al mes) ≈ $0,64/plaza/mes, frente a ≈ $0,60 de una plaza base; cada take() lee ≈ $0,0014. Tiempos medidos en AWS (AWS_API_NOTES Q147) por precios de lista, us-east-1, consultados 2026-10-06 (https://aws.amazon.com/lambda/pricing/). IAM: ninguna además de la del pool. Cómo apagarla: PoolConfig(warmup=()) (por defecto). Ejemplo: PoolConfig(size=2, template="rayito-agent", allow_internet_access=False, warmup=agent_pool_warmup("opencode"))

WarmupStep(cmd: str, background: bool = False, timeout_seconds: float = DEFAULT_WARMUP_STEP_TIMEOUT_SECONDS, tag: str | None = None) dataclass

Un paso de calentamiento: agent.prepare() y el warmup= de un pool (B2) corren la misma lista. background=True no espera a que termine (un proceso que sigue vivo); si no, se espera su salida con timeout_seconds.

Listado

SandboxListPaginator(*, io: ListingIo, request: ListingRequest, resolve_template_arn: Callable[[str], str])

Un listado reanudable (Sandbox.paginate), la forma del SandboxPaginator de E2B.

next_token es opaco: guárdalo y pásalo a un Sandbox.paginate() nuevo con los mismos filtros para seguir donde se quedó. No lo loguees: lleva el ARN de la imagen y la huella de los filtros.

next_token: str | None property

Antes de la primera llamada, el token recibido; después, el cursor mientras has_next, si no None.

next_items() -> list[SandboxListItem]

La siguiente página de hasta limit items (todos sin limit). Puede venir vacía mientras has_next siga siendo True.

AsyncSandboxListPaginator(*, io: AsyncListingIo, request: ListingRequest, resolve_template_arn: Callable[[str], str])

sandbox_sync.listing.SandboxListPaginator con next_items() asíncrono. Construirlo no hace E/S.

next_token: str | None property

Antes de la primera llamada, el token recibido; después, el cursor mientras has_next, si no None.