☰
Headless Agent CLI 独立评审:代码审查与配置检查的工程实践
2026/10/13 12:57:25 网站建设 项目流程

1. 为什么"独立评审"才是 Headless Agent CLI 的正确打开方式

很多人第一次接触 Headless 模式的 Agent CLI,脑子里冒出来的第一个念头都是"自动化"——让它自己跑任务、自己改代码、自己提交。我一开始也是这么想的,折腾了小半个月,结果发现这条路又累又不稳:任务越复杂,Agent 越容易在中间某一步跑偏,而且你根本不知道它是在哪一步开始错的。后来我换了个思路,把 Headless Agent CLI 当成一个独立的评审者来用,整个体验完全不一样了。

所谓 Headless,指的是 Agent CLI 不依赖交互式终端界面,可以直接在脚本、CI 流水线、Git hook 里被调用,输入一段内容、输出一段结果就退出。它没有"对话窗口"这个概念,你给它什么它处理什么。而"独立评审"的意思是:让一个和主开发流程隔离的 Agent 实例,专门负责对产出物做审查、挑刺、给意见,而不是让它去写代码。

这个用法为什么合适?因为 Agent 在"生成"这件事上天然有幻觉风险,但在"评审"这件事上,它的价值反而被放大了。评审不需要它百分之百正确,只需要它把可疑的地方标出来,人来拍板。它像一个不知疲倦、不会顾及情面的初级审阅者,你不需要它多聪明,你需要它稳定、可重复、不遗漏。

这篇文章适合三类人看:一是已经在用 Codex 这类 Agent CLI 但总觉得"自动化不靠谱"的开发者;二是想把代码评审、文档校对、配置检查这类重复劳动外包出去的团队;三是单纯想搞明白 Headless 模式到底该怎么落地、而不是被各种教程带着瞎跑的人。下面我会从架构、实操、踩坑、进阶四个层面,把"独立评审"这套用法讲透。

2. 独立评审的架构:主 Agent 与评审子代理如何分工

2.1 为什么不能让同一个 Agent 既写又审

这是最核心的一条经验。我早期图省事,让同一个会话里的 Agent 写完代码后自己 review 一遍,结果它几乎永远说"看起来没问题"。原因很简单:同一个上下文里的 Agent 对自己的产出有确认偏误,它刚刚生成的内容还在上下文窗口里,它会倾向于认为自己的推理是连贯的、正确的。

正确的做法是把"写"和"审"拆成两个独立的进程调用。写的那次调用结束后,进程退出,上下文清空;评审时重新起一个 Headless 进程,只把产出物本身(diff、文件内容、配置文本)喂进去,不带任何"这是我刚写的"这类元信息。这样评审 Agent 面对的就是一份陌生材料,它的挑刺意愿会明显上升。

2.2 子代理(Sub-agent)模式的三种落地形态

"子代理"这个词听起来玄乎,落地其实就三种形态,我按复杂度从低到高列一下:

形态实现方式适用场景隔离程度
进程级隔离每次评审起一个新的 CLI 进程个人项目、本地 pre-commit高
目录级隔离评审 Agent 在独立工作目录运行,只读挂载待审文件团队 CI、多仓库高
容器级隔离评审跑在一次性容器里,网络与文件系统受限对外部贡献代码的评审最高

我个人最常用的是进程级隔离,因为它最简单,一条命令就能起。目录级隔离适合 CI 场景,因为 CI 本身就是干净环境。容器级隔离一般只在评审第三方 PR 时才值得上,成本偏高。

2.3 评审 Agent 的输入应该包含什么

这一点很多人做错。评审 Agent 的输入不是"帮我看看这段代码",而是结构化的材料包。我通常会给它这几样东西:

  • 变更的 diff(用git diff或git diff --staged生成)
  • 相关的上下文文件(被改动文件的完整内容,而不是只有 diff 片段)
  • 一份评审清单(checklist),明确告诉它关注哪些维度
  • 项目约定(比如命名规范、目录结构约定)

