AI 编程代理(AI coding agent)进入研发流程后,团队最先感受到的往往不是效率提升,而是代码评审压力的增加。Figma 工程师在 AI Engineer 相关分享中讨论过同一个问题:当代理自动完成跨文件修改、自动执行命令、自动生成测试时,如何保证最终提交的代码仍然是干净、可维护、可回滚的。核心结论并不是“少用 AI”,而是先用工程手段把 AI 约束在安全边界内。
这篇文章不准备逐句复述某一场演讲,而是把这一类实践整理成一套可以在自己团队中复用的落地流程。整个过程会围绕一条主线展开:先理解 AI 编程代理为什么会产出质量不可控的代码,然后从任务边界、仓库规则、自动化校验、代码评审、线上监控和回滚几个环节建立护栏。每个环节都会给出可执行的配置、命令和检查清单,尽量做到看完就能在自己项目里试点。
1. 先理解 AI 编程代理为什么会写出“垃圾代码”
1.1 辅助补全工具与自主代理的最大区别
很多团队已经习惯了 AI 补全工具的工作方式:人写函数名,AI 补函数体,人再手动修改。这种模式下,修改方向和整体结构都由人控制,AI 只是加速了局部输入。
AI 编程代理不一样。它接收的是一个任务描述,而不是某一行代码的上下文。它会自己搜索代码库、修改多个文件、执行构建命令、读取测试结果,甚至反复重试。人从“逐行写代码”退后到“发布任务和检查结果”。
这一变化带来的风险是结构性的:
- 人不再对每个修改点有直接感知。
- AI 可能为了满足任务描述,修改超出范围的文件。
- AI 倾向于让测试通过,但不一定理解代码库的历史约定和设计意图。
- 一旦任务描述模糊,AI 会主动脑补业务规则,产生“看起来合理、实际上错误”的代码。
所以,把 AI 编程代理接入仓库,第一步不是打开工具开关,而是先理解它和补全工具完全不同的工作模式。
1.2 “垃圾代码”在代理场景下有哪些典型表现
“垃圾代码”不是一个笼统的贬义,而是一类可以在评审和运行时被识别的问题。在 AI 编程代理场景下,最常见的是这几种:
| 现象 | 具体表现 | 为什么容易发生 |
|---|---|---|
| 表面可用但内部混乱 | 大量 if else 堆叠、复制粘贴式修改、命名随意 | AI 优化的是“让测试通过”,而不是可读性 |
| 错误处理空白 | 吞掉异常、空值不判断、网络错误不重试 | 任务描述里没有提出错误分支要求 |
| 过度设计 | 为一个简单字段引入抽象接口和工厂 | AI 从训练数据中学习到“看起来专业的写法” |
| 修改范围失控 | 改完目标函数后顺手改掉公共工具类 | 代理在检索上下文时发现“相关代码”就会一并改 |
| 测试失效 | 测试断言被调整成恒真条件,或只覆盖正向路径 | 代理为了自圆其说,会修改测试来匹配实现 |
| 隐性破坏 | 调用方没改、函数签名变了、返回结构变了 | 代理只关注当前任务覆盖到的调用点 |
这些问题的共同根源是:代理在优化一个局部目标。它没有项目全局视角,也不清楚哪些代码是核心资产、哪些代码是临时方案、哪些修改需要同步通知其他团队。
1.3 先设护栏,再谈效率
Figma 工程师在分享中反复强调的一点,是把 AI 编程代理当作“高速但经验不足的工程师”来管理,而不是当成一个纯生成器。
一个刚入职的工程师,公司会给他什么?
- 明确的任务边界。
- 仓库结构和编码规范。
- 构建、测试、lint 命令。
- 代码评审机制。
- 上线后的监控和回滚手段。
这些就是护栏。AI 编程代理同样需要这套东西,而且需要得更严格,因为它的“理解能力”来自上下文,而不是长期记忆。如果仓库里没有明确的规则文件,代理就会按照自己的默认偏好写代码;如果任务描述没有边界,它就会把相关文件全改一遍;如果合并前没有强制校验,它就能把编译不过或测试失败的代码直接推进主干。
所以,安全落地的核心不是“更聪明的模型”,而是更完整的工程流程。后续章节按这个顺序展开:接入前准备、任务拆分、自动校验、代码评审、线上监控、失败复盘。
2. 接入 AI 编程代理前:确定边界、规则和基线
2.1 先回答三个问题:做什么、不做什么、怎么算完成
接入代理前,团队需要先对使用场景达成一致。不是所有任务都适合交给代理,也不是所有代码库都适合在第一天开放全部目录。
推荐先按任务类型做评估:
| 任务类型 | 适合交给代理吗 | 风险等级 | 说明 |
|---|---|---|---|
| 生成单元测试 | 适合 | 中 | 需要人检查断言是否有效 |
| 代码补全 | 适合 | 低 | 人在当前文件中掌握上下文 |
| Bug 修复 | 视情况 | 中高 | 必须先有稳定复现路径 |
| 跨模块重构 | 谨慎 | 高 | 建议拆成小步执行 |
| 自动化脚本生成 | 适合 | 低 | 独立脚本,影响范围小 |
| 底层公共库修改 | 不建议初期开放 | 极高 | 影响所有调用方 |
这三个问题必须在试点前回答清楚:
- 这个任务允许代理修改哪些目录和文件。
- 这个任务禁止代理修改哪些目录和文件。
- 任务完成的验收标准是什么,包括测试覆盖率、构建通过、无 lint 错误等。
没有边界判断就放代理进仓库,等于让一个新工程师自己决定改哪里。区别是,真人会问,代理不会问。
2.2 在仓库根目录建立规则文件
把团队的编码约束写进一个代理可读、人也可见的规则文件。目前很多编程代理会主动读取仓库根目录下的AGENTS.md或等价文件,并把它作为系统的上下文注入。这个文件可以包括:
- 项目技术栈和关键依赖。
- 构建、测试、lint 的准确命令。
- 目录结构和职责划分。
- 编码风格约定。
- 禁止修改的目录和文件。
- 提交信息规范。
- 完成任务的 Definition of Done。
下面是一个AGENTS.md示例,可以直接作为起点:
# 项目规则 ## 技术栈 - Python 3.11 - FastAPI - SQLAlchemy 2.x - pytest ## 常用命令 - 安装依赖: pip install -e ".[dev]" - 单测: pytest tests/ -x -q - 类型检查: mypy app/ - 格式化: ruff format app/ tests/ - lint: ruff check app/ tests/ ## 目录职责 - app/api: 路由层,只做参数解析和响应封装 - app/services: 业务逻辑层 - app/models: ORM 模型 - app/migrations: 数据库迁移文件,禁止手动改动 ## 禁止修改 - app/migrations/ - docs/ - gen/ ## 编码约束 - 函数需要 docstring - 禁止裸 except,必须捕获具体异常类型 - 新增对外接口必须包含输入校验 - 错误信息不允许直接暴露内部堆栈 ## 提交信息 - 遵循 Conventional Commits - 示例: fix(api): handle empty user id ## 完成定义 - pytest 全部通过 - mypy 无错误 - ruff check 无错误 - 不修改任务范围之外的文件注意,不要把规则文件写得像散文。代理对长文本的理解能力有限,规则要短、要清晰、要可检查。比如“保持代码整洁”这种话没有意义,要写成“函数长度不超过 50 行,超出则拆分”。
2.3 先把代码库基线跑稳态
在让代理开始改代码之前,仓库本身必须是健康的。否则会出现一个经典的死循环:代理跑出错误,你分不清是它造成的还是仓库原本就有的。
建议先完成:
# 本地完整执行一遍 pip install -e ".[dev]" pytest tests/ -x -q mypy app/ ruff check app/ tests/ # 记录执行结果和时间 echo "baseline done" > .ai_agent_baseline这道工序有几个作用:
- 确认 CI 命令和本地命令一致,避免代理在本地通过了、CI 却失败。
- 确认测试基线是绿的,代理后续改动如果破坏测试,可以直接定位到它。
- 确认构建时间合理,如果一次测试要跑 30 分钟,代理的每次尝试都会很昂贵。
如果仓库里存在大量历史遗留的失败测试,先把它们清理掉或者标注skip,再让代理介入。否则代理会修复失败测试作为“完成目标”,而不会关心这些测试是否应该有。
3. 任务拆分与上下文注入:不要让代理吃下过大的任务
3.1 一个任务对应一个可评审的变更集
把 AI 编程代理想象成一个只会专注当前 prompt 的工程师。给它一个“优化整个订单系统”的任务,它会输出一个几百行甚至上千行的 diff。这种 diff 很难评审,而且一旦产生问题,很难定位是哪一步引入的。
推荐的拆分粒度是:一个任务只解决一个问题,一个任务生成的变更集要能被人在 15 到 30 分钟内评审完。
下面的任务描述模板可以直接复制使用:
## 目标 修复 API 层在用户 ID 为空时返回错误码的问题。 ## 范围 - app/api/users.py - tests/test_api_users.py ## 禁止修改 - app/services/ - app/models/ - app/migrations/ ## 验收标准 - 新增测试覆盖 user_id 为 None 和空字符串两种情况 - pytest tests/test_api_users.py -x -q 通过 - ruff check app/api/users.py 通过 - 不修改范围之外的文件这个模板的关键是明确了边界和验收标准。代理不需要猜测,它只需要执行。
3.2 上下文越多不代表效果越好
有些团队为了让代理更“懂业务”,会把整个技术设计文档、需求文档、历史变更记录全部塞进 prompt。这会导致几个问题:
- 上下文过长后,代理会把注意力分散到无关信息上。
- 关键约束被淹没在大量文本中间。
- 每次请求的成本和时间都会上升。
更合理的做法是分三层提供上下文:
| 上下文类型 | 内容 | 示例 |
|---|---|---|
| 全局规则 | 仓库级规则文件,代理自行读取 | AGENTS.md |
| 任务上下文 | 当前需求的背景和业务规则 | prompt/issue 描述 |
| 局部参考 | 类似功能的实现代码或接口定义 | 粘贴少量代码片段 |
在实际操作中,不需要把整个业务背景都复制到 prompt 里。告诉代理“这个函数是给前端登录接口用的,user_id 来自 JWT token,为空说明鉴权失效,应该返回 401”,远好于贴三页需求文档。
3.3 分支策略:让代理的错误被隔离
代理在执行任务时可能会反复尝试、提交失败代码。不要让它在主干分支上直接工作,至少在试点阶段,给每次任务开一个独立分支。
git checkout -b feat/ai-agent/user-id-validation如果需要多个代理并行处理不同任务,命名规则可以用ai-agent/任务标签作为前缀,方便后续统一 review 和清理。
代理完成后的工作流:
# 拉取最新主干并合并到当前分支 git fetch origin main git merge origin/main # 运行完整校验 pytest tests/ -x -q mypy app/ ruff check app/ tests/ # 查看最终变更范围 git diff --stat main...git diff --stat main...这一步很重要,它能让你在合入前直观看到代理到底改了哪些文件。如果出现大量与任务无关的文件,应该直接打回,而不是手动挑拣。
4. 从生成到合并:强制自动校验和人工评审关卡
4.1 合并前的自动化检查不能只依赖“代理自测”
代理在执行任务时通常会自己运行一遍测试,但这个自测结果只能作为参考。它的测试目标可能被污染,它也可能因为环境差异在自己的沙箱里通过、在 CI 里失败。
所以仓库必须有一套不依赖代理自身的强制检查流程。最简单的方式是在 CI 中增加一个独立的 job,专门校验代理分支:
name: ai-agent-check on: pull_request: types: [opened, synchronize] jobs: verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.11" - run: pip install -e ".[dev]" - run: pytest tests/ -x -q - run: mypy app/ - run: ruff check app/ tests/ - run: ruff format --check app/ tests/ - name: check diff scope run: | # 如果任务只允许修改 app/api/,则检测是否出现其他目录变更 if git diff --name-only origin/main...HEAD | grep -qv "^app/api/" ; then echo "发现代理修改了范围之外的目录" exit 1 fi这里的check diff scope是关键步骤。它做了一件代码评审中最容易被忽略的事情:确认变更范围是否越界。代理也许能通过所有测试,但如果它改坏了app/models/下的数据模型,AI 生成的测试根本无法覆盖所有调用方的影响。
本地同样可以做一个 pre-push 钩子,避免代理把明显不合规的代码推到远端:
#!/usr/bin/env bash # .git/hooks/pre-push 或项目内的 pre-push 脚本 set -e echo "running ai-agent pre-push checks..." pytest tests/ -x -q mypy app/ ruff check app/ tests/4.2 人工评审的重点不是“读代码”,而是“问问题”
即使自动化检查全绿,也不能直接把 AI 生成的代码合入。人工评审仍然不可省略,但评审方式需要调整。
AI 生成代码的评审,重点不是逐行检查语法,而是回答这几个问题:
- 这个改动解决了任务描述里的问题吗?
- 它有没有修改任务范围之外的文件?
- 错误处理是否真实,还是只是让测试通过?
- 有没有引入重复逻辑或新的抽象,而这个抽象没有明显收益?
- 测试是否真的会失败?把关键断言暂时改错,测试是否变红?
- 是否有并发、时间、数据一致性方面的问题?
- 依赖和数据库迁移是否被无意修改?
可以把这些整理成评审模板,合入每个 AI 代理 MR 时使用:
| 评审点 | 检查内容 | 通过标准 |
|---|---|---|
| 范围控制 | 与git diff --stat对比任务范围 | 无越界文件 |
| 正确性 | 能复述这次改动解决的业务场景 | 理解与任务一致 |
| 异常处理 | 空值、异常、超时等分支有处理 | 不吞错、不裸 except |
| 测试质量 | 故意破坏断言,测试是否失败 | 测试有实际约束力 |
| 重复代码 | 搜索是否已有相似实现 | 没有重复逻辑 |
| 修为接口 | 公共函数签名、返回结构是否改变 | 调用方已经同步更新 |
| 性能风险 | 是否有循环内查询、N+1、大对象复制 | 明显性能隐患已消除 |
4.3 要求代理先完成自检,并通过 prompt 约束行为
可以让代理在生成代码后自动执行一系列检查,并把输出结果带回。比如在任务描述中追加:
## 提交前自检 在生成最终结果之前,你必须执行以下命令: 1. pytest tests/test_api_users.py -x -q 2. mypy app/api/users.py 3. ruff check app/api/users.py 如果检查失败,继续修复直至通过。如果无法通过,需要说明具体原因和待确认问题。这种方式相当于要求代理输出一份“自检报告”。它不一定完全准确,但可以提升代理对错误的关注程度,也能帮你快速判断代理卡在了哪个环节。要注意,不要因为代理报告“全部通过”就直接合入,报告需要和 CI 结果交叉验证。
5. 上线后的监控与回滚:质量问题是运行时才暴露的
5.1 区分学习环境与生产环境的要求
在本地试用 AI 编程代理时,可以只关注“代码能不能跑”。但一旦进入生产环境,AI 生成代码的质量判断标准就要切换到运行表现上:
| 维度 | 学习环境 | 生产环境 |
|---|---|---|
| 验收标准 | 编译通过、本地测试绿 | 错误率、耗时、业务指标无回退 |
| 检查手段 | IDE、手动测试 | 日志、监控、告警、链路追踪 |
| 问题处理 | 改代码重新跑 | 先止血、再定位、再修复 |
| 数据要求 | 造数方便、无真实用户 | 需要考虑兼容和数据迁移 |
| 回滚方式 | git revert | 功能开关、版本回滚、数据库兼容方案 |
如果团队正在用代理生成数据库变更或涉及支付、权限的核心代码,上线前必须增加一层额外评审,并且尽量让变更可以在不重新发布代码的前提下被关闭。
5.2 上线后先看异常率,再看耗时,最后看业务指标
生产环境不会直接告诉你“代码写错了”,它只会通过指标异常间接表达。针对 AI 生成代码,建议上线后按这个顺序观察:
- 错误率:如果部署后错误率出现明显增长,优先怀疑新增逻辑的异常分支没有处理好。
- 耗时:P50、P95、P99 是否上涨,提示可能存在循环内查询、不必要的重试或无界缓存。
- 业务指标:订单成功率、接口调用量、转化率是否有回退,防止代理把业务逻辑改出偏差。
下面是一个简单的压测对比方式,用于上线前快速暴露代理改动的性能问题:
# 部署前在基准版本上记录指标 k6 run --summary-export=baseline.json load-test.js # 部署代理分支后再次执行 k6 run --summary-export=after.json load-test.js # 对比 P95 耗时 python -c " import json with open('baseline.json') as f: base = json.load(f) with open('after.json') as f: after = json.load(f) b = base['metrics']['http_req_duration']['values']['p(95)'] a = after['metrics']['http_req_duration']['values']['p(95)'] print(f'P95 baseline={b:.2f}ms after={a:.2f}ms') "如果 P95 从 300ms 涨到 500ms,就要怀疑代理是否在高频路径里加了不需要的同步逻辑或重复查询。
5.3 回滚方案要在合入之前写好
AI 生成代码合入之前,应该先回答一个问题:如果线上出了问题,怎么最快回到上一个稳定版本。
推荐三种回滚手段,按速度排序:
| 手段 | 速度 | 适用场景 |
|---|---|---|
| 功能开关 | 秒级 | 新功能可以整体关闭 |
| 版本回滚 | 分钟级 | 代码变更和数据库兼容 |
| 补丁修复 | 半小时以上 | 问题定位明确、影响面小 |
同时要注意数据库回滚。如果代理生成的代码包含数据库迁移,不能只回滚代码而不回滚数据。例如新增了一个非空字段,代码回滚后,旧代码不会写这个字段,而数据库又要求它非空,就会出现线上写入失败。这类问题必须在评审阶段提前规避,尽量让数据库变更向后兼容。
5.4 把线上问题反哺给规则文件
每一次由 AI 生成代码引发的线上故障,都是规则文件迭代的素材。问题修复后,应回答这几个问题:
- 规则文件里缺少了哪条约束?
- 任务描述模板里缺少了哪个验收标准?
- 评审清单里漏掉了哪个检查点?
例如,如果代理因为吞掉了 Redis 连接异常导致缓存雪崩,那就应该把这条加入规则:
## 编码约束 - 所有 Redis 调用必须设置超时时间 - Redis 异常必须记录日志并返回降级响应,不允许吞掉异常规则文件不是一次性写好的,它是团队和 AI 协作过程的“沉淀物”。出现一次问题,就补一条规则,一段时间后,代理能犯的错误会明显变少。
6. 常见失败模式与排查路径
6.1 问题现象、原因、检查方式对照表
实践中,AI 编程代理相关的问题通常集中在几个固定场景。下面这张表可以直接用于团队内部排查:
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 代理生成大量无关代码 | 任务描述没有明确禁止目录 | 用git diff --stat查看文件范围 | 补充禁止修改列表,设定范围检查脚本 |
| 测试全绿但线上出错 | 测试断言被改弱,或没有覆盖真实业务分支 | 抽查关键断言,故意破坏实现看是否变红 | 要求测试必须验证业务结果 |
| 代理反复执行命令失败 | 本地命令和 CI 命令不一致,或依赖版本冲突 | 对比AGENTS.md命令与 CI 配置 | 统一命令,先跑通基线 |
| 代理一直修改同一个问题 | 上下文里缺少错误信息或根因提示 | 查看代理的执行日志和最后一次报错 | 补充日志或错误信息到任务上下文 |
| 规则文件没有生效 | 文件名不在代理支持的范围内,或路径不对 | 查看代理读取的文件列表 | 使用标准AGENTS.md文件名 |
| 代理改坏公共函数 | 对调用方感知不足 | 检查公共 API 变更 diff | 对公共模块单独设置评审人和保护分支 |
6.2 典型排查示例:代理完成任务但 CI 失败
假设你收到一个代理分支的 PR,CI 报错显示类型检查失败,但代理在任务描述里声称“所有检查通过”。可以按下面这条链路排查:
第一步,确认它改了哪些文件:
git diff --name-only origin/main...HEAD第二步,检查是否改动了AGENTS.md里声明过的依赖或配置:
git diff origin/main...HEAD -- pyproject.toml requirements.txt第三步,在本地用 CI 相同的命令复现:
rm -rf .venv && python -m venv .venv source .venv/bin/activate pip install -e ".[dev]" mypy app/第四步,如果本地能复现类型错误,让代理重新修复时,把错误信息完整放到 prompt 中:
类型检查失败,错误如下: app/api/users.py:42: error: "User" has no attribute "name" 请修复类型问题,不要修改 users.py 之外的文件。这个流程的关键是按顺序排查:先看输入(任务和规则),再看环境(依赖和命令),最后看输出(代码和错误)。不要一上来就怀疑模型能力,大多数问题其实出在流程和上下文上。
7. 从试点到制度化:让 AI 编程代理真正可控
7.1 先在小范围试点,不要全团队铺开
AI 编程代理落地不适合“一刀切”。建议先挑选 2 到 3 个具备以下特征的项目:
- 有完整的自动化测试,至少覆盖核心用例。
- 构建时间相对短,在 10 分钟内可以完成。
- 技术栈统一,依赖清晰。
- 团队成员愿意接受新的评审方式。
试点期间定义两个核心指标,不要追求过度复杂的度量:
- AI 分支被合入的比例,反映代理产出是否有可用性。
- AI 分支引发 CI 失败或线上问题的次数,反映代理是否稳定。
每周复盘一次,重点看失败案例而不是成功案例。成功案例只能说明流程没被触发,失败案例才能暴露流程缺口。
7.2 将规则、模板和评审清单固化为团队规范
当试点验证有效后,再把流程制度化:
- 把
AGENTS.md纳入仓库根目录评审范围,规则变更需要走 MR。 - 把任务描述模板、评审模板上传到团队文档库或 MR 模板中。
- 在 CI 中增加代理分支专用的范围检查任务。
- 让团队每个成员都学会写“可执行的任务描述”,而不是一句话需求。
- 在评审 AI 生成代码时,要求提交者附带代理的自检日志。
制度化不是为了增加流程负担,而是为了让每一次代理使用都产生可追踪、可复测、可改进的记录。没有记录的流程,无法沉淀经验。
7.3 可复用的落地检查清单
下面是一份可以直接打印出来贴在工位旁的检查清单,也可以作为 MR 模板的一部分。
接入前检查:
- [ ] 仓库能稳定通过 build、test、lint
- [ ]
AGENTS.md已包含技术栈、命令、目录边界、禁止修改项 - [ ] CI 已有独立的代理分支校验 job
- [ ] 已知哪些任务类型适合代理,哪些不适合
每次任务开始时:
- [ ] 任务描述包含目标、涉及文件、禁止修改项、验收标准
- [ ] 任务粒度控制在可评审的 diff 范围内
- [ ] 代理在独立分支上工作
合并前检查:
- [ ] 对比
git diff --stat,确认没有越界文件 - [ ] 自动检查全部通过
- [ ] 评审清单中的错误处理、测试有效性、公共接口影响已确认
- [ ] 数据库变更确认向后兼容
- [ ] 回滚方案已准备好
上线后观察:
- [ ] 错误率、P95 耗时、核心业务指标与基线对比
- [ ] 发现问题先回滚,再定位根因
- [ ] 将根因和预防措施更新到
AGENTS.md
这一套流程做完,AI 编程代理会在仓库里留下大量高质量代码,同时把风险控制在可接受的范围。它不会自动写出完美代码,但团队可以通过流程让“垃圾代码”很难通过每一道关卡。
Figma 工程师的分享之所以值得借鉴,不是因为 Figma 使用了某个特别的工具,而是因为它把 AI 编程代理当作系统中的一个组件来治理。任何团队都可以用同样的思路:先设置规则,再开放权限;先用小范围验证,再扩大使用;先保证能回滚,再追求效率。方向对了,代码质量自然会向好的方向收敛。