Monty:1 毫秒就能拉起的沙箱
模型往往更擅长编写一个小程序,而不是进行二十次精心编排的工具调用。
博途PLC工程智能体 | AI智能体博途网关 | 梯形图转SCL | 自然语言生成梯形图 | 自然语言生成SCL | 逆向生成程序块文档 | 梯形图在线查看 | 博途编程文档MCP | AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | PLC在线仿真器 | Tripo 3D | Meshy AI
Agentic 系统不断遇到同一个问题。
模型往往更擅长编写一个小程序,而不是进行二十次精心编排的工具调用。
这听起来很好,直到你必须运行那个程序。
合理的答案是容器或远程沙箱,而这正是架构开始变得昂贵的地方。
你要为启动延迟、网络延迟、编排和内存付费。你还要承担文件传输、秘密管理,以及一大堆会话生命周期代码。
Monty v1 赌定,很大一类 agent 工作负载可以用少得多的东西来完成:一个能力被严格限制的小型 Python 解释器,能快速运行模型编写的代码。
Pydantic Monty 是一个用 Rust 编写的 MIT 许可 Python 沙箱,提供 Python、JavaScript 和 Rust 的包。
Pydantic 当前的基准测试显示,当本地池已经打开时,一次全新的沙箱检出加上 1 + 1 大约需要 0.8 毫秒。
已发布的 10,000 会话基准测试每次都执行全新的检出运行 1 + 1,在作者的机器上总计大约 0.7 秒完成。
在这些数字下,很多过去在 agent 循环中不合理的事情开始变得合理了。
1、为什么 1 毫秒比听起来更重要
如果你只运行一次代码,1.5 秒的沙箱启动是可以忍受的;但当代码执行成为模型内部循环的一部分时,它就变得痛苦了。
想象一个 agent:编写一个小的转换函数、检查中间结果、调整代码、调用宿主函数、检查另一个值,然后重复。
如果每次新的执行环境都要花费几百毫秒甚至几秒钟,沙箱就变成了对推理的征税。
Monty 把这项税收压到了噪音地板附近。
官方延迟对比目前报告的数字大致如下:
沙箱 新沙箱启动 10 条命令运行 合计
OSS Monty 0.8 ms 0.4 ms 1.2 ms
Full Monty 1.7 ms 5.3 ms 7 ms
WASI / wasmtime 16 ms 180 ms 200 ms
本地 Docker 195 ms 700 ms 900 ms
远程沙箱服务 1500 ms 400 ms 1900 ms
Deno 中的 Pyodide 2700 ms 35 ms 2700 ms
请将这些视为来自特定机器和配置的基准测试结果,联网系统会因地理位置而异。
重要的是数量级的差距。

当前的 10k 会话 gist 小到近乎可笑:
import time
from pydantic_monty import Monty
N = 10_000
with Monty() as pool:
start = time.perf_counter()
for _ in range(N):
with pool.checkout() as session:
result = session.feed_run('1+1')
elapsed = time.perf_counter() - start
print(f'{N} sessions: {elapsed * 1000:.3f}ms total')
当前版本打印出约 684 毫秒的总时间。
你的数字会有所不同,而它保持如此低的原因很简单:工作进程已经被池化了,检出一个全新的解释器会话非常便宜。

2、快速开始:运行模型编写的 Python
对于 Python:
uv add pydantic-monty
# 或
pip install pydantic-monty
该包需要 Python 3.10 或更高版本。
本地 OSS Monty 没有守护进程、容器镜像或 API 密钥需要配置。
最小的有用示例如下:
from pydantic_monty import Monty
with Monty() as pool:
with pool.checkout() as session:
result = session.feed_run('1 + 2')
print(result)
# 3
池拥有工作子进程,检出给你一个有状态的会话,状态在多次 feed 之间保留:
from pydantic_monty import Monty
with Monty() as pool:
with pool.checkout() as session:
session.feed_run('x = 40')
print(session.feed_run('x + 2'))
# 42
Agent 从这种有状态性中受益匪浅,因为你不必在每一轮之后重放整个草稿板。
如果你的宿主是 JavaScript,同样的模型可通过 npm 获得:
npm install @pydantic/monty
Rust 用户可以用以下命令安装池 crate:
cargo add monty-pool

