实习日志 / 中文 / 凭据不等于授权:给一个人人用 Agent 的五人团队做仓库治理
凭据不等于授权:给一个人人用 Agent 的五人团队做仓库治理
中文 | English
这个项目从第一周起,团队里每个人都在用 AI 编程工具写代码、开 PR、回 review。我是仓库的维护者,一开始想做的只是让五个人能安全地并行工作。做到 8 月底,我发现自己在做的事情变了:Agent 让写代码、开 PR 变得很便宜,也让越权、误解和"看起来已经完成"变得同样便宜。仓库治理慢慢从一套协作说明,变成了约束"人加 Agent"这个协作系统的控制面。
这篇按时间讲这套治理是怎么被一次次真实的失败逼出来的,最后讲它落成的那份 skill 里写了什么。它现在是一个公开的 skill。
先搭路,再让大家跑
最早建 SPEC、Sprint 计划、目录结构、CI、main 分支保护和 Kanban,目的很朴素:如果没有明确的权威文档和交付边界,每个人和他的 Agent 都会根据自己手里的局部上下文,造出一套"看起来合理"的项目。所以第一阶段的重点是"先搭好路":建立事实来源、任务状态和依赖关系,队友不必掌握全部背景,认领一张写清楚了验收条件的卡片就能往前推。治理先解决的是大家怎样不互相踩踏。
写清楚流程,Agent 也不一定遵守
很快,一连串问题出现了:Agent 擅自在 Draft 和 Ready 之间切换 PR 状态;没关联 issue 就提交 PR;CI 已经失败,评审和合并照样继续;改了代码不更新对应的文档、卡片和依赖状态;没经授权就 force-push、重排堆叠的 PR、关掉 issue 或 PR;负责 review 的 Agent,也会把自己的错误理解包装成一条正式而笃定的修改要求。
我当时的原话是:"因为再不搭队友还会胡乱写。"
最典型的一次,是一个 PR 在后端检查失败的情况下被批准并合并了,main 随之坏掉,又靠下一个 PR 修回来。这件事证明了"大家知道流程"和"流程能阻止错误"完全是两回事。对人有效的提醒,对 Agent 未必有效:只要当前的提示词里没有那条规则,或者操作者给了一个范围过宽的授权,Agent 就会优先完成眼前的目标,顾不上仓库的整体秩序。
让系统替我说"不"
于是治理变成了三层:Agent 必须读取的规则入口;本地的 pre-push 检查;GitHub 端的 PR 规则检查、必需检查项和分支保护。
规则上线以后,队友那边开始出现"PR 怎么推不上去了"。我当时说了一句"有点幸灾乐祸"。现在看,那是一种迟来的确认:过去只有我看得见、需要反复解释的问题,现在终于由系统替我说"不"了。从这以后,我不再把主要希望放在提醒、模板和口头约定上,转而依赖可执行的状态条件:缺 issue、缺验收证据、缺检查结果,PR 就不能被当成具备合并条件。
格式完整,也可能是假的进度
门禁能管住进入正式评审的 PR,却管不住空洞的 issue、占位用的 Draft,以及没有实际验收意义的工作项。所以检查又往前提到了 issue 和 PR 被创建的那一刻。它不禁止任何人建 issue 或 PR,但要求至少说清楚:为什么要做、实际解决什么问题、怎样才算完成、有哪些依赖和风险、这一次提交是不是真的值得评审。
治理的对象,从"坏代码"扩大到了"伪装成进展的低信息量产物"。Agent 很擅长生成形式完整的文本,所以治理不能只检查字段有没有填,要检查这些字段有没有提供能约束实现和评审的内容。一个格式漂亮、却没有问题定义、验收边界和实际证据的 PR,并不比一个明显没写完的 PR 更成熟。
没有执行环境的检查,只是一张图
然后 GitHub Actions 被组织停用了,CircleCI 又还没获批(那个晚上我在上一篇 CI 日记里写过)。没有实际执行环境的必需检查,只是一张制度设计图。所以我连夜用 CircleCI、Cloudflare Worker、队列、webhook 和一个 Kanban 状态校验器,把整条链路先跑了起来,再靠真实的故障去修正它。
这一阶段的代价我也记下了:那几个基础设施 PR 合并时的证据,比后来的治理 PR 弱,有一个合并时页面上的规则检查甚至还是红的。当时优先解决的是"规则根本没法执行"这个真空,所以接受了比后来更高的治理债务。
门禁本身也会出错
接着是另一类问题:Windows 的 CRLF 换行导致检查假失败;本地和远端重复执行同一套检查;不同 CI 服务商之间的差异制造误报。我的看法在这时变了第二次:门禁并不是越多越好,一个不真实、诊断不了、来源不清楚的门禁,本身也在制造垃圾。
那几个 PR 做的事是:消除换行差异造成的假失败;避免本地和远端重复执行,同时保留远端不可用时必要的本地检查;建立 none、github、circleci 三种质量档位;建立一个和 CI 服务商无关的 PR 规则状态。治理开始对自己的工程质量负责:证据必须真实,失败必须能解释,环境差异不能制造无意义的返工,不能把一个没执行的质量来源伪装成"已验证",仓库规则也不能永久绑在某一个临时的 CI 服务商上。
这对 review 的 Agent 同样适用:修改要求和门禁都不是天然正确的,它们首先是待验证的诊断,作者不必无条件服从。
CircleCI 真正可用以后,我没有假设它会按设计产生哪些检查项,先用一个真实的 PR 看它实际产生了什么,再据此修改分支保护,最后核对合并后 main 上的流水线。先观察系统实际产生了什么,再把它提升成仓库的权威,这个顺序从此固定了下来。
真正的问题,是人怎样控制 Agent
8 月底,我对五个人的提交做了一次细致的调查。结论是:共享的模板、Kanban、质量门和 Agent 的文本能力,把所有 PR 的外观都抹平了。规范的 Summary、完整的勾选清单、漂亮的 review 回复,证明不了操作者真的理解自己的工作归属、非目标、和别人工作的重叠、当前的分支拓扑、失败路径和权限边界,也证明不了他能判断 reviewer 的意见是否合理。
我当时说最危险的是"手伸得太长管别人工作"。Agent 最危险的能力,比写错一段代码更甚:在模糊的授权下替别人改工作范围、重写历史、管理别人的 PR,甚至替人做合并决定。账号权限隔离解决不了这个问题:五个人都通过 gh 操作项目,仓库没法可靠地判断某一次操作是本人执行的,还是 Agent 用同一个身份调了 API。
所以治理的目标变成了一个更现实的控制面:行动前必须读取权威上下文;PR 必须声明工作归属、非目标和重叠;review 必须针对确切的当前提交;修改要求必须给出客观证据;规则不假装能识别账号背后的实际操作者;对分支、PR、issue、Kanban 和合并状态的管理动作,需要当前的、明确的、具体的授权。
文档结构也在这时被我当成了 Agent 的提示词基础设施。旧的 Sprint 报告、目标设计、当前契约和研究材料混在同一层 docs/ 里,人还能靠时间和经验判断哪份更权威,Agent 很可能把它们都当成同样有效的事实。既然不能限制 Agent 用 gh,就得保证它进到仓库以后,首先看到的是正确、当前、层次分明的事实。
治理者本人也要被治理
基础设施治理里,有时确实需要临时降低 approval 要求、调整分支保护或放宽 ruleset。可这就有了一个循环:规则由我定,例外由我开,修改规则的 PR 又由同一个账号合并,靠什么证明这个例外仍然受规则约束?
后来的"治理窗口"就是为了回答这个问题:每次例外都要记下操作者、允许操作的 PR、有效时间、允许的动作和恢复的证据。它没有取消维护者的特权,只是把一种隐藏的特权变成有边界、限时、可审计的例外。
这件事我做得也并不一致。不同的治理 PR 临时调整的控制项各不相同;有一个 PR 虽然检查都过了,却没有完整填写刚刚建立的治理例外字段。GitHub 上的"由某账号合并"也只能证明那个账号执行了合并,证明不了是我点的还是 Agent 调的 API。制度的作者,自己也需要时间去适应自己建立的制度。
落成一份 skill
8 月 30 日,我把这些整理成了一份仓库治理 skill:先写中文母版,把讨论稿和仓库现有的治理文档合并去重,再翻译成英文、做成 skill,随一个治理 PR 进了仓库,此后它是治理规则唯一的现行来源。后来我把它抽成了一个独立的公开仓库,也可以搬到别的项目里用:
https://github.com/Ysoseri1224/repo-governance-for-novice
最后的认识
回到开头那件事。我一开始担心的是大家写错代码,后来担心的是 Agent 在错误的权限、归属和上下文里,高效率地完成一件本来就不该做的事。这套治理能做的,是让所有操作者在行动前接触到同样的权威信息,在行动时声明范围和边界,在评审时提供能复核的证据,在出现例外时留下明确、限时、可恢复的记录。
Agent 让执行变得很便宜,但判断、授权和责任并没有因此变便宜。仓库治理要约束的,是目标、上下文、所有权、证据和状态变化之间的关系,Agent 只是这些关系里的一个执行者。