Capsule 是一个在隔离环境中执行不受信任代码的运行时。每个任务都在自己的 WebAssembly 沙箱中运行,提供:
只需用 @task 装饰器注解你的 Python 函数:
from capsule import task
@task(name="analyze_data", compute="MEDIUM", ram="512MB", timeout="30s", max_retries=1)
def analyze_data(dataset: list) -> dict:
"""在隔离、资源控制的环境中处理数据。"""
# 你的代码在 Wasm 沙箱中安全运行
return {"processed": len(dataset), "status": "complete"}
使用 task() 包装函数,可完全访问 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 => {
// 你的代码在 Wasm 沙箱中安全运行
return { processed: dataset.length, status: "complete" };
});
[!NOTE] 运行时需要一个名为
"main"的任务作为入口点。如果未定义,Python 将自动创建一个,但建议显式设置。
当你运行 capsule run main.py(或 main.ts)时,你的代码会被编译成 WebAssembly 模块并在隔离沙箱中执行。
每个任务都在其自己的沙箱中运行,具有可配置的资源限制,确保故障被隔离,不会级联到工作流的其他部分。主机系统控制执行的每个方面,从通过 Wasm 燃料计量的 CPU 分配到内存限制和超时强制。
pip install capsule-run
创建 hello.py:
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main() -> str:
return "Hello from Capsule!"
运行它:
capsule run hello.py
npm install -g @capsule-run/cli
npm install @capsule-run/sdk
创建 hello.ts:
import { task } from "@capsule-run/sdk";
export const main = task({
name: "main",
compute: "LOW",
ram: "64MB"
}, (): string => {
return "Hello from Capsule!";
});
运行它:
capsule run hello.ts
[!TIP] 添加
--verbose以查看实时任务执行详情。
run() 函数允许你从代码中以编程方式执行任务,而不是使用 CLI。args 会自动作为参数转发给 main 任务。
from capsule import run
result = await run(
file="./sandbox.py",
args=["code to execute"]
)
创建 sandbox.py:
from capsule import task
@task(name="main", compute="LOW", ram="64MB")
def main(code: str) -> str:
return eval(code)
[!IMPORTANT] 你的依赖中需要
@capsule-run/cli才能在 TypeScript 中使用 runner 函数。
import { run } from '@capsule-run/sdk/runner';
const result = await run({
file: './sandbox.ts',
args: ['code to execute']
});
创建 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] 如果你在寻找预配置、开箱即用的解决方案,请查看 Python 适配器 或 TypeScript 适配器。
使用以下参数配置你的任务:
Capsule 通过 WebAssembly 的燃料机制控制 CPU 使用量,该机制对指令执行进行计量。计算级别决定了你的任务获得多少燃料。
compute="1000000"),以便精确控制执行限制。每个任务返回一个结构化的 JSON 信封,包含结果和执行元数据:
{
"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": [{...}]
}
}
响应字段:
success — 布尔值,指示任务是否成功完成result — 任务的实际返回值(json、字符串,失败时为 null 等)error — 任务失败时的错误详情({ error_type: string, message: string })execution — 性能指标:
task_name — 执行的任务名称duration_ms — 执行时间(毫秒)retries — 发生的重试次数fuel_consumed — 使用的 CPU 资源(参见 计算级别)ram_used — 峰值内存使用量(字节)host_requests — 任务发出的主机请求列表任务可以对 allowed_hosts 中指定的域名发起 HTTP 请求。默认情况下,不允许出站请求([])。提供域名允许列表以授予访问权限,或使用 ["*"] 允许所有域名。
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();
});
任务可以读取和写入 allowed_files 中指定目录内的文件。尝试访问这些目录之外的文件是不可能的。
[!NOTE]
allowed_files仅支持目录路径,不支持单个文件。
每个条目可以是普通路径(默认为读写)或带有显式 mode 的结构化对象:
"read-only"(或 "ro")"read-write"(或 "rw")Python 的标准文件操作正常工作。使用 open()、os、pathlib 或任何文件操作库。
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
普通字符串仍然接受:allowed_files=["./output"] 默认为读写。
常见的 Node.js 内置模块可用。使用标准 fs 模块:
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;
});
普通字符串仍然接受:allowedFiles: ["./output"] 默认为读写。
--mount)--mount 标志(CLI)或 mounts 参数(SDK)将主机目录挂载到沙箱中,并使用别名。挂载会传播到子任务,并添加对新路径的访问,但不会更改 allowed_files 中已声明路径的访问模式。
格式: HOST_PATH[::GUEST_PATH][:ro|:rw]
CLI
# 挂载一个会话工作区,并在任务内将其暴露为 "workspace"
capsule run main.py --mount sessions/abc123_workspace::workspace
# 多个目录
capsule run main.py \
--mount sessions/abc123_workspace::workspace \
--mount sessions/bce456_workspace::workspace:ro
Python SDK
from capsule import run
result = await run(
file="main.py",
mounts=[".capsule/sessions/abc123_workspace::workspace"],
)
TypeScript / JavaScript SDK
import { run } from "@capsule-run/sdk";
const result = await run({
file: "main.py",
mounts: [".capsule/sessions/abc123_workspace::workspace"],
});
在任务内部,通过访客路径访问目录:
# 任务在 "workspace/" 下看到它,而不是完整会话路径
with open("workspace/output.txt", "w") as f:
f.write("done")
[!NOTE]
--mount路径必须是相对路径,且不得转义项目根目录。绝对路径将被拒绝。
任务可以访问环境变量来读取配置、API 密钥或其他运行时设置。
使用 Python 的标准 os.environ 访问环境变量:
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}
使用标准 process.env 访问环境变量:
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 };
});
你可以在项目根目录中创建一个 capsule.toml 文件,为所有任务设置默认选项并定义工作流元数据:
# capsule.toml
[workflow]
name = "My Workflow"
version = "1.0.0"
entrypoint = "src/main.py" # 运行 `capsule run` 时的默认文件
[tasks]
default_compute = "MEDIUM"
default_ram = "256MB"
default_timeout = "30s"
default_max_retries = 2
定义了入口点后,你可以直接运行:
capsule run
任务级别的选项在指定时始终会覆盖这些默认值。
运行代码时,Capsule 会在项目根目录中创建一个 .capsule 文件夹。这是构建缓存。它存储编译后的工件,以便后续运行很快(从几秒到几毫秒)。
[!TIP]
.capsule应添加到.gitignore。缓存特定于你自己的环境,会自动重新生成。
.capsule/
├── wasm/
│ ├── main_a1b2c3d4.wasm # 编译后的 WebAssembly 模块
│ └── main_a1b2c3d4.cwasm # 原生预编译缓存
├── wit/ # 接口定义
└── trace.db # 执行日志
使用 capsule build 预先编译,跳过首次运行的编译开销:
capsule build main.ts # 或 `main.py`
直接运行源代码(如 .py 或 .ts)会在运行时评估并编译你的文件。虽然适合开发,但首次调用时这个编译步骤会增加几秒延迟。对于亚秒级延迟至关重要的用例,你应该提前构建任务。
# 生成优化后的 hello.wasm 文件
capsule build hello.py --export
# 直接执行编译后的工件
capsule exec hello.wasm
[!NOTE] 或者从现有代码中:
from capsule import run result = await run( file="./hello.wasm", # 或 `hello.py` args=[] ) print(f"任务完成: {result['result']}")
执行 .wasm 文件完全绕过了编译器,将初始化时间减少到毫秒级别,同时在后端使用原生优化的(.cwasm)格式。
[!NOTE] TypeScript/JavaScript 的兼容性比 Python 更广泛,因为它不依赖原生绑定。
Python: 大多数标准 Python 库都能正常工作。使用 C 扩展的包需要 wasm32-wasi 编译的轮子。许多流行的包(如 numpy 和 pandas)尚未提供此类轮子,因此它们在沙箱内无法工作。但是,你的主机代码(使用 run())可以访问完整的 Python 生态系统,包括任何 pip 包和原生扩展。请参见代码内使用
TypeScript/JavaScript: npm 包和 ES 模块可用。常见的 Node.js 内置模块可用。如果你对某个内置模块遇到问题,请随时提出 issue。
欢迎贡献!
前置条件: Rust(最新稳定版)、Python 3.13+、Node.js 22+
git clone https://github.com/capsulerun/capsule.git
cd capsule
# 构建并安装 CLI
cargo install --path crates/capsule-cli
# Python SDK(可编辑安装)
pip install -e crates/capsule-sdk/python
# TypeScript SDK(本地开发链接)
cd crates/capsule-sdk/javascript
npm install && npm run build && npm link
# 然后在你的项目中:npm link @capsule-run/sdk
git checkout -b feature/amazing-featurecargo test(仅当修改 crates/capsule-cli 或 crates/capsule-core 时需要)需要帮助?打开问题
Capsule 基于以下开源项目构建:
本项目采用 Apache License 2.0 许可 - 详见 LICENSE 文件。
| 参数 | 描述 | 类型 | 默认值 | 示例 |
|---|
name | 任务标识符 | str | 函数名 (Python) / 必填 (TS) | "process_data" |
compute | CPU 分配级别:"LOW"、"MEDIUM" 或 "HIGH" | str | "MEDIUM" | "HIGH" |
ram | 任务的内存限制 | str | 无限制 | "512MB", "2GB" |
timeout | 最大执行时间 | str | 无限制 | "30s", "5m", "1h" |
max_retries / maxRetries | 失败时的重试次数 | int | 0 | 3 |
allowed_files / allowedFiles | 沙箱中可访问的文件夹(带有可选访问模式) | list | [] | ["./data"], [{"path": "./data", "mode": "ro"}] |
allowed_hosts / allowedHosts | 沙箱中可访问的域名 | list | [] | ["api.openai.com", "*.anthropic.com"] |
env_variables / envVariables | 沙箱中可访问的环境变量 | list | [] | ["API_KEY"] |
| 部分 | 必需 | 描述 |
|---|
HOST_PATH | 是 | 主机上的路径(相对于 cwd,必须保持在项目根目录内) |
::GUEST_PATH | 否 | 任务在沙箱内看到的路径。默认为 HOST_PATH |
:ro / :rw | 否 | 访问模式。默认为读写 |