3、有趣的部分是边界
一个有用的 agent 沙箱必须做两件相互拉扯的事情:
- 让生成的代码完成有意义的工作。
- 阻止生成的代码继承应用程序进程的权限。
Monty 通过显式能力来处理这一点。
默认情况下,沙箱化的代码对文件系统、环境变量、子进程或网络没有环境访问权限。
这些能力根本没有内建到解释器中。
代码只能通过宿主提供的路径到达外部世界,所以围绕 open() 或 socket() 没有你可能搞错的黑名单。

这些路径中最重要的是宿主函数。
假设 agent 需要产品定价。
把数据库凭据和出站网络访问留在沙箱之外,转而给它一个函数:
from pydantic_monty import Monty
def get_price(sku: str):
return {'A1': 3.5, 'B2': 12.0}[sku]
with Monty() as pool:
with pool.checkout() as session:
result = session.feed_run(
"get_price('B2') * 2",
external_lookup={'get_price': get_price},
)
print(result)
# 24.0
当沙箱化的 Python 执行到 get_price 时,执行会挂起。
你受信任的宿主解析该调用并返回一个值,然后解释器从中断处继续。
这比在远程沙箱中路由任意 HTTP 调用、同时试图防止凭据泄露进去要干净得多的 agent 架构。
沙箱可以执行该操作,而授权它的秘密保留在宿主手中。
你仍然需要保护宿主函数本身。
暴露 fetch_any_url(url) 或 read_any_path(path) 就等于你手动构建了一个网络或文件系统能力。
你应该把来自沙箱化代码的每个参数都视为不可信输入。

4、挂起、持久化、恢复
同样的挂起模型支撑了 Monty 最有用的 agent 特性之一:无需保持容器存活的持久执行。
feed_run()自动驱动宿主调用直到完成。feed_start()在每个挂起处把控制权交还给你。
from pydantic_monty import FunctionSnapshot, Monty, MontyComplete
with Monty() as pool:
with pool.checkout() as session:
snapshot = session.feed_start(
'greet(name) + "!"',
inputs={'name': 'Ada'},
)
assert isinstance(snapshot, FunctionSnapshot)
print(snapshot.function_name, snapshot.args)
# greet ('Ada',)
result = snapshot.resume({'return_value': 'hello Ada'})
assert isinstance(result, MontyComplete)
print(result.output)
# hello Ada!
你还可以序列化一个挂起的解释器:
from pydantic_monty import FunctionSnapshot, Monty, MontyComplete
with Monty() as pool:
with pool.checkout() as session:
snapshot = session.feed_start(
'fetch(url)',
inputs={'url': 'https://example.com'},
)
blob = snapshot.dump()
# 稍后,甚至在另一个进程或另一台机器上。
with pool.checkout() as session:
snapshot = session.load_snapshot(blob)
assert isinstance(snapshot, FunctionSnapshot)
result = snapshot.resume({'return_value': 'page contents'})
assert isinstance(result, MontyComplete)
print(result.output)
对于长期运行的 agent 来说,这是一个大事。
传统的有状态沙箱会迫使你保持计算环境存活,因为进程持有状态。
Monty 可以在宿主边界处持久化解释器状态,释放工作进程,稍后恢复一切。
快照文档指出,序列化状态可以包含暂停的调用栈。
这为你提供了一个自然的检查点,用于审批、队列、人在环流程、重试,以及可能休眠数小时或数天的工作流。

一个注意事项:快照是受信任的状态。
永远不要从不可信方获取任意快照字节,并将其作为用户数据反序列化。
5、不交出文件系统的文件访问
Agent 经常需要读写几个文件,而少数几个范围明确的目录通常就够了。
Monty 支持显式目录挂载。
挂载将宿主目录映射到沙箱内的虚拟路径,支持只读、读写和覆盖模式。
from pathlib import Path
from pydantic_monty import Monty, MountDir
with Monty() as pool, MountDir(
host_path=Path('host-dir'),
virtual_path='/data',
mode='read-only',
) as mount:
with pool.checkout() as session:
contents = session.feed_run(
"open('/data/notes.txt').read()",
mount=mount,
)
覆盖模式对 agent 工作负载尤其方便。
读取可以来自宿主文件,而写入保留在内存中,所以真实目录永远不会改变。
安全边界被刻意收窄。
文件系统文档涵盖了路径约束、虚拟路径、符号链接处理和每挂载的内存限制。
如果你选择 read-write,请把 agent 写入的一切都视为不可信数据,永远不要自动执行它们。