提示:diff 单独喂给 Agent 时,它经常因为看不到上下文而误报。把改动文件的完整内容一起给它,误报率能降一大截。

评审清单是灵魂。没有清单,Agent 会自由发挥,一会儿聊性能一会儿聊风格,输出没法用。有了清单,它的输出就变成可解析的结构化结果。我常用的清单维度包括:逻辑正确性、边界条件、错误处理、命名一致性、潜在的性能陷阱、安全相关写法。

3. 把评审跑起来:从命令行到 CI 的完整链路

3.1 最小可用的本地评审命令

先给一个能直接抄的最小例子。假设你已经装好了 Codex CLI,并且配置好了模型接入,那么一次本地评审大概长这样:

# 生成待评审的 diff 和上下文 git diff --staged > /tmp/review.diff git diff --staged --name-only | xargs -I{} cat {} > /tmp/review.context # 组装评审材料 cat /tmp/review.diff /tmp/review.context > /tmp/review.input # 起一个 Headless 评审进程 codex exec --headless \ --prompt-file ./review-checklist.md \ --input /tmp/review.input \ --output /tmp/review.result

这里几个参数值得说明。--headless表示不进入交互模式,处理完就退出。--prompt-file指定评审清单,把清单和材料分开管理,方便复用。--input是待审内容,--output是结果落盘,方便后续解析。

不同版本的 CLI 参数名可能略有差异,比如有的用-p代替--prompt-file,有的把 headless 模式叫exec或run。参数名以你本地--help输出为准,别照搬网上的教程,我见过太多人因为参数名对不上卡半天。

3.2 评审清单怎么写才有效

清单不是越长越好。我试过写二十条的清单,结果 Agent 每条都蜻蜓点水,输出质量反而下降。后来精简到六到八条,每条都要求它给出具体行号 + 问题描述 + 修改建议,输出立刻变得可用。

一份我常用的清单长这样:

你是独立代码评审者。请针对输入内容逐项检查,每项给出: - 是否存在问题(是/否) - 若存在,指出具体位置(文件:行号) - 问题描述与修改建议 检查项: 1. 逻辑正确性:是否存在明显的逻辑错误或死代码 2. 边界条件:空值、越界、并发等边界是否处理 3. 错误处理:异常是否被吞掉或未处理 4. 命名一致性:命名是否符合项目约定 5. 性能陷阱:是否存在明显的低效写法 6. 安全写法:是否存在注入、越权等风险模式

注意最后一句"每项给出是否存在问题",这个格式约束非常关键。它逼着 Agent 对每一项都表态,而不是挑几个它感兴趣的说说。没有这个约束,它经常只报两三条就收工。

3.3 把评审接进 CI 流水线

本地跑通之后,接 CI 是水到渠成的事。核心思路是:在 CI 的某个阶段,把 diff 和上下文准备好,起一个评审进程,把结果作为构建产物或评论输出。

# 伪配置,字段名按你所用 CI 平台调整 review_job: steps: - run: git fetch origin main - run: git diff origin/main...HEAD > review.diff - run: | codex exec --headless \ --prompt-file .ci/review-checklist.md \ --input review.diff \ --output review.result - run: cat review.result

这里有个坑要提前说:CI 环境里的 Agent 调用通常需要凭证,而凭证管理是安全红线。我的做法是把凭证放在 CI 平台的加密变量里,绝不写进仓库,也绝不在日志里打印。评审结果里如果包含敏感路径信息,输出前要做一次过滤。

注意:评审 Agent 的输出不要直接当成"通过/不通过"的硬门禁。它会有误报,硬门禁会误伤正常提交。我的做法是把它作为提示性评论,人来决定是否阻塞合并。

4. 踩过的坑:评审 Agent 的误报、漏报与上下文陷阱

4.1 误报的三种典型来源

误报是评审 Agent 最让人头疼的问题。我统计过自己项目里一个月的评审输出,误报大概占三成。归因下来主要是三类:

