
这是什么
一套给编程 agent 用的治理底座。对外是一个 MCP 服务器、47 个工具;里面是一套调度器为任务挑选策略、准入闸门决定什么能上线、默认七个角色的投票面板用 consensus_vote 表决真正的分叉,而每一次工具调用、每一次投票、每一次路由选择都会写进一条哈希链式、只追加的审计日志,随时可以用 verify_audit_chain 走一遍。结果会回灌进路由:一份 outcome store 喂给打分算法,另有一个有上限、会衰减、只能降级的调节回路。Claude Code、Codex、Gemini、OpenCode 是数据面——真正干活的 agent;Nexus Agents 是负责审查、审计和调度的那一层。
谁做的一个 2015 年注册的 GitHub 账号,84 个公开仓库,35 个关注者。5,184 次提交里 4,147 次是他写的,另外 1,037 次来自两个机器人——其中一个还会按周自动开 pull request。CODEOWNERS 里每一条路径都写着他,包括那段要求所有者批准的治理区,所以这道闸门分开的是「agent 写的」与「所有者批准的」,而不是「一个人的」与「另一个人的」。pull request 由他开,而对自己那次改动、指名到具体提交哈希的对抗性审查,也由他贴在下面。
它是怎么搭起来的
组成 · 6前面一个 MCP 服务器,后面四个编程 CLI,中间是一层控制面,而它的每个部件都向同一条审计链汇报。解释其余设计的那个决定是平面分离:写代码的 agent 是数据面、可以替换;「准入」——什么允许上线——是控制面,那才是产品本身。任务从单一入口进来,路由器挑选策略,闸门决定结果能不能往下走,遇到真正的分叉就交给面板表决,结果被写下来;写下来的记录再回灌进下一个任务的路由。在这套回路之外还有一圈更窄的回路,管的是底座自己:实现治理的那些路径被 CODEOWNERS 里两条指令圈出来,审查闸门和批准闸门读的是同一次解析,于是「改审查机制」这件事没法由审查机制自己落地。项目也把上限写明白了——「autonomic」指的是在边界内自我管理,每个回路的权限都由一条权限阶梯封顶,升到更高的权限要靠证据和人的批准挣来,而不是默认打开。
- packages/nexus-agents/src/
- 3,074 个文件。
mcp/是对外接口(47 个工具、资源、中间件);consensus/是投票引擎、投票者角色与判决计算;pipeline/是任务契约、运行器、事件总线与策略引擎;audit/是那条哈希链;governance/是适配度审计、漂移检测与注册表覆盖闸门;learning/是 outcome store 与策略蒸馏;cli-adapters/把外部 CLI 包成子进程;adapters/直接对接模型 API;security/是恶意输入防火墙与信任等级;replay/确定性地重放执行轨迹;observability/承载群体指标;dogfooding/是那套自我指涉的审查工具。 - governance/
- 放的是耐久的记录而不是代码:
vote-records.jsonl(41 条批准记录,795 KB)、claim 登记册、验证投票签名用的公钥,以及required-jobs.json——那份把「治理闸门里必须接上哪些 CI 任务」钉死的清单,而检查它的脚本本身就在这道闸门里面。 - .rules/ 与 scripts/
- 二十个规则文件,由 agent 加载、由 CI 执行;加上 216 个脚本——两道治理闸门、账本证据检查、只追加检查、签名策略、必须任务检查,以及
inject-governance.ts:它把AGENTS.md生成成CLAUDE.md里的一段,因为决定了 agent 读到什么,所以它本身也是一条治理路径。 - AGENTS.md、CLAUDE.md 与联邦式指令
AGENTS.md84,682 字符,是 agent 指令的唯一正典来源;CLAUDE.md89,570 字符,承载一段从前者注入、在 CI 里被把关的生成块。其余各家 harness——.cursor/rules/、.windsurf/rules/、.continue/rules/、.clinerules/、.aider.conf.yml——都只是一行跳转;把内容复制进某家专属文件的 PR 会在合并前被改回跳转。文件自己也写明了限度:生成块以外的内容是「在构造上就不受把关」的,「TypeScript 的版本钉就是因为这个漂掉的」。- skills/ 与 agents/
- 约三十五个技能,每个就是一份指令文件——
self-critique、reviewing-code、research-and-vote、test-driven-development、pre-push-parity、dogfooding-issues、system-review、security-advisory-response,以及把活交给 Codex 和 Gemini 的 delegator——另有十三个 agent 定义和.claude/agents/下的四个。技能是把.rules/里写下的纪律变成 agent 真会去跑的一步的那种东西。 - .github/workflows/
- 二十七个工作流文件,其中包括治理审查,以及那个定时跑、并自动开一个 issue 的「系统审查」:它报告注册表覆盖率、以天为单位的文档陈旧度、未关闭与陈旧 issue 数、漏洞数、lint 与类型检查状态——正是那些否则要有人亲自去凑的数字。
取舍,以及它替代了什么
治理路径集合从 CODEOWNERS 里解析一次 替代 每道闸门各自维护一份清单
指令文件把机制和意图一起写了:集合取自两条指令之间那一段,两道治理闸门都从这一次解析派生,所以新增一条受保护路径是往一个受审查的文件里加一行,而不是同时改两个检查脚本。那些条目上方的注释给了「闸门本身也要放进这一段」的理由——一个 agent 可以悄悄削弱、而不会触发它的闸门,就不是闸门。
批准失败即拒,并绑定到具体提交 替代 一个标签、一次批准,或一次看起来没问题的合并
治理路径上的改动要通过,既要有所有者的批准或所有者亲手贴的标签,又要在提交进仓库的账本里有一条绑定到该 pull request 与该 head SHA、在超多数或全体一致下、覆盖整个面板、按绝对法定人数错误策略通过的记录。绑定 SHA 才让这条记录说的是真正合并进去的那份代码,而不是它被提出时的那个分支。
审计链是可选项,而威胁模型开篇就说明这一点 替代 默认打开,好让保证开箱即成立
文档的第 0 节之所以存在,是因为早先的版本把这条链的保证完整描述了一遍,却没说过到底有没有在写链;这个开关在所有随仓库发布的配置里都是未设置的,服务端改为在启动时警告。它同时把「能力」和「默认状态」在指令文件里分开——那里的措辞把这份日志描述成了承重设施。
聚合出来的结论必须声明「空集」是什么意思 替代 让语言默认值来回答
[].every(p)为真、![].some(p)为真、Math.min(...[])是无穷大,于是一个在空集合上跑的检查会报告健康。辅助函数要求传入whenEmpty,而规则明说:抓住这一类问题的是那个空输入测试,不是类型系统,也不是 lint 规则。拜占庭检测留在实时投票路径之外 替代 把加权检测器接进 consensus_vote
架构文档两个方向都写了:
WeightedVoting模式检测器与它的byzantine.*事件是实现好的、是导出的,而事件表旁边那句注明它们不在实时的consensus_vote路径上。同一份文档把六种策略名字如实记成五种不同策略,因为higher_order是一个别名。
依据ARCHITECTURE.md(18,204 字符)及其模块表、AGENTS.md(84,682)、CLAUDE.md(89,570)、README.md(39,076)、审计哈希链威胁模型(43,380)、docs/architecture/CONSENSUS_PROTOCOLS.md、CODEOWNERS、.rules/development-disciplines.md、governance/vote-records.jsonl,以及目录树。
制作过程
6 个阶段- 01
一个人、两个机器人,和 6,829 个带编号的东西
仓库建于 2026-01-03,到 2026-09-28 有 5,184 次提交——4,147 次来自一个人类账号,932 次来自一个工作流机器人,105 次来自 Dependabot,每一次提交都挂在账号上。同一时期还有 3,313 个 pull request、3,516 个 issue(其中 157 个未关闭)、903 个 release、913 个 tag、3,897 个文件、500 MB。提交曲线没有闲置的月份:一月 630 次、二月 777 次、九月 988 次。版本号不是一条线而是三条互不相符的线——最老的 tag 是
v2.2.0(2026-01-16),最新的是nexus-agents@8.113.0,而ARCHITECTURE.md里写着「Version: 2.137.0,Last Updated 2026-06-21」。最近二十个 release 全部发生在 2026-09-24 这一天,从 05:47 到 15:11;仓库自己的定时审查机器人会用「天」为单位报告文档陈旧程度,那天它给ARCHITECTURE.md记的是 23。而与这一切相对的是:19 个星、2 个 fork、1 个 watcher。 - 02
它把自己写的东西拿去对抗性审查,并指明是哪个提交
这里没有第二个人,所以第二个读者是被构造出来的。打开任何一个 pull request,正文下面的第一条评论就是作者对自己那份待合并提交的对抗性审查——「Independent adversarial review of exact head
ce559a93found no substantive blocker」——后面跟着他查了什么、没能查什么,以及这次改动没有关掉的那个缺口,逐条说明。修 npm 安装冒烟测试的那条(#6826)读起来是一串「假通过」:被截断的tools/list响应靠grep数出 39 个名字、越过了 25 的下限;响应在 65,536 字节处被切断,jq报的是字符串未闭合。把 stdin 保持打开后,拿回来的是完整的 47 个工具。独立审查随后又找出了三种「照样退出 0」的形态——空的initialize结果、同时带 result 和 error 的tools/list、以及一个健康但带nextCursor的分页响应——每一种都先变成会失败的用例,再变成修复。同一条评论也写明了审查者自己的边界:「Focused tests 8/8……等刷新后的同提交 CI 与复核;两者齐全前不要合并。」 - 03
治理者不能削弱自己的治理
仓库里负责执行规则的那部分代码,本身也被管着,而且靠的不是一段声明。
CODEOWNERS里有一段由# @governor-section-start和# @governor-section-end圈起来的路径清单,列出永远不允许自动合并的东西:审计哈希链、治理源码、把一份指令文件注入另一份的脚本、claim 登记册、验签用的公钥,以及——正是为这个理由加进去的——「治理者自己的闸门」,因为注释写得很直白:「一个 agent 可以悄悄削弱、而不会触发它的闸门,就不是闸门。」 两道闸门都从这一次解析里取路径集合。批准是失败即拒的,而且是机械的:既要有所有者的批准或所有者亲手贴的标签,又要在提交进仓库的账本governance/vote-records.jsonl里有一条绑定到该 pull request 与该 head SHA 的记录,且该记录必须是超多数或全体一致、覆盖整个面板、在绝对法定人数的错误策略下通过。仓库同时记下了这里曾经出错的地方:在 #5997 之前,.rules/一直落在那两条指令之外,「而这段文字此前声称它在里面」。指令文件把设计意图浓缩成一句:一个能悄悄降低自己审查门槛的 agent,等于没有审查门槛。 - 04
审计链默认是关的,而威胁模型自己写了这件事
哈希链是这个项目里所有「不可篡改的审计」说法的承重结构,所以有一份文档的职责就是对抗性地分析它。它的头条结论是:这条链能发现就地修改,但挡不住整份日志被重写——没有外部锚点、没有签名、没有单调计数器——文档让读者去看残余风险那几行,而不是看设计图。第 0 节是多数项目会省掉的那部分:在默认安装里这条链根本不存在。 整个子系统由一个没有出现在任何随仓库发布的配置里的开关
security.audit.enabled控制,所以除非运维显式打开,一个事件都不会写;文档里列出的每一条威胁既不是「已缓解」也不是「未缓解」,而是不适用。在这种安装上跑verify_audit_chain会返回ok: true加notVerified: 'empty'——文档说这很诚实,但只看.ok的读者什么也学不到。服务端启动时会警告。文档还记下:它此前把这条链的保证完整描述了一遍,包括enableHashChain默认为true和那些编号威胁,却从未说过到底有没有在写链;而指令文件把这份记录称为承重设施,那是在描述一种能力,不是在描述默认状态。 - 05
那份提交进仓库的账本里到底有什么
governance/vote-records.jsonl是签进仓库的:795,042 字节、41 条记录,平均一条约 19 KB,因为每条都带着每个投票者的理由与置信度,而不只是票数。41 条全部写于 2026-09-14 到 2026-09-24 之间,而且形状完全一致——策略supermajority、结论approved、七票赞成、零票反对、零票弃权。第一条记录批准的,正是对这套批准机制本身的修复(#6241):账本闸门此前会检查决定、错误策略、只追加状态和面板覆盖,却从未读过record.strategy,于是一条在简单多数下产生的记录也会算数。批准那次修复的面板就是标准的七个角色——architect、security、devex、ai_ml、pm、catfish、scope_steward——其中catfish的存在意义就是唱反调;而在那条签进仓库的记录里,它和其余六位一样投了赞成。一个默认面板里专门设了职业反调角色的投票系统,在它自己留存的、真正要紧的那些表决记录里,没有反调可看。 - 06
被检查的规则,和被点名的空集
.rules/里有二十个文件、约 105 KB,README 说得很清楚:它们「由 CI 闸门和 PR 审查投票者执行,不只是建议」。漂移检测会在写下来的规则与登记在册的行为(模型注册表、MCP 工具表、专家类型、技能集合)分家时让构建失败。这些纪律具体到可以被反驳:Red/Green TDD 要求分支上的第一个提交是一个「因为正确的原因而失败」的测试;TypeScript 的零any由 ESLint 而不是由评审来守;还有一条规则针对的是一整类 bug 而不是风格——给空集起名字。[].every(p)、![].some(p)、Math.min(...[])、errors.length === 0都会把「什么都没有」渲染成「一切正常」,所以聚合必须走空集参数必填的辅助函数,而规则里补了一句:真正抓住这一类的是那个空输入测试,不是类型系统,也不是那条 lint 规则。指令文件里有两句话就是整套主张:「一个在构造上就不可能失败的检查,不是检查」,以及「审查必须消费产物本身,而不是对产物的描述」——后面跟着一个限定:有边界的阅读是正当的,只要记录里写明读的是哪一部分。这个项目把自己那场 PR 审查实验的结果称为「小样本的方向性数字,不是测量出来的比率」,并注明人工复核两个被检查的假阳性时,发现其中一个是数据集标错的真问题。