作者让 GitLab Duo 的规划、审查、修复、安全和发布 Agent 参与一个 Python 服务的交付,记录返工、流水线竞争问题及人工操作失误。文中称容器扫描发现项从 166 降至 1,并提供公开流水线和合并请求作为依据,合并仍由人完成。
简单说:我围绕一个小型 Python 服务配置了五个 GitLab Duo Agent,分别负责 plan、review、fix、secure 和 release,让它们接手代码写完之后的工作。它们用大约两分钟规划了一项功能,让同一个修复经历了三轮审查,诊断出一次 pipeline 竞态,并把容器扫描发现的问题从 166 个降到了 1 个。它们从未合并任何代码:合并都是我做的,而我在这个过程中犯过一次错。
对于小型服务来说,写代码已经不再是构建过程中最慢的环节。真正耗时的是后面的工作:规划变更、审查代码、修复出错的 pipeline、分诊安全问题,以及在不影响生产环境的前提下发布。我参加 GitLab Transcend 黑客松(主题是“代码之后的生活”,Life After Code),就是想弄清楚:这些工作有多少能交给 AI Agent,它们又必须在哪里停下来。
这篇文章会如实记录整个过程。所有内容都来自一个真实项目、真实的 merge request 和真实的审查结论,也包括两次失误。文中的每一个数字,都来自该项目公开的 pipeline 和 merge request 历史记录。
这个服务叫 ro-cited-answers,是一个采用 MIT 许可证的演示项目,用罗马尼亚语回答有关公共办事流程的问题,只有一条原则:没有来源,就不回答。四道 gate 会拒绝任何缺乏文档支持的回答。我在黑客松之前就已经做出了第一个版本。这次参赛的重点,是后来围绕它补上的东西:一条 GitLab CI/CD pipeline,以及一组 GitLab Duo Agent。它们都放在公开仓库中。
线上服务的 health endpoint 会标明当前版本。
比赛定义了代码完成之后的九个阶段:plan、create、verify、package、secure、release、configure、monitor 和 govern。这条 pipeline 的覆盖方式如下:
整个系统里没有任何 service account key。组织策略禁止创建这类密钥,因此 GitLab 会为每个 job 签发一个 token,Google Cloud 则通过 Workload Identity Federation 信任它。
每个 Agent 都是一个自定义 GitLab Duo flow,并且拥有独立的 service account。定义文件放在仓库的 .gitlab/duo/flows/ 中,由同步脚本负责应用,因此仓库就是唯一事实来源。所有 Agent 都受 AGENTS.md 约束:可以创建 merge request、写备注,但不能合并代码,也不能部署。后来我又增加了第六个 flow,用来处理配置漂移;下面这五个是核心。
创建 issue 后,规划 Agent 就会自动启动。issue #1 提出了添加 tracing 的需求,大约两分钟后,它就发出了一份完整计划。它读过 AGENTS.md,指出这次变更没有修改任何 gate 规则,并把 trace 属性限制在一个永远不包含问题文本的 allowlist 内。这份计划后来变成了实际功能,以 !6 合并。
有一个缺陷:我把 issue #3 分配给安全 Agent 时,规划 Agent 也运行了,因为“创建 issue”同样会触发事件。这造成了一次重复运行,我把它记成了后续待办。
当它被指定为 reviewer 时,就会启动。它只围绕一个问题展开审查:这项变更会不会让系统回答原本会拒绝回答的内容?
最好的例子是 MR !5。审查 Agent 对它进行了三轮审查:第一轮发现两个高严重性问题,第二轮发现三个中严重性问题,第三轮给出可以合并的结论。我接受了 merge request 中的两个低严重性问题。前两轮的发现都是对的。它还在后来的 merge request !7 中发现了一个真实 bug:代码假定第一条 traffic entry 就是正在提供服务的 revision,这个假定是错的。问题在合并前得到了修复。
我也发现了一个限制:Duo 会在 Agent 的上下文中脱敏那些看起来像罗马尼亚个人身份号码的字符串。因此,在审查 !6 时,我在测试里使用的示例值,在 reviewer 眼中变成了 "[REDACTED]"。它起初也无法读取 .gitlab/duo/,因为这个目录被 Duo 排除在上下文之外。针对不同路径的审查规则,只好移到 AGENTS.md 中。
pipeline 失败时,修复 Agent 就会启动。我在一个分支上故意放入了一个贴近实际的 bug,也就是一个 NameError,并创建了 !2。pipeline 2954432632 的启动检查失败了。修复 Agent 随后创建了 !3,用一个字符的修改修好了问题,运行了离线 gate 测试,6 项全部通过;又运行了本地 smoke check,4 项全部通过。merge request 的 pipeline 随之变绿。
后来,它还诊断出一次并非我刻意制造的故障。两次合并相隔 8 秒,都使用了同一个 Cloud Run tag:candidate。其中一条 pipeline 验证了另一个 commit 的 revision,导致 7 项检查中有 6 项失败。没有任何错误版本进入生产环境。修复 Agent 创建了 issue #4,描述了这个竞态,给出了三个选项,并写明“需要人来决定”。它没有修改代码。我最终选择的修复方案是 !5:为每个候选版本打上包含其 commit 的 tag,并且只有当该 commit 仍然是 main 的最新提交时,才允许提升为生产版本。
我给它分配了一个 issue,让它分诊扫描器的输出。它整理出一张表,共有 173 个发现项:166 个来自容器扫描,7 个来自 SAST,secret 为 0。它没有把任何一项直接判定为无需处理,而是创建了 !4,将基础镜像从 python:3.12-slim 换成 Alpine,并使用非 root 用户。合并后,main pipeline 的容器扫描结果从 166 个问题降到了 1 个,剩下的是 zlib 中的一个 Medium 问题。merge request pipeline 无法展示这个变化,因为容器扫描不会在 merge request pipeline 中运行,所以前后数字只有在合并之后才能测出来。这次合并由人完成,因为真正保障生产安全的控制措施,是候选版本检查加上自动回滚。
main 上的 pipeline 无论成功还是失败,都会触发它。它会创建一个 issue,写入发布摘要。第一次运行时,它试图自行检查生产环境,却被 Duo sandbox proxy 拦住了。它报告的是“未验证”,没有靠猜测下结论。随后,我们在 .gitlab/duo/agent-config.yml 中精确放行了那两个 Cloud Run hostname,后续运行就能自行检查线上服务了。
“未验证”这一行,是整个项目中我最喜欢的结果。因为这个服务从头到尾坚持的,就是不对缺乏依据的事情作出断言。
那次竞态。 上面提到的共享 candidate tag,是我的设计失误,而 pipeline 自己的版本检查抓住了它。Agent 帮忙诊断了问题,但没有阻止它发生。
没读完结论就合并。 我写过一条命令,把读取审查结论和合并放在同一步执行。!13 是对修复 Agent 的去重修复,它得到的结论是 NOT READY,而且那个高严重性问题确实成立:推送同一个 commit,两次都能成功。建议的补救方案也没通过我自己的测试。!14 改用了一个唯一的占位 commit,并采用只有目标分支不存在时才会成功的 push;它在获得 READY 结论后才被合并。我留下的规则是:读取审查结论单独一步,合并再单独一步。
重复劳动。 修复 Agent 针对同一个 YAML 故障创建了重复的 merge request,也就是 !8 和 !9;规划 Agent 也在 #3 上重复运行过。这两件事都进入了后续待办。
还有一件事尚未在实际环境中验证。 !14 依赖 Agent sandbox 内的 git 工具在 push 时接受一个 lease flag。我在本地测试过这套逻辑。等下一次真实故障发生,才能知道 sandbox 是否接受它。
每一次合并。Agent 在权限设计上就没有合并权限。在这个项目中,每次都是我读完审查结论,再明确说出“合并”。
此外,还包括:当 Agent 写下“需要人来决定”时选择哪个方案,某个发现的问题是否可以接受,任何涉及比赛规则的事项,以及视频和参赛提交本身。
对于上面这些案例,这条 pipeline 已经能走通 Hands-off 路径,但我只测试过这些案例。Agent 只运行了寥寥几次,而不是几千次。服务本身也是基于小规模文档集的演示项目,模型调用使用的是托管的 open-weight 模型,并不是我在本地运行的模型。我也不知道每次运行会消耗多少 Duo credit。
在 GitLab 上 fork 这个公开仓库。它采用 MIT 许可证,README 中的“The post-code pipeline”一节介绍了各个阶段。
设置 pipeline 需要的 CI variables,包括设为 masked 和 protected 的模型 API key,以及生产环境 URL;再将 Google Cloud Workload Identity Federation 的 condition 指向你自己的 namespace。
使用 GitLab token,通过 deploy/duo_sync.py 应用这些 flow。没有变化的 flow 会被跳过;发生变化的 flow 会以新的冻结版本发布。
创建一个 issue,看看规划 Agent 如何响应。创建一个 merge request,并把审查 Agent 指定为 reviewer。故意让一个测试失败,再看看修复 Agent 如何处理。
值得带走的规则是:让 Agent 提建议、创建 merge request,把合并保留为独立的人工步骤,并让生产版本在接收流量之前先证明自己能正常工作。
你自己的 pipeline 中,哪一步是你绝不会交给 Agent 的?支撑这条界线的证据又是什么?
最初发表于 devaland.com:《代码之后的五个 AI Agent:GitLab Duo 在真实 Pipeline 中做了什么》
如需采取进一步措施,可以考虑屏蔽此人和/或举报滥用行为。