第一类是上下文缺失导致的误报。Agent 只看到 diff,看不到被调用函数的定义,就会说"这个函数不存在"。解决办法前面提过,把完整文件内容一起喂进去。

第二类是清单过于宽泛导致的误报。比如清单里写"检查性能问题",Agent 会把任何循环都标成性能隐患。把清单细化成"检查是否存在嵌套循环导致的 O(n²) 复杂度",误报立刻减少。

第三类是模型对项目约定不了解导致的误报。比如项目里snake_case和camelCase混用是有意为之(历史原因),Agent 不知道,就会一直报命名不一致。解决办法是在清单里附上一段项目约定说明。

4.2 漏报往往比误报更危险

误报烦人但无害,漏报才是真问题。我遇到过最典型的一次漏报:Agent 评审一个并发修改,没发现两个 goroutine 同时写同一个 map,结果上线后偶发崩溃。复盘时发现,清单里根本没有"并发安全"这一项。

所以清单要定期根据线上事故反推补充。每次出了 bug,问一句"如果评审清单里有这一项,能不能提前发现",能就加进去。这样清单会随着项目演进越来越贴合实际风险。

4.3 上下文窗口的边界处理

大项目里,一次 diff 可能涉及几十个文件,全塞进去会超上下文窗口。我的处理策略是分层:

  • 核心改动文件:完整内容喂入
  • 相关但未改动的依赖文件:只喂函数签名和注释
  • 无关文件:不喂

这个分层逻辑可以写成一个脚本自动完成,根据 diff 涉及的文件路径,去仓库里捞对应的上下文。别偷懒全塞,超窗口后 Agent 会截断,截断位置之后的内容它完全看不到,漏报就是这么来的。

4.4 一个真实的排查链路

有段时间评审结果里频繁出现"无法解析输入"的报错。我按这个顺序排查:

  1. 先看输入文件大小,发现某个 diff 有 8MB,怀疑超限
  2. 手动截断到 1MB 重跑,正常了,确认是大小问题
  3. 进一步查,发现是某次提交误把构建产物也 diff 进去了
  4. 在生成 diff 的命令里加上路径过滤,排除dist/、build/等目录
  5. 重跑,问题消失

这个链路说明一件事:评审 Agent 的很多"故障"其实是输入准备阶段的故障,别一上来就怀疑模型,先检查你喂给它的东西干不干净。

5. 让评审更准:清单迭代、结果解析与人工兜底

5.1 评审结果的结构化解析

Agent 的输出是自然语言,直接看很累。我一般要求它输出成固定格式,然后用脚本解析成表格。比如要求每条问题以[严重度] 文件:行号 - 描述开头,解析脚本就能按严重度排序、按文件聚合。

import re pattern = re.compile(r"\[(高|中|低)\]\s+(\S+):(\d+)\s+-\s+(.+)") issues = [] with open("review.result") as f: for line in f: m = pattern.match(line.strip()) if m: issues.append({ "severity": m.group(1), "file": m.group(2), "line": int(m.group(3)), "desc": m.group(4), }) # 按严重度排序输出 order = {"高": 0, "中": 1, "低": 2} issues.sort(key=lambda x: order[x["severity"]]) for i in issues: print(f"{i['severity']} {i['file']}:{i['line']} {i['desc']}")

有了结构化结果,就能做很多事:只把"高"严重度的推到人面前,"低"的攒起来定期看;统计哪类问题出现频率最高,反过来优化清单。

5.2 清单迭代的节奏

清单不是写完就完事的。我的节奏是每两周回顾一次:把这两周的评审结果翻一遍,看哪些条目从来没报过问题(可能太宽泛或没用),哪些条目误报特别多(需要细化),哪些线上问题清单没覆盖(需要新增)。

这个迭代过程本身就是价值。它逼着团队把"我们到底在意什么"想清楚,而不是笼统地说"代码质量要好"。

