在执行层为 AI 智能体加一道闸:ctrlrun 开源工具
ctrlrun 是一个 Apache-2.0 的 Python 库,在智能体调用工具前校验参数与权限,防止重复、越权与错…
随着 AI 智能体(agent)逐步接入支付、退款、部署等高风险操作,「模型决定调用某个工具」与「真的发出请求」之间的鸿沟被越来越多开发者意识到。开源工具 ctrlrun 正是填补这一缺口:它是一个 Python 库,部署在智能体进程内部,在每一次外部调用真正发出前,按规则判定「放行」「请求人工审批」或「直接拒绝」,并为每次动作生成可回溯的收据(receipt)。
核心思路:模型可以猜,ctrlrun 不猜
ctrlrun 的设计者认为,模型本身对「应不应该退款 500 欧元」可以非常自信地犯错——例如把金额多写一个零。要拦截这种错误,不能依赖模型的判断,只能在调用真正离开本进程前检查参数本身。库的定位因此十分明确:「可被调用」不等于「被允许这样调用」。
库内置的策略由四种规则构成:
- Exact means exact:参数变更需要重新审批,避免审批后被替换。
- Once stays once:通过 effect key 与共享存储,保证同一后果只生效一次。
- Unknown means wait:结果未知时显式返回 AMBIGUOUS,而不是 FAILED,杜绝盲目重试。
- Every answer is kept:请求、决策、结果、拒绝全部留痕。
演示覆盖的五类失败
ctrlrun 自带一个本地 demo,策略示例为「500 欧元以内自主退款、10000 欧元以内需人工审批、以上全部拒绝」。在不到一秒、纯本地、无外部服务的情况下,它重现并拦截五类典型事故:
- 重复执行:远程已提交、响应丢失,智能体重试同一退款 → 因 effect key 已记录为 AMBIGUOUS 而被拒绝。
- 审批替换:人工批准了 2000 欧元,智能体实际请求 5000 欧元 → 因 action hash 不一致被拒。
- 并发抢占:两个智能体同时尝试处理同一笔退款 → 后者因 effect 已 ACQUIRED 被拒。
- 审批重放:单次使用的审批 ID 第二次提交 → 被识别并拦截。
- 权限越级:客服智能体被授予 2000 欧元额度,却尝试 50000 欧元 → 因超出 delegated grant 被拒;连上游财务智能体想越权下放额度也会被拒绝。
demo 会把所有决策写入 .ctrlrun/demo/receipts.jsonl 与 events.jsonl,便于审计。
三步上手
使用流程被作者刻意压缩到最小:
- 安装:
pip install ctrlrun。 - 写策略文件
ctrlrun.yaml,金额采用最小货币单位(整数),例如 50000 即 500 欧元;金额下限与上限都要显式声明,避免负数绕过;任何未列出的 action 默认拒绝,无默认放行。 - 在智能体调用工具前插入 ctrlrun 检查;敏感动作挂审批流程。
部署方式上,单机可直接用文件存储,多主机可切到 Postgres,库本身不依赖任何外部服务。
边界与不做的事
作者明确划出了 ctrlrun 不覆盖的范围,以免造成误解:
- 不检测提示词注入(prompt injection),它控制的是「后果」而非「原因」。
- 对不受控的远端无法承诺 exactly-once,只能拒绝「明知会执行两次」的操作,且不做回滚。
- 收据采用链式结构可发现篡改,但对尾部截断与伪造追加无能为力——这一项由配套的
ctrlrun anchor负责。 - 收据未签名,「能发现被改」不等于「能证明作者身份」。
- 项目 README 中的徽章仅表示「在所跑配置下声明的保证通过」,并不等同于安全、合规或通过审计。
如果智能体只读不写,作者建议直接跳过 ctrlrun;一旦它具备「发送、支付、退款、删除、部署、授权、撤销、审批、提交、购买、取消」等任一能力,就值得把它接进调用链路。整体来看,ctrlrun 是一份面向 AI 智能体执行安全的小而清晰的开源实现,适合作为生产级 agent 系统中的「最后一道闸」组件进行评估。
