Secure WASM runtime to isolate and manage AI agent tasks
Project description
capsule-run
A secure, durable runtime for agentic workflows
Overview
Capsule is a runtime for coordinating AI agent tasks in isolated environments. It is designed to handle untrusted code execution, long-running workflows, large-scale processing, or even multi-agent systems.
Each task runs inside its own WebAssembly sandbox, providing:
- Isolated execution: Each task runs isolated from your host system
- Resource limits: Set CPU, memory, and timeout limits per task
- Automatic retries: Handle failures without manual intervention
- Lifecycle tracking: Monitor which tasks are running, completed, or failed
This enables safe task-level execution of untrusted code within AI agent systems.
Installation
pip install capsule-run
Getting started
Create hello.py:
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main() -> str:
return "Hello from Capsule!"
Run it:
capsule run hello.py
Use
--verboseto display real-time task execution details.
Production
Running source code directly (like .py) evaluates and compiles your file at runtime. While great for development, this compilation step adds a few seconds of latency. For use cases where sub-second latency is critical, you should build your tasks ahead of time.
# Generates an optimized hello.wasm file
capsule build hello.py --export
# Execute the compiled artifact directly
capsule exec hello.wasm
[!NOTE] Or from your existing code:
from capsule import run result = await run( file="./hello.wasm", # or `hello.py` args=[] ) print(f"Task completed: {result['result']}")See Integrate Into an Existing Project for details.
Executing a .wasm file bypasses the compiler completely, reducing initialization time to milliseconds while using a natively optimized (.cwasm) format behind the scenes.
Integrate Into an Existing Project
The run() function lets you execute tasks programmatically from your application code, no CLI needed.
from capsule import run
result = await run(
file="./capsule.py", # or `capsule.wasm`
args=["code to execute"]
)
Create capsule.py:
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main(code: str) -> str:
return exec(code)
How It Works
Simply annotate your Python functions with the @task decorator:
from capsule import task
@task(name="analyze_data", compute="MEDIUM", ram="512MB", timeout="30s", max_retries=1)
def analyze_data(dataset: list) -> dict:
"""Process data in an isolated, resource-controlled environment."""
return {"processed": len(dataset), "status": "complete"}
The runtime requires a task named
"main"as the entry point. Python will create one automatically if none is defined, but it's recommended to set it explicitly.
When you run capsule run main.py, your code is compiled into a WebAssembly module and executed in a dedicated sandbox.
Response Format
Every task returns a structured JSON envelope containing both the result and execution metadata:
{
"success": true,
"result": { "processed": 5, "status": "complete" },
"error": null,
"execution": {
"task_name": "data_processor",
"duration_ms": 1523,
"retries": 0,
"fuel_consumed": 45000,
"ram_used": 1200000,
"host_requests": [{...}]
}
}
Response fields:
success— Boolean indicating whether the task completed successfullyresult— The actual return value from your task (json, string, null on failure etc.)error— Error details if the task failed ({ error_type: string, message: string })execution— Performance metrics:task_name— Name of the executed taskduration_ms— Execution time in millisecondsretries— Number of retry attempts that occurredfuel_consumed— CPU resources used (see Compute Levels)ram_used— Peak memory used in byteshost_requests— List of host requests made by the task
Documentation
Task Configuration Options
| Parameter | Description | Type | Default | Example |
|---|---|---|---|---|
name |
Task identifier | str |
function name | "process_data" |
compute |
CPU level: "LOW", "MEDIUM", "HIGH" |
str |
"MEDIUM" |
"HIGH" |
ram |
Memory limit | str |
unlimited | "512MB", "2GB" |
timeout |
Maximum execution time | str |
unlimited | "30s", "5m" |
max_retries |
Retry attempts on failure | int |
0 |
3 |
allowed_files |
Folders accessible in the sandbox (with optional access mode) | list |
[] |
["./data"], [{"path": "./data", "mode": "ro"}] |
allowed_hosts |
Domains accessible in the sandbox | list |
[] |
["api.openai.com", "*.anthropic.com"] |
env_variables |
Environment variables accessible in the sandbox | list |
[] |
["API_KEY"] |
Compute Levels
- LOW: Minimal allocation for lightweight tasks
- MEDIUM: Balanced resources for typical workloads
- HIGH: Maximum fuel for compute-intensive operations
- CUSTOM: Specify exact fuel value (e.g.,
compute="1000000")
Project Configuration (Optional)
Create a capsule.toml file in your project root to set default options:
[workflow]
name = "My AI Workflow"
version = "1.0.0"
entrypoint = "src/main.py" # Run `capsule run` without specifying a file
[tasks]
default_compute = "MEDIUM"
default_ram = "256MB"
default_timeout = "30s"
Task-level options always override these defaults.
File Access
Tasks can read and write files within directories specified in allowed_files. Any attempt to access files outside these directories is not possible.
allowed_filessupports directory paths only, not individual files.
Each entry can be a plain path (read-write by default) or a dict with an explicit mode: "read-only" (or "ro") or "read-write" (or "rw").
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
Plain strings are still accepted: allowed_files=["./output"] defaults to read-write.
Network Access
Tasks can make HTTP requests to domains specified in allowed_hosts. By default, no outbound requests are allowed ([]). Provide an allowlist of domains to grant access, or use ["*"] to allow all domains.
Wildcards are supported:
*.example.commatches all subdomains ofexample.com.
from capsule import task
from urllib.request import urlopen
import json
@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"))
Environment Variables
Tasks can access environment variables to read configuration, API keys, or other runtime settings. Use Python's standard os.environ to access environment variables:
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}
Compatibility
✅ Supported:
- Pure Python packages and standard library
json,math,re,datetime,collections, etc.
⚠️ Not yet supported (inside the sandbox):
- Packages using C extensions require a
wasm32-wasicompiled wheel (e.g.numpy,pandas)
These limitations only apply to the task file executed in the sandbox. Your host code using
run()has access to the full Python ecosystem, any pip package, native extensions, everything. (see Integrate Into an Existing Project)
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file capsule_run-0.8.5.tar.gz.
File metadata
- Download URL: capsule_run-0.8.5.tar.gz
- Upload date:
- Size: 150.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8165fb35890d632673889a52bd6289c74bd17da654a1c4742e5413db3f5f2d2e
|
|
| MD5 |
1e0e2667564d4f2f1fc52030102d9007
|
|
| BLAKE2b-256 |
b2fd243c7edd2be7ce6bc713814c57b275c5a8ef8365936fb0710eeb7753cc9c
|
File details
Details for the file capsule_run-0.8.5-py3-none-win_amd64.whl.
File metadata
- Download URL: capsule_run-0.8.5-py3-none-win_amd64.whl
- Upload date:
- Size: 10.2 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b05d7a8f12fc22bcc7bce062dfde1fc97e3c02ff301c26c567dcaa1cec51d7b8
|
|
| MD5 |
aad300f0003d08e0c32f7e6862a9c90c
|
|
| BLAKE2b-256 |
4bdd75f6b1a951d1ea1dc1988241d94a89406174ddefa734c45e6249d8044bbc
|
File details
Details for the file capsule_run-0.8.5-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: capsule_run-0.8.5-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 10.6 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7373576d056f90cbd284496a6d48e30ffb4454a7c044b240c6db71fe6770963b
|
|
| MD5 |
c412d11222d8670bf09f71885ee726ad
|
|
| BLAKE2b-256 |
5e7c332dac94563d7524ce6dbe7dadeae7ae7476220ccdab8334b98bc5ea3108
|
File details
Details for the file capsule_run-0.8.5-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: capsule_run-0.8.5-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 9.5 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2dd55eb029acaf487910fcc4c47d41d883a9b02bfe7c5ea755d93dbe9c38d86f
|
|
| MD5 |
1f6017947f24ad315604477ea44e2705
|
|
| BLAKE2b-256 |
897f11b6bbe095675577300d2418adb63692f9a701a277858857014fcc907f86
|