给 Agent 一把能改文件的权限,真正的风险往往不在它写错了什么,而在于你还没看清楚它写了什么。多数做法是先让它跑在容器里,再事后翻 git diff——可是 git 只能告诉你「变了什么」,不能在你点头之前把改动按住。PolicyVisor(pVisor)换了个位置:它把 Agent CLI 或脚本当成一个 Job 来跑,工作区的写入先进一层写时复制的暂存区,你审完再决定哪些路径落盘、哪些直接丢掉。本教程六步走完这条链路:装好并确认平台前提、用安全预设跑一个 Job、看清楚它改了什么、选择性落盘、换到更硬的执行器,最后把边界与红线一次说清。
Step 1:装好,并先确认你的平台走哪条路
安装本身有三种走法:Python wheel 最直接,nightly 构建不需要 Rust 工具链,Homebrew 走项目自己的 tap。稳定版与 nightly 的 GitHub 发布里都带原生压缩包与 SHA-256 校验值,装完先确认版本号。
pip install pvisor-sandbox
pvisor --version
# 不想装 Rust 工具链:滚动 nightly 构建
curl -fsSL https://raw.githubusercontent.com/DeepLink-org/pvisor/main/scripts/install-nightly.sh | bash
# Homebrew(项目自带 tap)
brew tap DeepLink-org/formula https://github.com/DeepLink-org/Formula.git
brew install DeepLink-org/formula/pvisor
装完先别急着跑 Agent,把三条平台前提确认掉。其一,wheel 只发 Linux x86_64 与 Apple Silicon macOS 两种,其他平台要从源码构建。其二,macOS 上带暂存的宿主执行需要 macFUSE 并启用 FSKit 扩展,README 给的门槛是 5.4.0 及以上,而站点文档里另有 6.3.0 的口径——以你所用版本的安装指南为准,装错了的表现通常是挂载直接失败。其三,虚拟机执行在 Linux 上走 KVM,在 Apple Silicon 上走 Hypervisor.framework,且 macOS 上还需要准备一个 Linux 镜像或 rootfs。
还有一处文档层面的坑值得先说:README 与站点文档混用了 -- 与 ++ 两种参数前缀,遇到命令不认的时候,以 pvisor help 后面接具体子命令的实际输出为准,不要照抄某一篇文档。另外仓库提供了中文文档,入口在官方文档站的 /zh/ 路径下。
先读一遍安装指南里的平台清单再动手:这个项目的隔离强度高度依赖内核特性,KVM、Hypervisor.framework、FUSE 各有一条独立前提,任何一条没满足,后面所有关于边界的说法都不成立。
Step 2:一个安全预设,把改动留在暂存区
跑你熟悉的命令,只是在前面加一段:spawn 后面跟 --safe,再用两个短横线隔开真正要执行的命令。也可以显式指定暂存区位置,之后按路径引用它。想在配置文件里固化,TOML 的 run 段里写 profile 等于 safe,效果与命令行一致。
# 在项目目录下跑你熟悉的 Agent CLI
pvisor spawn --safe -- claude
# 显式指定暂存区位置,之后按路径引用它
pvisor spawn --stage /abs/path/draft -- claude
# 配置文件里固化:[run] profile = "safe" 等价于 --safe
# 注意:显式写 --filesystem host 与安全预设冲突,会在执行前就失败
--safe 请求的是安全预设,效果是工作区写入不直接落到目标目录,而是留在 Job 的存储里等你审。准入阶段会先解析执行器与所需能力,如果选中的执行器满足不了这个预设,它会拒绝派发,而不是悄悄降级——这一点比「尽量隔离」的做法可靠。last 指代默认存储里最近的那个 Job,同时管多个 Job 时请用打印出来的 Job ID。
有一条必须记牢:不带暂存的普通宿主 Job 是直接写工作区的,没有任何暂存可言。也就是说,安全预设不是默认行为,漏写就是直写。另外暂存只覆盖配置好的文件系统视图,HOME、rootfs 与显式挂载各有各自的生命周期,不要以为整台机器都被罩住了。
Step 3:审:看差异,并进暂存区取证
审阅这一步给的不是一个差异清单而已,而是一份 Run 记录或 Run Bundle:结果、文件系统变更、边界观测与产物引用都在里面。真正要读的是其中的证据与警告——它们说明这一次到底装上了哪些控制。项目自己的措辞很准确:请求一项策略,并不等于它被执行了。所以验收时请把 Bundle 里的证据当成结论,把你命令行里写过的参数当成意图。
pvisor review last # 审阅默认存储里最近的 Job
pvisor review 暂存区路径 --diff # 带差异
# 在暂存区里直接跑命令取证,不影响原工作区
pvisor inspect 暂存区路径 -- /bin/cat report.txt
# 机器可读的状态
pvisor status last --json
还有一处容易踩空:如果一个 Attempt 的完成记录没有提交,带 --review 的状态查询与 review 会显示 UNKNOWN,并给出保留下来的暂存区与恢复入口,而不是编一份安全结论给你。这个只读诊断会正常退出,但它并不授权你执行落盘或丢弃。
不要拿 review 的输出当成「已经隔离」的证明:它给的是这一次观测到的边界,而缺少观测不等于某件事没发生——项目自己也明说代理记录只覆盖经过代理的流量。
Step 4:落盘:必须显式选择,空选择会被拒
审完之后两条路:接受你挑中的那部分,或者把剩下的整批丢掉。落盘时必须带上整批接受的开关,或者显式的路径、包含、排除选择;空选择会在动目标之前就被拒绝,整批接受也不能和过滤条件同时用。落盘时会拿记录下来的原像比对冲突,并在一份恢复账本里记录进度——即使中途断了,也知道哪些已经落地。
# 只接受 src 目录
pvisor apply last --path src
# 也可以按 --include / --exclude 组合,或者整批接受
pvisor apply last --all
# 剩下的不要了
pvisor drop last
这一节的红线比命令更重要。落盘不提供跨文件的多文件事务,也不针对其他写入者做保护:接受一批改动的时候,请把并发的编辑器与其他落盘进程停掉。冲突检测与恢复记录不会让任意并发写入变得安全。文档里还有一句值得原样转述给团队:崩溃恢复是不完整的,一次完成的提交会分别落多份持久记录,宿主进程在它们之间崩溃就需要人工对账。
远程调用撤不回来:发出去的 API 请求、写进数据库的记录、已经发送的消息,都在落盘与丢弃的范围之外,不会因为丢掉暂存区而回滚;已经落盘的文件在丢弃之后也留在原地。这一层管的是文件系统,不是副作用。
Step 5:换执行器:宿主、容器与虚拟机
三种执行器的边界差别很大,选错等于白做。宿主最轻,靠平台控制加可选的文件系统暂存,网络代理除非配了强制边界否则是协作式的。容器把执行委派给 OCI 运行时,但 README 明确写了它不满足安全预设,并且委派结果的来源可信度是当前实现的已知限制。虚拟机跑一个独立的 Linux guest,隔离最强,原生 VMM 目标只有 Linux x86_64 的 KVM 与 macOS ARM64 的 Hypervisor.framework。
# 宿主执行(默认):平台控制 + 可选文件系统暂存
pvisor spawn --safe -- claude
# 容器:经 OCI 运行时委派,不满足 --safe 预设
pvisor spawn --executor container -- claude
# 虚拟机:独立 Linux guest,virtio-fs + 用户态网络数据面
pvisor spawn --executor vm --rootfs image=ubuntu:24.04 -- /bin/echo hello
更硬的隔离之外还有一批配套入口:检查点与分叉适合「同一个任务跑两种做法再挑一个」,轨迹回放与交互式审阅前端分别由相邻的可执行文件提供,守护进程则负责缓存与内存池这类单节点服务。要看清的是,逻辑检查点是文件系统层面的:它不捕获进程内存,回放也不会把外部服务回滚。虚拟机的实时控制另有一组命令级开关,暂停、卸载与恢复分别对应不同的 CPU 与设备静默边界,日常用不到,排障时很有用。
配套入口(各自独立契约)
pvisor checkpoint create / list / show 逻辑文件系统检查点与分叉
pvisor fork 从某个检查点分出一条独立尝试
pvisor-tui / --ask 交互式审阅前端
pvisor-replay 轨迹回放
pvisor-daemon serve --cache 缓存与内存池等单节点服务
containerd-shim-pvisor-v2 Linux containerd 集成
Step 6:边界、红线与成熟度
把跨执行器的通用限制整理成一张表,贴进团队 wiki 比事后解释省力。这几条都是 README 里明写的,不是推测。
跨执行器的通用限制
1) 远程 API 调用、数据库写入、已发送消息在落盘与丢弃之外,保持生效
2) 落盘不提供针对其他写入者的多文件事务;接受批次时停止并发写入
3) 代理记录只覆盖经过代理的流量;缺少观测不证明某件事没发生
4) 崩溃恢复不完整:一次完成会分别落多份持久记录,中途宿主崩溃需对账
5) 逻辑检查点不捕获进程内存;回放不回滚外部服务
6) 信任宿主内核、其监管进程与宿主账号的管理代码与配置
最后一段预期管理必须写在方案里。项目自称开发者预览版:架构评审与针对性测试覆盖的是单个行为,它们并不能确立稳定的崩溃恢复、敌对租户之间的隔离,或者完整的 OCI 与 SDK 兼容性。它也不声称在同一账号下互为敌对的租户之间有加固边界。想把它用在多人共享的机器上,请先想清楚信任模型——它信任宿主内核与宿主账号的管理代码,这不是一句客套。
别把它当成一个安全沙箱的成品:它是一套把「先审后落」变成默认动作的执行框架,隔离强度取决于你选的执行器与平台前提。涉及不可信代码或敌对多租户,请另配一层真正的隔离,并把上面的限制逐条转述给团队成员。
常见问题速查
| 现象 | 原因与处理 |
|---|---|
| 命令参数不认 | README 与站点混用 -- 与 ++ 前缀;以 pvisor help 子命令的输出为准 |
| macOS 上暂存挂载失败 | 需要 macFUSE 与 FSKit 扩展;版本门槛以安装指南为准 |
| 虚拟机跑不起来 | Linux 需 KVM,Apple Silicon 需 Hypervisor.framework,macOS 还需 Linux 镜像或 rootfs |
| 漏写安全预设 | 不带暂存的宿主 Job 直接写工作区;--safe 不是默认行为 |
| 准入阶段被拒 | 选中的执行器满足不了安全预设,换宿主或虚拟机 |
| 审阅显示 UNKNOWN | 完成记录未提交;这是只读诊断,不授权落盘或丢弃 |
| 落盘报空选择 | 必须给 --all 或显式 --path / --include / --exclude;--all 不能与过滤同用 |
| 丢了暂存区但改动还在 | 已落盘的文件在丢弃后保留;远程调用与数据库写入本就不回滚 |
| 容器执行想当安全沙箱 | OCI 执行器明确不满足 --safe 预设,且来源可信度是已知限制 |
| 崩溃后状态对不上 | 崩溃恢复不完整,完成记录分多次落盘,需人工对账 |
| 想找中文文档 | 官方文档站的 /zh/ 路径下提供中文版 |
| 想在 CI 里批量用 | 先备好镜像缓存与平台前提;守护进程只实现部分 OpenSandbox 与 E2B 配置 |
| 多人共享一台机器 | 不承诺敌对租户之间的隔离,请另配隔离层并明确信任模型 |