5.3 人工兜底不可省

无论评审 Agent 多好用,最终拍板必须是人。我见过有团队把评审结果直接当门禁,结果因为一次误报阻塞了紧急修复,得不偿失。正确的定位是:Agent 负责"广撒网",人负责"精判断"。它把可疑点都列出来,人快速扫一遍,该忽略的忽略,该改的改。

提示:把评审 Agent 的输出当成"同事的初稿意见",而不是"权威结论"。这个心态摆正了,用起来会顺很多。

6. 进阶玩法:多评审子代理交叉验证与场景延展

6.1 两个评审子代理交叉验证

单个评审 Agent 会有盲区。我的进阶做法是起两个评审进程,用不同的清单或不同的模型,然后对比结果。两个都报的问题,基本可以确认;只有一个报的,人工重点看。

# 评审者 A:侧重逻辑与边界 codex exec --headless --prompt-file checklist-logic.md \ --input review.input --output review.a # 评审者 B:侧重安全与性能 codex exec --headless --prompt-file checklist-security.md \ --input review.input --output review.b # 取交集与差集 comm -12 <(sort review.a) <(sort review.b) > confirmed.issues comm -3 <(sort review.a) <(sort review.b) > needs.human.review

这个模式成本翻倍,但换来的是更高的召回率。对于核心模块的改动,我觉得值。对于边角改动,单评审就够了。

6.2 评审场景不止于代码

"独立评审"这套思路可以延展到很多场景,我试过并且有效的有:

  • 配置评审:把配置文件 diff 喂给 Agent,检查是否有拼写错误、是否有未识别的字段。热词里提到的"unrecognized configuration setting"这类问题,用评审 Agent 提前抓非常合适。
  • 文档评审:检查文档里的链接是否失效、术语是否一致、示例代码是否能跑通。
  • SQL 迁移评审:检查迁移脚本是否有锁表风险、是否有回滚方案。
  • 依赖变更评审:检查新增依赖的许可证、体积、维护状态。

这些场景的共同点是:产出物是文本,评审标准可以写成清单,且人工评审很枯燥。只要满足这三点,就适合交给 Headless Agent。

6.3 与主开发流程的隔离原则

最后强调一条架构原则:评审 Agent 永远不要有写权限。它只读材料、只输出意见,绝不碰仓库。我见过有人图省事让评审 Agent 直接改代码,结果它把"建议"当"指令"执行了,改出一堆问题。

隔离的具体做法:评审进程的工作目录是只读挂载的,或者干脆在一个临时目录里跑,材料是拷贝进去的。这样即使 Agent 想写也写不了,安全边界清晰。

7. 我在实际使用中总结的几条硬经验

第一,评审 Agent 的价值不在"聪明",在"稳定"。它不需要比人强,它只需要每次都按同样的标准检查一遍,不遗漏、不偷懒。人做不到每次都全神贯注,它能。

第二,输入质量决定输出质量。花在准备评审材料上的时间,远比花在调 prompt 上的时间值得。diff 干净、上下文完整、清单明确,输出自然可用。

第三,别追求零误报。误报是评审 Agent 的固有特性,接受它,然后用结构化解析和人工兜底来消化它。追求零误报只会让你不断加约束,最后把有用的信号也约束没了。

第四,清单要跟着事故走。每次线上出问题,都问一句"评审清单能不能提前发现",能就加。这样清单会越来越贴合你的真实风险,而不是网上抄来的通用模板。

第五,评审结果要留档。我习惯把每次评审的输入和输出都存下来,一是方便回溯"这个问题当时为什么没报",二是积累一段时间后可以用来分析团队的高频问题类型,反过来指导开发规范。

这套用法我用了大半年,最大的感受是:Headless Agent CLI 最舒服的姿势,真的不是让它替你干活,而是让它替你盯着。写代码的活人自己干更靠谱,但盯代码的活,交给一个不知疲倦、不讲情面的独立评审者,刚刚好。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询