关于 GVNR by Instruxi
GVNR by Instruxi 是 Claude Code 插件,可在 Agent 操作执行前按预算、循环状态和能力规则作出允许、改写、拒绝或询问决定,并记录可验证的决策链。连接 Enforcer 后还可应用组织策略。
主要功能
- 01执行前检查 Agent 操作
- 02按预算和速率限制决策
- 03拦截或改写危险命令
- 04应用 Enforcer 组织策略
- 05生成哈希链审计记录
适用场景
- 01限制编码 Agent 的支出
- 02在危险命令执行前要求确认
- 03审计 Agent 操作决策
MCP 相关
GVNR 插件本身并不附带 MCP 服务;Enforcer 插件提供 Enforcer MCP 服务,与 Governor 共用登录凭据。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y enforcer-governorGOVERNOR_PORTPort where GVNR is running (npx enforcer-governor start). Defaults to 4000.GVNR_AGENTA name for this client's agent, shown as mcp:<name>. Defaults to a random id per session.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
5收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | string | 是 | 即将执行的具体操作:完整的 shell 命令;如果是文件操作,则提供动词和路径,例如 "write /app/.env"。 |
详细介绍
Enforcer Governor
一款 Claude Code 插件,可在 Agent 执行动作之前判断是否允许执行,并留下可供验证的记录。
Agent 会陷入循环、重复工作,还会直接运行来自互联网的脚本。其他工具都是事后才报告损害。这款工具会先回答一个问题:
此刻允许这个 Agent 执行此操作吗?
- allow — 未超预算且符合当前任务,继续执行
- rewrite — 以符合策略的形式执行
- deny — 超出预算、陷入循环,或完全不允许
- ask — 暂停并征求你的确认
每个决定都会追加到一份哈希链式记录中。只要编辑或删除其中一行,链条就会明显断裂。在登录 Enforcer 工作区之前,所有内容都会留在你的设备上——请参阅哪些内容会离开你的设备。
安装
/plugin marketplace add instruxi-io/claude-plugins
/plugin install enforcer@instruxi
/plugin install enforcer-governor@instruxi
然后重启 Claude Code,并使用 /enforcer:login 登录一次。enforcer 插件负责连接到你的 Enforcer 工作区(包括其 MCP 服务和登录功能);governor 无需它也能运行,在本地做出决定并记录,但没有租户策略可供查询,也无处发送回执。
governor 提供强制执行功能:钩子、能力规则和八个斜杠命令。执行 /plugin disable enforcer-governor 即可同样彻底地移除这些功能。无需启动守护进程。governor 会将自身状态保存在 ~/.enforcer-governor/ 下,并在 ~/.enforcer/ 下保存一个小型共享文件;只有在你使用 /enforcer-governor:telemetry on 明确要求时,它才会写入你的 ~/.claude/settings.json。
有两项内容无法通过这种方式安装,因为 Claude Code 不允许插件打包其中任何一项:插件的 settings.json 只接受 agent 和 subagentStatusLine 键,其他内容都会被忽略,且不会给出提示。这两项都只需粘贴到你自己的 ~/.claude/settings.json 中;即使不添加其中任何一项,governor 也能正确执行强制策略。
状态栏是唯一始终显示的界面——你扫一眼就能看到的数字,无需打开其他内容:
{
"statusLine": {
"type": "command",
"command": "node \"$(cat \"$HOME/.enforcer/plugin-root\")/statusline/spend.mjs\""
}
}
插件的安装目录会随每个版本变化,因此状态栏不会写死该目录:governor 会在每次会话开始时,将自身运行位置记录到 ~/.enforcer/plugin-root,命令会根据该记录定位。安装后首次启动会话之前,状态栏不会显示任何内容。
权限规则为钩子提供额外保障。将此仓库的 settings.json 中的 permissions 块复制到你的配置中。插件无法自动安装这部分内容;如果不粘贴,钩子就只能独自处理——其具体影响请参阅工作原理,实际影响没有听起来那么大。
你将获得什么
状态栏中的一个数字,也是金额数据的来源。 · $3.40/$20 — 实时显示相对于限额的支出,无需打开仪表板。需要你处理问题时,它会改变一次颜色。
它还有第二个作用。Claude Code 会把自己的 total_cost_usd 传给状态栏,仅传给状态栏——钩子永远看不到这个值。该金额可能按组织签约费率计算,因此比任何第三方价格表都更准确。状态栏会记录此值,门控会在下次工具调用时读取。即使不粘贴 安装 中的配置,governor 仍会执行强制策略;只是会改为根据对话记录计量,而且每份回执都会注明采用了哪种数据。
插件随附的能力规则。 直接将 URL 内容管道传入 shell 会被直接拒绝。删除目录树、重写 git 历史、读取凭据、发布或部署:这些操作都会暂停并征求确认。这些是能力判断,不涉及支出,因此即使预算已用满也会触发;而且检查时完全不会读取任何状态,因此即使 governor 无法读取自己的文件,这些规则仍然有效。删除状态目录会关闭支出限额,但不会关闭这些规则。
这是你所在组织的策略,不只是我们的策略。 通过 /enforcer:login 登录一次后,上述规则就不再是对所有人都相同的六种模式。规则匹配时,治理器会就此询问你的 Enforcer 租户策略:一个类型为 agent_action、ID 对应规则名称(fs.delete_tree、deploy.publish、git.force_push、git.rewrite_history、secrets.access、shell.pipe_to_shell)的资源。策略采用 Rego 编写,按版本管理,上线前经过测试,并通过激活上一版本回滚。因此,“这里的 Agent 绝不删除整个目录树”或“发布需要人工确认”只需更改一次,全团队即可生效。
策略的回答会与本地规则组合,且处理方向很重要:
| 本地拒绝 | 本地询问 | 本地改写 | |
|---|---|---|---|
| 策略拒绝 | 拒绝 | 按策略措辞拒绝 | 拒绝 |
策略 ask: 原因 | 拒绝 | 按策略措辞询问 | 询问 |
| 策略允许 | 拒绝 | 无异议 | 改写 |
| 无规则 / 无法访问 | 拒绝 | 询问 | 改写 |
策略可以让任何规则更严格,也可以免除确认。它不能解除硬性拒绝(curl | sh 仍会被拒绝),也不能跳过改写。如果 Enforcer 响应缓慢、服务中断或你已退出登录,则仅由本地规则决定,因此网络中断绝不会让更多操作获准。只有匹配的命令才会被询问;普通工具调用不会等待网络响应。回答会缓存 policyTtlSec(30s),这也决定了新策略版本多久能同步到每台机器。
declared_types := ["agent_action"]
allow if { input.resource_type == "agent_action" } # declared, so silence would deny
deny contains "agents in this tenant do not delete whole directory trees" if {
input.resource_type == "agent_action"
input.resource.id == "fs.delete_tree"
}
deny contains "ask: publishing from an agent needs a person to confirm" if {
input.resource_type == "agent_action"
input.resource.id == "deploy.publish"
}
治理器和 Enforcer MCP 服务只需登录一次。 MCP 服务随同一市场中的 enforcer 插件一起提供(/plugin install enforcer@instruxi),而不包含在治理器中;在 2.6.0 之前,治理器自带一份副本。两者读取同一份凭据 ~/.enforcer/credentials.json(0600),因此 /enforcer:login 和 /enforcer-governor:login 是同一次登录:登录一次,两者都会登录;退出一次,两者都会停止发送凭据。
以美元计价的支出上限。 /enforcer-governor:limit 40 为每个 Agent 设置 $40 上限。Claude 按各模型自身的费率计价,因此无论 Agent 使用 Opus 5 还是 Haiku 4.5,$40 都是 $40。如果无法获取运行环境提供的数据,上限就会改用按成本加权的有效令牌数,因为缓存会话每轮都会重新读取完整上下文,原始令牌数会激增,但费用却很低。
让 Agent 知道,而不只是让你知道。 得知自己接近上限的 Agent 可以先完成手头工作,而不是另开战线。预算消耗约达到三分之二时,系统会在回合边界告知一次——只用一句话,此时 Agent 仍能调整计划——然后治理器会保持安静,直到情况发生变化。等到触及上限才警告就太迟了:当前回合已经开始。每句话都有成本,因为它会加入缓存前缀,并在之后的每个回合重复计费;这就是为什么只说一句,而且只说一次。
用更安全的命令替代被拒绝的命令。 有些操作有一种既保留意图又去除风险的形式。git push --force 会改为 git push --force-with-lease;只有在你上次获取后有人推送过内容时,该命令才会拒绝执行——这正是会导致工作丢失的情况。改写会被告知并记录;悄悄编辑命令的治理器会成为对话记录中一个隐形的操作者。
速率限制,而不只是总量限制。 真正造成大额开支的事故,都是速率方面的事故。每分钟美元支出、新增 Agent 数量和错误数都会分别受到监控,并且每项都只会询问,而不会阻止操作——且只询问一次,因此夜间运行会等待你的确认,而不是失败或反复提醒。
可供审计读取的记录。 /enforcer-governor:verify 会遍历文件,并指出第一处对不上的行。每张回执都记录了 Agent 代表谁执行操作、尝试了什么、由哪个模型响应,以及由哪条规则作出决定。
命令
/enforcer-governor:status | 查看每个 Agent 的支出、限额、当前消耗和记录状态 |
/enforcer-governor:verify | 检查链条,并指出第一处损坏的记录 |
/enforcer-governor:limit <dollars> | 设置每个 Agent 的限额 |
/enforcer-governor:resume [agent] | 重新运行已停止的 Agent,并为其留出完成任务的空间 |
/enforcer-governor:config [--why] | 查看每项设置、其作用,以及你修改过的设置 |
/enforcer-governor:set <name> <value> | 更改一项设置,并进行验证 |
/enforcer-governor:login [<WORKSPACE-CODE> | api-key <key> | status | logout] | 登录 Enforcer,与 /enforcer:login 使用相同的登录方式;不带参数会打开浏览器,工作区代码可跳过输入提示 |
/enforcer-governor:telemetry [on | off | status] | 将 Claude Code 自身的 OpenTelemetry 数据(费用、令牌、工具使用情况——绝不包括提示词文本)发送到你的 Enforcer 工作区;将 OTEL_* 导出器设置写入 ~/.claude/settings.json |
暂时解除限制
因支出而停止的 Agent,可通过提高限额来解除限制——/enforcer-governor:limit 40——然后从停止的地方继续运行。因循环执行而停止的 Agent,或由你手动停止的 Agent,则需要使用 /enforcer-governor:resume,因为提高限额并不代表同意它继续重复执行相同的操作。
若要在不卸载的情况下关闭 governor,请将以下任意内容写入 ~/.enforcer-governor/config.json:
{"budgetOn": false, "loopOn": false, "rulesOn": false}
budgetOn 控制支出和速率检查,loopOn 控制循环检查,rulesOn 控制能力规则。这三项相互独立:关闭支出跟踪后,curl | sh 和 rm -rf 仍会受到保护。三项全部关闭后,功能将完全停用——除非你的组织发布了下文所述的底线设置。不要删除 state.json 来解除卡住的状态:它保存着收据链的链头,因此下一张收据会在没有前一张收据的情况下进行哈希计算,verify 也会正确地报告记录已损坏。
组织设置
Enforcer 租户可以为每个登录的安装实例发布一组此类设置(GET /api/v1/governance/settings,由租户管理员写入)。这些设置是底线,而非覆盖值:对于每项设置,governor 都会采用两者中更严格的那个,因此受管理的 $150 限额会优先于你的 $400,而你的 $40 则会优先于两者;组织启用的检查也无法在本地关闭。/enforcer-governor:config 会用 ! 标记这些设置,并在旁边显示你自己的值。
无需等待网络响应来决定:设置会在会话开始时获取,此后最多每小时获取一次,并进行缓存。已退出登录或从未连接过控制平面的机器,只使用自己的配置运行。身份设置(centralUrl、ingestUrl、operator)不可由组织管理——能够重新指定安装实例的目标,就能重定向其收据。
会离开你设备的内容
在你登录之前,不会有任何内容离开。治理器会在本地做出决定并记录;从未登录过的设备(/enforcer:login)不会发起任何网络请求。
登录 Enforcer 工作区后,以下内容可能会离开设备,且每项都可通过开关控制:
| 内容 | 发送时机 | 开关 |
|---|---|---|
决策回执 — 裁定结果、触发的规则、工具名称、模型、令牌数量、根据工作目录推导出的项目名称、你设置的 operator,以及做出决定的框架(claude-code)及其适配器版本。绝不包含命令文本、文件内容或提示词。 | 每个会话结束后在后台发送到工作区的治理 API | shipOn(默认开启) |
会话的项目 — 会话开始时发送回执使用的会话 ID(claude: + 8 个字符)和根据工作目录推导出的项目名称,以便将会话归档到对应项目下,即使该会话从未做出受治理的决定。不会发送其他内容。 | 每个会话开始时发送一次简短请求,绝不重试 | shipOn(默认开启) |
| 你所在组织的策略答复 — 当本地规则与某项操作匹配时,治理器会询问你的工作区应允许、询问还是拒绝。请求中会指出规则,而不是命令。 | 仅在规则匹配时 | policyOn(默认开启) |
| Claude Code 自身的遥测数据 — 来自 Claude Code 内置 OpenTelemetry 导出器的费用、令牌和工具使用指标。不会导出提示词文本。 | 仅在你启用后 | /enforcer-governor:telemetry on(默认关闭) |
所有内容只会发送到你登录的工作区,不会发送到其他地方。/enforcer:login logout(或 /enforcer-governor:login logout,是同一个命令)会停止此设备上的所有发送;已发送的内容仍会保留在工作区记录中,这正是记录的意义所在。
治理器写入的文件:~/.enforcer-governor/(配置、状态、回执链,以及会在 sweepDays 后清理的会话临时文件)、~/.enforcer/credentials.json(你的登录信息)和 ~/.enforcer/plugin-root(已安装插件的位置,因此遥测在插件更新后仍会保持登录状态)。
决策及其代码
治理器作出的每项决策,包括“无异议”,都是一条 JSON 记录:
{"decision":"deny","code":"push_not_alone","rule":"graph.push","tool":"Bash","summary":"a push, pull request or land must be the whole command, on its own","run_id":"a54645af-…"}
decision为allow、deny或ask(改写内容会作为携带更安全命令的ask提交给用户)。code是以下代码之一,在core/codes.mjs中定义一次。代码是一套公开的词汇:只会新增,不会重命名或重复使用。rule是规则的政策 ID(git.force_push),如果没有规则作出决策,则为null。- 只有当会话中有 enforcer-graph 运行(
ENFORCER_GRAPH_RUN_ID,或 enforcer-graph 的钩子保留的运行文件)时,才会出现run_id。
此记录会写入三个位置:作为 decision 字段,写在 ~/.enforcer-governor/receipts.jsonl 中哈希链回执的末尾;作为一行写入 PreToolUse 钩子的 stderr,并以 enforcer-governor:decision 为前缀;以及作为权限理由的第一行,只要钩子允许、拒绝或询问,就会写入该行。父进程(图调度器、CI 包装器)会解析这一行,而不是从下方的句子中搜索。
| 代码 | 含义 |
|---|---|
pipe_to_shell | 将通过 curl/wget 获取的脚本传入 shell |
force_push | 强制推送(--force、-f 或 +refspec) |
destructive_delete | 对整个目录树执行 rm -rf |
destructive_git | 改写历史:reset --hard 或 filter-branch |
secret_in_command | 操作会读取或写入凭据(.env、密钥、credentials.json、 |
deploy_publish | 发布或部署(npm publish、vercel --prod、kubectl apply/delete、terraform apply) |
custom_rule | 来自 config.json 且没有专属代码的规则 |
graph_push_allowed | 无头工作器将自己的 graph/ 分支作为完整命令推送 |
graph_pr_allowed | 无头工作器从自己的 graph/ 分支创建拉取请求 |
graph_land_allowed | 无头工作器使用 land-pr.sh 合并自己的 graph/ 拉取请求 |
graph_push_confirm | 有人在场的会话中推送 graph/:由其确认 |
graph_pr_confirm | 有人在场的会话中,从 graph/ 分支创建拉取请求 |
graph_land_confirm | 有人在场的会话中执行 land-pr.sh |
push_not_alone | 推送、拉取请求或合并操作与其他命令串联;必须单独运行 |
push_default_branch | 推送到默认分支(main、master、develop、trunk) |
push_needs_approval_surface | 会话中无人可供询问,且没有规则允许此推送或拉取请求 |
branch_mismatch | 推送的分支不是工作树当前检出的分支 |
outside_worktree | 工作目录不是位于 graph/ 分支上的 git 工作树 |
governor_settings_edit | 修改插件或治理器设置 |
tenant_policy | 由组织的 Enforcer 政策作出决策 |
agent_stopped | Agent 已被用户停止或因循环而停止;恢复前会保持停止状态 |
period_limit | 已达到每日、每周或每月支出限额 |
loop_detected | Agent 超过循环限额,重复执行同一操作 |
burn_rate | 支出速度超过每分钟限额 |
fanout_rate | 启动子 Agent 的速度超过扇出限额 |
retry_storm | 失败和重试的速度超过重试限额 |
client_limit | 已达到客户端的支出限额 |
spend_limit | 已达到 Agent 的支出限额 |
spend_warning | Agent 的支出已超过限额中的警告阈值 |
no_rule_matched | 没有规则提出异议,且支出在限额内 |
spend_unchecked | 没有规则提出异议;治理器无法读取其状态,因此未检查支出 |
checks_off | 没有规则提出异议;支出和循环检查均已关闭 |
无头图工作器
图工作器是一个 claude -p 会话,调度器会在 graph/<key> 分支的 git 工作树中启动它,且没有人可以回答提示词。Governor 是允许或拒绝其交付步骤的插件(core/worker.mjs);jev-hooks 和 enforcer-graph 不再决定这些步骤。当设置了 JEV_HOOKS_HEADLESS=1 或 ENFORCER_HEADLESS=1,或者 Claude Code 报告入口点为 sdk-cli(claude -p)时,会话即为无头。分支从命令运行所在目录的 .git 中读取(cwd,或 git -C <dir>)。
| 规则 | 无头 | 有人在场 |
|---|---|---|
graph.push:在该分支上执行完整命令 git push -u origin graph/<key>(也包括 HEAD:graph/<key>,以及 rebase 路径中的 --force-with-lease) | 允许 graph_push_allowed;命令串联:拒绝 push_not_alone;其他分支:拒绝 branch_mismatch;不在图工作树中:拒绝 outside_worktree;其他任何 push:拒绝 push_needs_approval_surface | 询问 graph_push_confirm |
graph.pr_create:从图分支执行 gh pr create | 允许 graph_pr_allowed;不在图分支上:拒绝 outside_worktree | 询问 graph_pr_confirm |
graph.land:land-pr.sh,包括技能中的 "$(ls -d …/land-pr.sh | tail -1)" 形式 | 允许 graph_land_allowed;不在图分支上:拒绝 outside_worktree | 询问 graph_land_confirm |
git.force_push:--force、-f、+refspec | 拒绝 force_push | 改写为 --force-with-lease(需询问)force_push |
git.push_default_branch:推送到 main、master、develop 或 trunk | 拒绝 push_default_branch | 询问 push_default_branch |
governor.settings:编辑 .claude/settings*.json、managed-settings.json、.claude/plugins/ 或 ~/.enforcer-governor/(Edit/Write,或会写入文件的 shell 命令) | 拒绝 governor_settings_edit | 询问 governor_settings_edit |
这些是唯一会允许的规则(明确授权并跳过提示词)。它们会在能力规则之前运行;租户策略仍可拒绝它们允许的操作;将 rulesOn: false 设为关闭时,也会一并关闭这些规则。
工作原理
prompt ─► UserPromptSubmit ─► a word to the agent, if the situation changed
┌─ capability rules ─── no state, FAILS CLOSED
│ └─ matched? ask your tenant policy (Enforcer)
tool call ─► PreToolUse ─┤ unreachable leaves the local rule in place
└─ spend + rate ─────── needs state, FAILS OPEN
│
▼
allow / deny / ask / rewrite ─► hash-chained receipt
result ─► PostToolUse ─► what actually happened (errors feed the retry check)
subagent─► SubagentStart ─► fan-out, counted rather than inferred
七个钩子,没有守护进程、没有端口,也没有任何监听进程。状态保存在 ~/.enforcer-governor/ 中,并由锁保护,因此并行工具调用会按判定顺序写入记录。
这种分工就是整体架构,而且两部分会故意朝相反方向失效。支出检查默认放行:如果无法读取状态,钩子就会允许操作,并在原因行中说明未执行检查,因为一个会因自身状态文件缺失而阻止实际工作的 Governor,在更重要的事情上已经失职,而不只是没能执行策略。能力检查默认拒绝,因为它可以这样做——规则是针对操作文本匹配的模式,不需要状态,因此状态目录无法读取不会影响它们。以这种方式判定的拒绝仍会写入记录,但会刻意省略哈希:没有可读取的链尾可供哈希比对,而 verify 会将未哈希的行计为无法验证,而不是链断裂。不记录会掩盖真实的拒绝,而伪造链接则会让正常文件被误报为遭到篡改。
转录内容会从上次调用停止的位置向前读取,因此检查成本不会随着会话时长增加。
运行测试
npm test
更大的图景
Governor 是将一个理念应用于一种资源。这个理念就是 Enforcer,Instruxi 的策略引擎,它会在每个受保护的系统前提出同一个问题:此身份此刻是否可以执行此操作?——并以三种方式给出答案,每个答案都有防篡改回执。在这里,身份是 AI Agent,资源是你的资金。instruxi.io
许可证
Functional Source License 1.1(FSL-1.1-ALv2)。可免费用于你自己的 Agent,包括生产环境和商业用途。不得将其作为竞争性的商业产品或服务提供。每个版本发布两年后转为 Apache 2.0。详见 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。