6、资源限制内建于 API 中
迟早,不可信代码会做一些愚蠢或对抗性的事情。
它会永远分配、永远递归、永远循环,或者发起太多宿主调用。
Monty 为内存、执行时间、递归深度和挂起次数暴露了会话限制。
from pydantic_monty import Monty, MontyRuntimeError
limits = {
'max_memory': 10_000_000,
'max_feed_duration_secs': 1.0,
'max_recursion_depth': 100,
}
with Monty() as pool:
with pool.checkout(limits=limits) as session:
try:
session.feed_run('x = [0] * 100_000_000')
except MontyRuntimeError as exc:
print(exc.display(format='type-msg'))
在生产 agent 系统中,请设置解释器限制和宿主端请求超时。
两者各自捕获对方遗漏的失败。
一旦会话触及终止性的内存或时间限制,也要将其丢弃。
资源失败之后,你不应该继续信任该解释器状态。

7、当工作进程约 2 MB 时,规模看起来完全不同
每 agent 一个沙箱的架构带来另一个隐藏成本:闲置内存。
Pydantic 报告 Monty 工作进程的基线低至约 2 MB,这是在配置限制和可选类型检查之前。
在这个大小下,在一台机器上并发运行数百或数千个沙箱变得可行,而这在容器密集的设计中很难实现。
这就是为什么 1 毫秒的启动数字和内存数字是相辅相成的:
- 快速启动意味着你不需要囤积热沙箱。
- 小工作进程在你确实保留会话时能保持高密度。
- 快照让长期工作流可以完全归还其工作进程。
这三个特性相互强化。

8、该信任安全模型多深
Monty 是一个语言级沙箱。
它的隔离来自解释器能做什么和不能做什么,周围没有虚拟机边界。
在规划部署时要记住这一点。
解释器被刻意构建为没有通用宿主访问权限。
Python 和 JavaScript 绑定在工作子进程中运行代码,所以工作进程崩溃不会拖垮你的应用程序进程。
Pydantic 还针对该沙箱运行了多轮公开赏金计划。
这段历史是有用的上下文。
- 第一轮 Hack Monty 发现了一个真实的逃逸,Pydantic 修复了它。
- 第 2 轮报告没有沙箱逃逸。
- 第 3 轮将赏金提高到 20,000 美元,并测试了子进程/WebSocket 架构和挂载隔离。
这是一份扎实的对抗性测试记录。
尽管如此,没有人能保证每个漏洞都已被发现。
对于更高保障的部署,Pydantic 的文档描述了 Full Monty,它在同一沙箱模型周围增加了操作系统级隔离。
这是正确的思考方式。语言级隔离缩小了攻击面,进程和容器边界增加了纵深防御。
9、Monty 不适合的场景
最大的限制是刻意的,因为 Monty 并不试图成为完整的 CPython。
它实现了 Python 的一个有用子集和有限的标准库。在沙箱内安装任意第三方包不是它当前的目标。
如果你的 agent 真的需要 pip install 随机包、编译原生扩展、运行 shell 命令,或者表现得和一个不受限制的 Linux 开发环境完全一样,请使用更重的全系统沙箱。
大量的 agent 任务需要的远比这少。
模型通常需要循环、条件、函数、JSON、字符串处理、算术、数据整形、少量状态,以及宿主提供的一小组特权操作。
对于这类工作负载,给每个 agent 一台小型 Linux 机器可能解决了一个比你实际问题更大的问题。
10、你可以围绕它构建的架构
对于一个 agentic 产品,你会把模型和特权能力保存在宿主应用程序中,并把 Monty 视为一个临时计算层。
流程很简单:
- 模型编写用于计算和编排的 Python。
- Monty 以严格的限制运行它。
- 未定义的特权操作挂起到允许列表中的宿主函数。
- 宿主验证参数,执行真实的副作用,并返回结构化数据。
- 长时间等待在挂起边界处做检查点,稍后恢复。
- 文件访问通过窄的显式挂载进行,最好是只读或覆盖模式。
- 可观察性记录代码、挂起、持续时间、限制和宿主调用。
这避免了两个糟糕的极端:应用程序内部不受限制的 exec(),以及为每一小段模型编写的逻辑都使用一个重量级远程容器。

更大的思想是:只有当沙箱便宜到可以在任何地方使用时,代码执行才能成为正常的 agent 原语。
Monty v1 让这变得可信得多。
原文链接:Agent Sandbox That Starts in Under 1 Millisecond
汇智网翻译整理,转载请标明出处