
Runtime sécurisé pour mettre en bac à sable les tâches des agents IA. Exécutez du code non fiable dans des environnements WebAssembly isolés.
Capsule est un runtime pour exécuter du code non fiable dans des environnements isolés. Chaque tâche s'exécute dans son propre bac à sable WebAssembly, offrant :
Annotez simplement vos fonctions Python avec le décorateur @task :
from capsule import task
@task(name="analyze_data", compute="MEDIUM", ram="512MB", timeout="30s", max_retries=1)
def analyze_data(dataset: list) -> dict:
"""Traite les données dans un environnement isolé et contrôlé en ressources."""
# Votre code s'exécute en toute sécurité dans un bac à sable Wasm
return {"processed": len(dataset), "status": "complete"}
Utilisez la fonction d'encapsulation task() avec un accès complet à l'écosystème 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 => {
// Votre code s'exécute en toute sécurité dans un bac à sable Wasm
return { processed: dataset.length, status: "complete" };
});
[!NOTE] Le runtime nécessite une tâche nommée
"main"comme point d'entrée. Python en créera une automatiquement si aucune n'est définie, mais il est recommandé de la définir explicitement.
Lorsque vous exécutez capsule run main.py (ou main.ts), votre code est compilé en un module WebAssembly et exécuté dans des bacs à sable isolés.
Chaque tâche opère dans son propre bac à sable avec des limites de ressources configurables, garantissant que les échecs sont contenus et ne se propagent pas à d'autres parties de votre flux de travail. Le système hôte contrôle tous les aspects de l'exécution, de l'allocation CPU via le comptage de carburant Wasm aux contraintes de mémoire et à l'application des délais d'attente.
pip install capsule-run
Créez hello.py :
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main() -> str:
return "Hello from Capsule!"
Exécutez-le :
capsule run hello.py
npm install -g @capsule-run/cli
npm install @capsule-run/sdk
Créez hello.ts :
import { task } from "@capsule-run/sdk";
export const main = task({
name: "main",
compute: "LOW",
ram: "64MB"
}, (): string => {
return "Hello from Capsule!";
});
Exécutez-le :
capsule run hello.ts
[!TIP] Ajoutez
--verbosepour voir les détails d'exécution des tâches en temps réel.
La fonction run() vous permet d'exécuter des tâches par programme depuis votre code au lieu d'utiliser la CLI. Les args sont automatiquement transmis comme paramètres à la tâche main.
from capsule import run
result = await run(
file="./sandbox.py",
args=["code to execute"]
)
Créez sandbox.py :
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main(code: str) -> str:
return eval(code)
[!IMPORTANT] Vous avez besoin de
@capsule-run/clidans vos dépendances pour utiliser les fonctions d'exécution en TypeScript.
import { run } from '@capsule-run/sdk/runner';
const result = await run({
file: './sandbox.ts',
args: ['code to execute']
});
Créez sandbox.ts :
import { task } from "@capsule-run/sdk";
export const main = task({
name: "main",
compute: "LOW",
ram: "64MB"
}, (code: string): string => {
return eval(code);
});
[!TIP] Si vous cherchez une solution préconfigurée et prête à l'emploi, consultez l'adaptateur Python ou l'adaptateur TypeScript.
Configurez vos tâches avec ces paramètres :
Capsule contrôle l'utilisation du CPU via le mécanisme de carburant de WebAssembly, qui mesure l'exécution des instructions. Le niveau de calcul détermine la quantité de carburant que votre tâche reçoit.
compute="1000000") pour un contrôle précis des limites d'exécution.Chaque tâche renvoie une enveloppe JSON structurée contenant à la fois le résultat et les métadonnées d'exécution :
{
"success": true,
"result": "Hello from Capsule!",
"error": null,
"execution": {
"task_name": "data_processor",
"duration_ms": 1523,
"retries": 0,
"fuel_consumed": 45000,
"ram_used": 1200000,
"host_requests": [{...}]
}
}
Champs de réponse :
success — Booléen indiquant si la tâche s'est terminée avec succèsresult — La valeur de retour réelle de votre tâche (json, chaîne, null en cas d'échec, etc.)error — Détails de l'erreur si la tâche a échoué ({ error_type: string, message: string })execution — Métriques de performance :
task_name — Nom de la tâche exécutéeduration_ms — Temps d'exécution en millisecondesretries — Nombre de tentatives effectuéesfuel_consumed — Ressources CPU utilisées (voir Niveaux de calcul)ram_used — Mémoire maximale utilisée en octetshost_requests — Liste des requêtes hôte effectuées par la tâcheLes tâches peuvent faire des requêtes HTTP vers les domaines spécifiés dans allowed_hosts. Par défaut, aucune requête sortante n'est autorisée ([]). Fournissez une liste blanche de domaines pour accorder l'accès, ou utilisez ["*"] pour autoriser tous les domaines.
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();
});
Les tâches peuvent lire et écrire des fichiers dans les répertoires spécifiés dans allowed_files. Toute tentative d'accès à des fichiers en dehors de ces répertoires est impossible.
[!NOTE]
allowed_filesne prend en charge que les chemins de répertoires, pas les fichiers individuels.
Chaque entrée peut être un chemin simple (lecture-écriture par défaut) ou un objet structuré avec un mode explicite :
"read-only" (ou "ro")"read-write" (ou "rw")Les opérations standard sur les fichiers de Python fonctionnent normalement. Utilisez open(), os, pathlib ou toute bibliothèque de manipulation de fichiers.
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
Les chaînes simples sont toujours acceptées : allowed_files=["./output"] par défaut en lecture-écriture.
Les modules intégrés courants de Node.js sont disponibles. Utilisez le module fs standard :
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;
});
Les chaînes simples sont toujours acceptées : allowedFiles: ["./output"] par défaut en lecture-écriture.
--mount)Le drapeau --mount (CLI) ou le paramètre mounts (SDK) monte un répertoire hôte dans le bac à sable sous un alias. Les montages se propagent aux sous-tâches et ajoutent l'accès à de nouveaux chemins, ils ne modifient pas le mode d'accès des chemins déjà déclarés dans allowed_files.
Format : HOST_PATH[::GUEST_PATH][:ro|:rw]
CLI
# Monter un espace de travail de session et l'exposer comme "workspace" à l'intérieur de la tâche
capsule run main.py --mount sessions/abc123_workspace::workspace
# Plusieurs répertoires
capsule run main.py \
--mount sessions/abc123_workspace::workspace \
--mount sessions/bce456_workspace::workspace:ro
SDK Python
from capsule import run
result = await run(
file="main.py",
mounts=[".capsule/sessions/abc123_workspace::workspace"],
)
SDK TypeScript / JavaScript
import { run } from "@capsule-run/sdk";
const result = await run({
file: "main.py",
mounts: [".capsule/sessions/abc123_workspace::workspace"],
});
À l'intérieur de la tâche, le répertoire est accessible via le chemin invité :
# la tâche le voit sous "workspace/", pas sous le chemin complet de session
with open("workspace/output.txt", "w") as f:
f.write("done")
[!NOTE] Les chemins
--mountdoivent être relatifs et ne doivent pas sortir de la racine du projet. Les chemins absolus sont rejetés.
Les tâches peuvent accéder aux variables d'environnement pour lire la configuration, les clés API ou d'autres paramètres d'exécution.
Utilisez os.environ standard de Python pour accéder aux variables d'environnement :
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}
Utilisez process.env standard pour accéder aux variables d'environnement :
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 };
});
Vous pouvez créer un fichier capsule.toml à la racine de votre projet pour définir les options par défaut de toutes les tâches et définir les métadonnées du workflow :
# capsule.toml
[workflow]
name = "My Workflow"
version = "1.0.0"
entrypoint = "src/main.py" # Fichier par défaut lors de l'exécution de `capsule run`
[tasks]
default_compute = "MEDIUM"
default_ram = "256MB"
default_timeout = "30s"
default_max_retries = 2
Avec un point d'entrée défini, vous pouvez simplement exécuter :
capsule run
Les options au niveau de la tâche remplacent toujours ces valeurs par défaut lorsqu'elles sont spécifiées.
Lorsque vous exécutez votre code, Capsule crée un dossier .capsule à la racine de votre projet. C'est le cache de compilation. Il stocke les artefacts compilés afin que les exécutions suivantes soient rapides (de quelques secondes à quelques millisecondes).
[!TIP]
.capsuledoit être ajouté à.gitignore. Le cache est spécifique à votre propre environnement et sera régénéré automatiquement.
.capsule/
├── wasm/
│ ├── main_a1b2c3d4.wasm # Module WebAssembly compilé
│ └── main_a1b2c3d4.cwasm # Cache précompilé natif
├── wit/ # Définitions d'interface
└── trace.db # Journaux d'exécution
Utilisez capsule build pour précompiler à l'avance et éviter le coût de compilation lors de la première exécution :
capsule build main.ts # ou `main.py`
L'exécution directe de code source (comme .py ou .ts) évalue et compile votre fichier au moment de l'exécution. Bien que ce soit idéal pour le développement, cette étape de compilation ajoute quelques secondes de latence lors du premier appel. Pour les cas d'utilisation où une latence inférieure à la seconde est critique, vous devez construire vos tâches à l'avance.
# Génère un fichier hello.wasm optimisé
capsule build hello.py --export
# Exécute l'artefact compilé directement
capsule exec hello.wasm
[!NOTE] Ou depuis votre code existant :
from capsule import run result = await run( file="./hello.wasm", # ou `hello.py` args=[] ) print(f"Tâche terminée : {result['result']}")
L'exécution d'un fichier .wasm contourne complètement le compilateur, réduisant le temps d'initialisation à quelques millisecondes tout en utilisant un format optimisé nativement (.cwasm) en arrière-plan.
[!NOTE] TypeScript/JavaScript a une compatibilité plus large que Python car il ne repose pas sur des liaisons natives.
Python : La plupart des bibliothèques Python standard fonctionnent parfaitement. Les packages qui utilisent des extensions C nécessitent une roue compilée wasm32-wasi. De nombreux packages populaires comme numpy et pandas n'en fournissent pas encore, donc ils ne fonctionneront pas à l'intérieur du bac à sable. Cependant, votre code hôte (utilisant run()) a accès à tout l'écosystème Python, y compris n'importe quel package pip et les extensions natives. Voir utilisation dans le code
TypeScript/JavaScript : Les packages npm et les modules ES fonctionnent. Les modules intégrés courants de Node.js sont disponibles. Si vous rencontrez des problèmes avec un module intégré, n'hésitez pas à ouvrir un problème.
Les contributions sont les bienvenues !
Prérequis : Rust (dernière version stable), Python 3.13+, Node.js 22+
git clone https://github.com/capsulerun/capsule.git
cd capsule
# Construire et installer la CLI
cargo install --path crates/capsule-cli
# SDK Python (installation modifiable)
pip install -e crates/capsule-sdk/python
# SDK TypeScript (lier pour le développement local)
cd crates/capsule-sdk/javascript
npm install && npm run build && npm link
# Ensuite dans votre projet : npm link @capsule-run/sdk
git checkout -b feature/amazing-featurecargo test (uniquement si vous modifiez crates/capsule-cli ou crates/capsule-core)Besoin d'aide ? Ouvrez un problème
Capsule s'appuie sur ces projets open source :
Ce projet est sous licence Apache License 2.0 - voir le fichier LICENSE pour plus de détails.
| Paramètre | Description | Type | Défaut | Exemple |
|---|
name | Identifiant de la tâche | str | nom de la fonction (Python) / obligatoire (TS) | "process_data" |
compute | Niveau d'allocation CPU : "LOW", "MEDIUM" ou "HIGH" | str | "MEDIUM" | "HIGH" |
ram | Limite mémoire pour la tâche | str | illimitée | "512MB", "2GB" |
timeout | Durée d'exécution maximale | str | illimitée | "30s", "5m", "1h" |
max_retries / maxRetries | Nombre de tentatives en cas d'échec | int | 0 | 3 |
allowed_files / allowedFiles | Dossiers accessibles dans le bac à sable (avec mode d'accès optionnel) | list | [] | ["./data"], [{"path": "./data", "mode": "ro"}] |
allowed_hosts / allowedHosts | Domaines accessibles dans le bac à sable | list | [] | ["api.openai.com", "*.anthropic.com"] |
env_variables / envVariables | Variables d'environnement accessibles dans le bac à sable | list | [] | ["API_KEY"] |
| Partie | Requis | Description |
|---|
HOST_PATH | oui | Chemin sur la machine hôte (relatif à cwd, doit rester dans la racine du projet) |
::GUEST_PATH | non | Chemin que la tâche voit à l'intérieur du bac à sable. Par défaut égal à HOST_PATH |
:ro / :rw | non | Mode d'accès. Par défaut lecture-écriture |