
Entorno de ejecución seguro para aislar tareas de agentes de IA. Ejecute código no confiable en entornos WebAssembly aislados.
Capsule es un runtime para ejecutar código no confiable en entornos aislados. Cada tarea se ejecuta dentro de su propio sandbox de WebAssembly, proporcionando:
Simplemente anota tus funciones de Python con el decorador @task:
from capsule import task
@task(name="analyze_data", compute="MEDIUM", ram="512MB", timeout="30s", max_retries=1)
def analyze_data(dataset: list) -> dict:
"""Procesa datos en un entorno aislado y con control de recursos."""
# Tu código se ejecuta de forma segura en un sandbox Wasm
return {"processed": len(dataset), "status": "complete"}
Usa la función envolvente task() con acceso completo al ecosistema npm:
import { task } from "@capsule-run/sdk";
export const analyzeData = task({
name: "analyze_data",
compute: "MEDIUM",
ram: "512MB",
timeout: "30s",
maxRetries: 1
}, (dataset: number[]): object => {
// Tu código se ejecuta de forma segura en un sandbox Wasm
return { processed: dataset.length, status: "complete" };
});
[!NOTA] El runtime requiere una tarea llamada
"main"como punto de entrada. Python creará una automáticamente si no se define ninguna, pero se recomienda establecerla explícitamente.
Cuando ejecutas capsule run main.py (o main.ts), tu código se compila en un módulo WebAssembly y se ejecuta en sandboxes aislados.
Cada tarea opera dentro de su propio sandbox con límites de recursos configurables, garantizando que los fallos estén contenidos y no se propaguen a otras partes de tu flujo de trabajo. El sistema anfitrión controla todos los aspectos de la ejecución, desde la asignación de CPU mediante el medidor de combustible de Wasm hasta las restricciones de memoria y la aplicación del tiempo de espera.
pip install capsule-run
Crea hello.py:
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main() -> str:
return "¡Hola desde Capsule!"
Ejecútalo:
capsule run hello.py
npm install -g @capsule-run/cli
npm install @capsule-run/sdk
Crea hello.ts:
import { task } from "@capsule-run/sdk";
export const main = task({
name: "main",
compute: "LOW",
ram: "64MB"
}, (): string => {
return "¡Hola desde Capsule!";
});
Ejecútalo:
capsule run hello.ts
[!CONSEJO] Agrega
--verbosepara ver detalles en tiempo real de la ejecución de la tarea.
La función run() te permite ejecutar tareas programáticamente desde tu código en lugar de usar la CLI. Los args se reenvían automáticamente como parámetros a la tarea main.
from capsule import run
result = await run(
file="./sandbox.py",
args=["código a ejecutar"]
)
Crea sandbox.py:
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main(code: str) -> str:
return eval(code)
[!IMPORTANTE] Necesitas
@capsule-run/clien tus dependencias para usar las funciones de ejecutor en TypeScript.
import { run } from '@capsule-run/sdk/runner';
const result = await run({
file: './sandbox.ts',
args: ['código a ejecutar']
});
Crea sandbox.ts:
import { task } from "@capsule-run/sdk";
export const main = task({
name: "main",
compute: "LOW",
ram: "64MB"
}, (code: string): string => {
return eval(code);
});
[!CONSEJO] Si buscas una solución preconfigurada y lista para usar, consulta el adaptador de Python o el adaptador de TypeScript.
Configura tus tareas con estos parámetros:
Capsule controla el uso de CPU mediante el mecanismo de combustible de WebAssembly, que mide la ejecución de instrucciones. El nivel de cómputo determina cuánto combustible recibe tu tarea.
compute="1000000") para un control preciso sobre los límites de ejecución.Cada tarea devuelve un sobre JSON estructurado que contiene tanto el resultado como los metadatos de ejecución:
{
"success": true,
"result": "¡Hola desde Capsule!",
"error": null,
"execution": {
"task_name": "data_processor",
"duration_ms": 1523,
"retries": 0,
"fuel_consumed": 45000,
"ram_used": 1200000,
"host_requests": [{...}]
}
}
Campos de la respuesta:
success — Booleano que indica si la tarea se completó con éxitoresult — El valor de retorno real de tu tarea (json, string, null en caso de fallo, etc.)error — Detalles del error si la tarea falló ({ error_type: string, message: string })execution — Métricas de rendimiento:
task_name — Nombre de la tarea ejecutadaduration_ms — Tiempo de ejecución en milisegundosretries — Número de reintentos realizadosfuel_consumed — Recursos de CPU utilizados (ver Niveles de cómputo)ram_used — Pico de memoria usada en byteshost_requests — Lista de solicitudes al anfitrión realizadas por la tareaLas tareas pueden realizar solicitudes HTTP a los dominios especificados en allowed_hosts. Por defecto, no se permiten solicitudes salientes ([]). Proporciona una lista blanca de dominios para otorgar acceso, o usa ["*"] para permitir todos los dominios.
import json
from capsule import task
from urllib.request import urlopen
@task(name="main", allowed_hosts=["api.openai.com", "*.anthropic.com"])
def main() -> dict:
with urlopen("https://api.openai.com/v1/models") as response:
return json.loads(response.read().decode("utf-8"))
import { task } from "@capsule-run/sdk";
export const main = task({
name: "main",
allowedHosts: ["api.openai.com", "*.anthropic.com"]
}, async () => {
const response = await fetch("https://api.openai.com/v1/models");
return response.json();
});
Las tareas pueden leer y escribir archivos dentro de los directorios especificados en allowed_files. Cualquier intento de acceder a archivos fuera de estos directorios no es posible.
[!NOTA]
allowed_filessolo admite rutas de directorio, no archivos individuales.
Cada entrada puede ser una ruta simple (lectura-escritura por defecto) o un objeto estructurado con un mode explícito:
"read-only" (o "ro")"read-write" (o "rw")Las operaciones estándar de archivos de Python funcionan normalmente. Usa open(), os, pathlib, o cualquier biblioteca de manipulación de archivos.
from capsule import task
@task(name="main", allowed_files=[
{"path": "./data", "mode": "read-only"},
{"path": "./output", "mode": "read-write"},
])
def main() -> str:
with open("./data/input.txt") as f:
content = f.read()
with open("./output/result.txt", "w") as f:
f.write(content)
return content
Las cadenas simples aún se aceptan: allowed_files=["./output"] por defecto es lectura-escritura.
Los módulos integrados comunes de Node.js están disponibles. Usa el módulo fs estándar:
import { task } from "@capsule-run/sdk";
import fs from "fs/promises";
export const main = task({
name: "main",
allowedFiles: [
{ path: "./data", mode: "read-only" },
{ path: "./output", mode: "read-write" },
]
}, async () => {
const content = await fs.readFile("./data/input.txt", "utf8");
await fs.writeFile("./output/result.txt", content);
return content;
});
Las cadenas simples aún se aceptan: allowedFiles: ["./output"] por defecto es lectura-escritura.
--mount)La bandera --mount (CLI) o el parámetro mounts (SDK) monta un directorio del anfitrión dentro del sandbox bajo un alias. Los montajes se propagan a las subtareas y agregan acceso a nuevas rutas; no cambian el modo de acceso de las rutas ya declaradas en allowed_files.
Formato: RUTA_ANFITRION[::RUTA_INVITADO][:ro|:rw]
CLI
# Monta un espacio de trabajo de sesión y expónelo como "workspace" dentro de la tarea
capsule run main.py --mount sessions/abc123_workspace::workspace
# Múltiples directorios
capsule run main.py \
--mount sessions/abc123_workspace::workspace \
--mount sessions/bce456_workspace::workspace:ro
SDK de Python
from capsule import run
result = await run(
file="main.py",
mounts=[".capsule/sessions/abc123_workspace::workspace"],
)
SDK de TypeScript / JavaScript
import { run } from "@capsule-run/sdk";
const result = await run({
file: "main.py",
mounts: [".capsule/sessions/abc123_workspace::workspace"],
});
Dentro de la tarea, el directorio se accede a través de la ruta del invitado:
# la tarea lo ve en "workspace/", no en la ruta completa de la sesión
with open("workspace/output.txt", "w") as f:
f.write("done")
[!NOTA] Las rutas de
--mountdeben ser relativas y no deben escapar de la raíz del proyecto. Las rutas absolutas son rechazadas.
Las tareas pueden acceder a variables de entorno para leer configuraciones, claves de API u otros ajustes de tiempo de ejecución.
Usa os.environ estándar de Python para acceder a las variables de entorno:
from capsule import task
import os
@task(name="main", env_variables=["API_KEY"])
def main() -> dict:
api_key = os.environ.get("API_KEY")
return {"api_key": api_key}
Usa process.env estándar para acceder a las variables de entorno:
import { task } from "@capsule-run/sdk";
export const main = task({
name: "main",
envVariables: ["API_KEY"]
}, () => {
const apiKey = process.env.API_KEY;
return { apiKeySet: apiKey !== undefined };
});
Puedes crear un archivo capsule.toml en la raíz de tu proyecto para establecer opciones predeterminadas para todas las tareas y definir metadatos del flujo de trabajo:
# capsule.toml
[workflow]
name = "Mi flujo de trabajo"
version = "1.0.0"
entrypoint = "src/main.py" # Archivo predeterminado al ejecutar `capsule run`
[tasks]
default_compute = "MEDIUM"
default_ram = "256MB"
default_timeout = "30s"
default_max_retries = 2
Con un punto de entrada definido, simplemente puedes ejecutar:
capsule run
Las opciones a nivel de tarea siempre anulan estos valores predeterminados cuando se especifican.
Cuando ejecutas tu código, Capsule crea una carpeta .capsule en la raíz de tu proyecto. Esta es la caché de compilación. Almacena artefactos compilados para que las ejecuciones posteriores sean rápidas (de segundos a unos pocos milisegundos).
[!CONSEJO]
.capsuledebería agregarse a.gitignore. La caché es específica de tu propio entorno y se regenerará automáticamente.
.capsule/
├── wasm/
│ ├── main_a1b2c3d4.wasm # Módulo WebAssembly compilado
│ └── main_a1b2c3d4.cwasm # Caché nativa precompilada
├── wit/ # Definiciones de interfaz
└── trace.db # Registros de ejecución
Usa capsule build para precompilar con anticipación y evitar el costo de compilación en la primera ejecución:
capsule build main.ts # o `main.py`
Ejecutar código fuente directamente (como .py o .ts) evalúa y compila tu archivo en tiempo de ejecución. Aunque es excelente para el desarrollo, este paso de compilación agrega unos segundos de latencia en la primera llamada. Para casos de uso donde la latencia de submilisegundos es crítica, debes compilar tus tareas con anticipación.
# Genera un archivo hello.wasm optimizado
capsule build hello.py --export
# Ejecuta el artefacto compilado directamente
capsule exec hello.wasm
[!NOTA] O desde tu código existente:
from capsule import run result = await run( file="./hello.wasm", # o `hello.py` args=[] ) print(f"Tarea completada: {result['result']}")
Ejecutar un archivo .wasm omite completamente el compilador, reduciendo el tiempo de inicialización a milisegundos mientras se utiliza un formato nativamente optimizado (.cwasm) en segundo plano.
[!NOTA] TypeScript/JavaScript tiene una compatibilidad más amplia que Python, ya que no depende de enlaces nativos.
Python: La mayoría de las bibliotecas estándar de Python funcionan perfectamente. Los paquetes que usan extensiones C requieren una rueda compilada para wasm32-wasi. Muchos paquetes populares como numpy y pandas aún no envían una, por lo que no funcionarán dentro del sandbox. Sin embargo, tu código anfitrión (usando run()) tiene acceso a todo el ecosistema de Python, incluidos cualquier paquete pip y extensiones nativas. Ver uso desde el código
TypeScript/JavaScript: Los paquetes npm y los módulos ES funcionan. Los módulos integrados comunes de Node.js están disponibles. Si tienes algún problema con un módulo integrado, no dudes en abrir un issue.
¡Las contribuciones son bienvenidas!
Requisitos: Rust (última estable), Python 3.13+, Node.js 22+
git clone https://github.com/capsulerun/capsule.git
cd capsule
# Compila e instala la CLI
cargo install --path crates/capsule-cli
# SDK de Python (instalación editable)
pip install -e crates/capsule-sdk/python
# SDK de TypeScript (enlace para desarrollo local)
cd crates/capsule-sdk/javascript
npm install && npm run build && npm link
# Luego en tu proyecto: npm link @capsule-run/sdk
git checkout -b feature/caracteristica-increiblecargo test (solo necesario si modificas crates/capsule-cli o crates/capsule-core)¿Necesitas ayuda? Abre un issue
Capsule se basa en estos proyectos de código abierto:
Este proyecto está licenciado bajo la Apache License 2.0 – consulta el archivo LICENSE para más detalles.
| Parámetro | Descripción | Tipo | Valor por defecto | Ejemplo |
|---|
name | Identificador de la tarea | str | nombre de la función (Python) / obligatorio (TS) | "process_data" |
compute | Nivel de asignación de CPU: "LOW", "MEDIUM" o "HIGH" | str | "MEDIUM" | "HIGH" |
ram | Límite de memoria para la tarea | str | ilimitado | "512MB", "2GB" |
timeout | Tiempo máximo de ejecución | str | ilimitado | "30s", "5m", "1h" |
max_retries / maxRetries | Número de intentos de reintento en caso de fallo | int | 0 | 3 |
allowed_files / allowedFiles | Carpetas accesibles en el sandbox (con modo de acceso opcional) | list | [] | ["./data"], [{"path": "./data", "mode": "ro"}] |
allowed_hosts / allowedHosts | Dominios accesibles en el sandbox | list | [] | ["api.openai.com", "*.anthropic.com"] |
env_variables / envVariables | Variables de entorno accesibles en el sandbox | list | [] | ["API_KEY"] |
| Parte | Obligatorio | Descripción |
|---|
RUTA_ANFITRION | sí | Ruta en la máquina anfitriona (relativa a cwd, debe permanecer dentro de la raíz del proyecto) |
::RUTA_INVITADO | no | Ruta que la tarea ve dentro del sandbox. Por defecto es RUTA_ANFITRION |
:ro / :rw | no | Modo de acceso. Por defecto es lectura-escritura |