AI 编程代理完成代码后,引导它以对手视角主动寻找 bug、安全漏洞和边界情况,比温和自审更有效。
AI 编程 Agent 非常擅长把"我想要这样"变成"这是一个可运行的实现"。
它们也非常擅长对自己刚写出来的东西感到满意。
第二个特性是个问题。
我在使用编程 Agent 时养成的最高杠杆习惯之一,出人意料地简单:
在 Agent 完成一项有意义的工作之后,让它对自己的实现进行对抗性审查。
这往往会产生一段礼貌的小小庆祝。
相反,要改变目标。
扮演一个对抗性审查者。假设这个实现包含微妙的 bug、错误的假设、安全问题、竞态条件、遗漏的边界情况或架构问题。你的工作是找到它们。不要为实现辩护。试着去破坏它。
差异可能是巨大的。
当 Agent 正在实现一个功能时,它的工作目标大致是:
找到一条满足需求的可行路径。
一旦它找到了那条路径,它看到的一切都会它刚构建出的解决方案为基调。
你写了一个函数,运行了明显的测试,你的大脑悄悄地成为了这个函数的辩护律师。
代码看起来是合理的,因为你知道它本来要做什么。
对抗性审查给模型一个不同的角色:
假设实现是错的。找到证据。
这改变了它搜索的内容。
这是否满足了愉快路径?
我是否实现了请求的功能?
畸形输入会发生什么?
我做了什么假设,而调用者从未承诺过?
并发情况下会发生什么?
这个操作实际上是原子的吗?
它会在执行到一半时失败吗?
当依赖返回意外内容时会发生什么?
我是否引入了授权绕过?
我是否保留了现有行为?
有没有隐藏的性能悬崖?
测试是在证明行为,还是仅仅在执行实现?
同一个模型。同样的上下文。截然不同的搜索空间。
这样的提示词效果很好:
Perform an adversarial review of the implementation you just created.
Assume there are bugs.
Do not explain why the current implementation is good. Your job is to attack it.
Look specifically for:
- incorrect assumptions
- edge cases
- race conditions
- security vulnerabilities
- data corruption risks
- failure/retry problems
- backwards compatibility issues
- performance regressions
- missing validation
- incorrect error handling
- tests that pass without proving the intended behavior
For every issue you find:
1. Describe the failure mode.
2. Explain how it could occur in practice.
3. Rate its severity.
4. Point to the relevant code.
5. Propose a concrete fix.
Do not modify the code yet. First produce the review.
最后那条指令很重要。
我通常想要先看审查报告,再修复。
如果你立即让 Agent"找出并修复任何问题",它可能会在跳过解释的同时悄无声息地打补丁。将诊断和修复分离使推理过程变得可检查。
这也让你能够决定哪些发现是真实的。
因为 Agent 绝对有可能捏造问题。
对于更大的变更,我有时会更进一步,创建两个明确的角色。
You are the implementation engineer. Complete the feature.
You are now a senior engineer reviewing this change before production deployment.
You did not write this code.
Assume the implementation engineer was competent but may have made subtle mistakes.
Try to reject this change.
"You did not write this code"这句话出人意料地有用。
显然模型确实写了它。我们并不是在对 transformer 进行形而上学手术。
但角色框架会影响模型进行的分析类型。移除心理所有权,即使只是虚构的,往往会产生更怀疑的审查。
对于特别重要的代码,你可以更进一步:
Imagine this change caused a production incident three months from now.
Work backwards and identify the most plausible ways this implementation could have caused it.
现在你实际上是在要求一个小型的前置分析。
这通常会暴露出通用代码审查遗漏的问题。
让人工智能审查更有用的最简单方法之一是要求具体的失败案例。
这个实现是否健壮?
给我五个具体的输入、系统状态或事件序列,它们可能导致这个实现表现不正确。
对于每个疑似 bug,构建一个最小的可复现场景来证明它。
这将批评推向可证伪的主张。
例如,不要说:
这里可能有竞态条件。
而是要求:
请求 A 读取 balance=100。请求 B 读取 balance=100。两者都减去 80。两者都持久化为 20。系统从 100 元的余额中处理了 160 元的提款。
这是一个你可以推理的东西。
这是工作流变得特别强大的地方。
在对抗性审查之后,问:
For every credible issue you identified, write a regression test that fails against the current implementation.
Do not change the production code yet.
现在循环变成:
实现 → 攻击 → 复现 → 修复 → 验证
这比:
实现 → 看起来没问题 → 发版
要强大得多。
而且这让 Agent 证明它的批评。
如果所谓的 bug 无法复现,也许审查是错误的。
如果测试失败了,你现在既有证据又有永久的覆盖。
"审查这段代码"是非常不明确的。
我通过运行多个有针对性的审查来获得更好的结果。
Review this implementation as a hostile application security engineer.
Look for ways an attacker could abuse inputs, authentication, authorization, state transitions, serialization, file access, network calls, or resource consumption.
Review this as a distributed systems reliability engineer.
Focus on partial failure, retries, duplicate execution, idempotency, ordering, timeouts, race conditions, stale state, and recovery after crashes.
Review this as the maintainer of clients that depend on this API.
Look for undocumented behavior changes, ambiguous contracts, backwards compatibility problems, surprising defaults, and error semantics.
Assume this works correctly at 100 requests per day but fails badly at 10 million.
Find the scaling problems.
这些提示词约束了搜索。
而受约束的搜索往往比让模型模糊地"更努力思考"要好得多。
对抗性审查做了超出发现实现 bug 的事情。
它经常发现你的需求是不完整的。
假设 Agent 问:
如果两个用户同时更新对象会发生什么?
也许你从来没有指定过这一点。
删除这个资源是否应该级联删除关联记录?
这个端点是否应该暴露一个电子邮件地址是否已经存在?
恭喜,你的编程 Agent 刚刚伪装成实现细节走进了一个产品/安全决策。
这是对抗性审查更有用的特性之一。
它暴露了你的规范周围的负空间。
原始实现任务问:
用户让我构建什么?
对抗性任务问:
用户忘记告诉我什么了?
第二个问题可能更有价值。
因为"再检查一下"保留了原来的框架。
模型仍在试图验证解决方案。
对抗性审查改变了成功标准。
成功不再是:
实现看起来是正确的。
而是:
我找到了一种可信的失败方式。
这个小小的提示词工程转变很重要。
它基本上等同于软件的红队演练。
你不会让红队确认防御看起来合理。
你告诉他们想办法进去。
有一个重要的警告。
人工智能生成的批评并不自动正确。
一个足够努力的模型可以以令人印象深刻的自信找出想象中的 bug。
所以我把对抗性发现视为假设。
层次结构大致是:
具体的失败测试
可复现的执行路径
基于文档化行为的清晰推理
穿着安全工程师服装的直觉
列表中越靠下的发现,我给予的权重越小。
这也是为什么让 Agent 生成复现案例和测试如此有用。
它把叙述转化为证据。
如果你的工具支持,另一个有用的指令是:
Review the actual git diff and all directly affected code.
Do not rely on your memory of what you intended to change.
意图在审查时是危险的。
实现可能与 Agent 心中的实现 mental model 不匹配。
对于重大变更,我还让它检查相邻的代码和调用点。Bug 经常生活在边界上,而不是新写的函数内部。
对于有意义的变更,我越来越喜欢的 Agent 循环是这样的:
1. Understand the task.
2. Inspect the existing code.
3. Propose an implementation plan.
4. Implement the change.
5. Run relevant tests.
6. Perform an adversarial review.
7. Produce concrete failure cases for credible findings.
8. Add regression tests.
9. Fix confirmed issues.
10. Run the full relevant test suite.
11. Review the final diff again.
你可以把它直接放进 Agent 指令文件。
边际成本微乎其微。
价值可能是巨大的。
人工智能编程 Agent 在我们不再把它们当作具有单一连续思路的程序员时变得更有用。
它们可以是实现者。
然后是测试工程师。
然后是六个月后好奇是谁写了这段代码的维护者。
这些角色优化不同的事物。
而提高人工智能生成软件质量最便宜的方法之一,就是故意让模型反驳它写代码的那个版本的自己。
所以下次你的编程 Agent 宣布:
Implementation complete. All tests pass.
先别祝贺它。
告诉它试着摧毁它刚构建的东西。