项目复盘
不是更强的提示词,而是更稳的控制回路
一次 Agent 编程项目复盘
- 作者:Key of the Door(GitHub:Key-of-door)
- 版本:v0.1
- 版本日期:2026-07-11
- 许可:CC BY-SA 4.0
- 项目仓库:Key-of-door/code-risk-review-v0.1
- 项目 Release:v0.1(c5054ac)
- 配套材料:《Agent 协作控制回路:一个面向复现的最小实践框架》
Word 下载版:下载《不是更强的提示词,而是更稳的控制回路》v0.1
我一开始只想做一个轻量的代码风险审查工具,并借这个项目补足自己的实现能力。
项目叫 Code Risk Review v0.1。它只做 Python 静态分析,不运行用户代码,不保存用户源码,不承诺代码安全,不提供安全认证,也不替代人工审查、测试和交付判断。
它的目标很克制:在交付前,帮助用户发现当前规则范围内可识别的风险,并说明原因、边界和下一步建议。
后来让我意外的,不是工具本身,而是开发过程。
这个项目推进得异常顺利。规则增加、前后端接入、错误状态、测试、mutation 和最后的发布收尾,没有像我预期的那样逐渐堆成混乱。
起初我以为,只是因为 Agent 足够强,或者所有人都在用类似的方式开发。后来我才意识到,自己真正遇到的问题不是“如何让 Agent 写代码”,而是:
当 Agent 在不完整意图下拥有较高实现自由时,怎样防止它把产品悄悄改写成另一个东西?
我后来把这种现象称为 Agent 语义漂移。
它不一定表现为报错。
Agent 可能误解边界、补全没有说清的假设,甚至在代码能够运行、测试仍然通过、文档彼此一致的情况下,把产品实现成另一个东西。
方法不是预先设计出来的
我并没有在项目开始时准备一套完整方法论。
相反,它是在开发过程中,被一个个具体问题逼出来的。
我经常说:“好了,继续学习吧。”
表面上,我是在让 Agent 继续讲解概念、给出练习;实际上,我也会通过它的解释和我的回答,观察它是否仍然正确理解产品边界。
关键判断随后会被写入 API 契约、工程护栏或 README,避免它们只存在于对话上下文中。
我也会问:“感觉怎么样?”
这不是为了让 Agent 表态,而是为了让它暴露当前理解:
- 它是否把阶段性约束当成了永久原则
- 是否把前后端职责混在了一起
- 是否忘记“不运行代码、不保存源码、不承诺安全”等基本边界
- 是否在没有明确授权的情况下扩大了修改范围。
后来我意识到,这些看似随意的对话,实际承担了 理解检查点 的作用。
工程护栏文档就发生过一次这样的偏移。
它最初被理解成永久不变的工程宪法;经过讨论后才重新明确:其中既有长期原则,也有只服务于 v0.1 当前阶段的工程护栏。
这让我逐渐形成一个判断:
人必须保留产品定义权与解释权。
Agent 可以拥有较大的实现自由,但不能默认拥有重新定义产品的权力。
约束上层,放开实现层
这套做法不是把 Agent 锁成一段只能机械执行的脚本。
产品方向、用户承诺、职责边界、状态语义、隐私限制和单一事实源属于上层约束,必须由人明确决定。
接口契约、测试和 release 条件,则把这些判断落成可以检查的结构。
但在这些边界之内,Agent 可以自由选择具体实现路径:
- 函数怎样拆分
- AST 如何遍历
- 测试如何组织
- 局部逻辑怎样重构
- 在既有契约内,哪种实现更清晰、更稳妥。
这些细节不必由人逐行指定。
换句话说:
语义自由度要小,实现自由度可以大。
这有点像飞控系统。
飞行员给出意图,系统持续限制不可接受的状态。飞机不必沿着唯一轨迹飞行,但不能失去控制。
这里的“飞控”只是一个帮助理解的比喻,并不意味着我已经建立了严格的控制论模型。它只是让我更容易理解:约束 Agent,并不等于锁死 Agent 的能力。
四个收尾闭环
在项目收尾阶段,我们通过几个小闭环,把这套做法落成了可验证的工程证据。
1. source_size:字段语义到底是什么
source_size 原本存在“Python 字符数”和“UTF-8 字节数”之间的歧义。
如果这个字段用于输入限制和资源边界,UTF-8 字节数是更一致的定义。
于是,项目将 source_size 统一定义为源码按 UTF-8 编码后的字节数,并同步完成了:
- API 契约更新
- 中英文混合输入测试
- 边界行为验证
- 将实现故意改回字符数计算的 mutation。
在该提交对应的验证环境中,34 个测试通过,mutation checker 中的 9 项预设变异全部被测试杀死。
对应提交:14e746e
这份证据证明,现有测试能够阻止“UTF-8 字节数退化为 Python 字符数”这一已明确设计的语义退化。
它不证明所有与编码、输入大小或资源消耗有关的问题都已被覆盖。
2. HTTP 请求体限制:入口边界和业务边界不能混在一起
这里需要区分两类“输入过大”:
一种是 HTTP 请求体在读取前就超过入口上限,应返回:
413 / REQUEST_BODY_TOO_LARGE
另一种是请求已经进入业务层,但其中的源码超过分析上限,应返回:
SOURCE_TOO_LARGE
两者发生在不同层级,代表不同的失败语义,不能被合并成同一个错误。
项目随后在读取 rfile 前,根据可解析的 Content-Length 检查请求体大小,并补充了真实 HTTP 测试和禁用入口限制的 mutation。
在该提交对应的验证环境中,36 个测试通过,mutation checker 中的 10 项预设变异全部被测试杀死。
对应提交:4c5c256
这份证据证明,在当前实现和测试覆盖的 HTTP 输入路径中,带有可解析 Content-Length 的超限请求能够在进入 JSON 解析和源码分析前被拒绝。
它不自动证明所有传输编码、代理配置或部署环境都受到完全相同的保护。
3. filename:辅助元数据不能成为第二个事实源
上传文件时,前端只传递 File.name,后端只接受不包含路径信息的干净 basename,并拒绝:
- 路径片段
- 驱动器前缀
- .
- ..
- 非字符串输入。
filename 可以作为规则判断中的有限上下文,但不能让前端长出第二套风险判断逻辑,也不能仅凭文件名生成未经后端规则确认的风险事实。
这个闭环最终覆盖了:
- 前端 payload
- 后端输入校验
- HTTP 测试
- API 契约
- 对应的 mutation。
在该提交对应的验证环境中,40 个测试通过,mutation checker 中的 11 项预设变异全部被测试杀死。
对应提交:1d85c2e
这里守住的不只是“文件名是否合法”,而是:
风险事实由谁生成,谁没有资格重新定义它。
4. report_depth:披露深度不能改变分析事实
摘要和详情原本只是一个体验需求,但它很容易被实现成两套分析路径:
- summary 少分析一部分
- detail 多分析一部分。
- 如果这样实现,report_depth 就不再只是披露深度,而会开始改变产品事实。
我们先确定了一组不变量:
- 规则层始终生成完整风险对象
- report_depth 只在后端序列化阶段裁剪字段
- 风险命中、数量、等级、置信度、行动优先级和状态语义不得改变
- 前端不能补写未请求的原因、建议或验证步骤。
最终,summary 和 detail 的区别只存在于信息披露深度,而不存在于分析事实本身。
在该提交对应的验证环境中,44 个测试通过,mutation checker 中的 12 项预设变异全部被测试杀死。
对应提交:f030294
这个闭环也让我更明确地理解了 单一事实源:
每一类核心事实,都应有一个权威定义和生成来源。其他层级可以传递、裁剪、排序和展示,但不能重新定义它。
让测试不只检查代码能不能跑
项目中大量实现由 Agent 完成。
但我没有因此把产品解释权交给 Agent。
Agent 可以选择实现路径,却不能决定:
- report_depth 是否改变分析事实
- filename 是否成为风险事实源
- partial 是否可以被悄悄改成 user_error
- 某项阶段性约束是否可以被写成永久原则
- 测试通过以后,项目可以对外承诺什么。
这些边界必须由人定义,再由契约、测试和 mutation 固化。
测试长期保持全绿时,我反而开始担心:
它们会不会只是没有真正碰到关键退化点?
于是,我们开始故意破坏产品语义,而不只是破坏普通实现。
例如:
- 把 partial 改成 user_error
- 把 UTF-8 字节数改回字符数
- 移除 HTTP 请求体入口限制
- 关闭文件名校验
- 让 report_depth 失去响应裁剪行为
- 让字符串中的 eval( 被误判为真实函数调用。
普通 mutation 测试通常会问:
实现被改坏以后,测试会不会失败?
这里还需要再问一层:
产品承诺被悄悄改写以后,测试会不会失败?
我暂时把这种做法称为语义边界变异测试(semantic-boundary mutation testing):
- 写出一条具体产品承诺
- 找到它在代码中的实现位置
- 设计一个最小的“背叛性修改”
- 验证现有测试能否捕获它
- 记录这份证据证明什么,以及不证明什么。
这不是证明测试能发现所有问题。
它只证明:
针对这些被明确设计出来的语义退化,测试已经不只是检查函数能否运行,而开始具备阻止产品变形的能力。
最后,项目在干净克隆环境中通过了 44 个测试,12 项预设变异全部被测试杀死。
release checklist、仓库卫生检查、文档一致性检查和 Git 历史,共同构成了发布前证据。
对应提交:4c4fe7e
公开准备阶段,仓库又在隔离克隆中完成了历史隐私重写,并补充 Apache-2.0 许可证、SECURITY.md、Python 3.10 的 Windows/Ubuntu CI,以及 v0.1 规则数量和文档语义对齐。
干净克隆验证对应的候选提交是 7496b05。此后只新增 release evidence 和发布顺序措辞校准,没有改变分析实现,也没有扩大 v0.1 的规则范围。最终 v0.1 标签与 GitHub Release 指向 c5054ac;该提交的 Windows 与 Ubuntu CI 均通过 44 个测试和 12 项预设变异验证。
最终发布:v0.1(c5054ac) · Release Evidence
测试之外,还需要审查语义 diff
测试只能阻止已经被写成断言的退化。
但现实中,一次修改也可能引入尚未被契约和测试描述的新行为。
因此,在较大的改动完成后,我还会重新检查:
- 本次 diff 是否改变了产品的对外含义
- 是否产生了新的产品承诺或默认行为
- 某类核心事实是否被移动到了另一个层级
- 是否出现了第二个事实源
- 是否把阶段护栏写成了长期原则
- Agent 是否修改了原本未被授权修改的范围
- 当前实现是否仍然位于允许的实现自由区内。
我把这种检查称为 语义 diff 审查。
它不是要求人重新逐行设计代码,而是检查:
代码的变化,是否改变了产品是什么、用户会观察到什么,以及谁拥有事实解释权。
如果语义 diff 暴露了新的边界,下一步不应只是“这次人工记住”,而应决定是否需要补充契约、测试、mutation 或工程护栏。
与相邻工作的关系
在完成这个项目的主要工程闭环后,我开始寻找外界是否存在相似实践。
我发现,这套做法并不是发生在完全空白的方向上。
Spec-Driven Development、human-in-the-loop software agents、coding-agent harness engineering、Acceptance Test-Driven Development 和 mutation testing 等方向,都在处理相邻问题:
- 如何把意图外化为规格
- 如何让人在关键阶段保留控制
- 如何通过确定性检查点约束 Agent
- 如何验证测试是否真的能够发现退化。
其中,Disciplined Agentic Engineering(DAE) 是目前与本项目高度相邻的公开实践之一。
DAE 已经将 charter、行为规格、人工批准、检查点、ATDD 和 mutation testing 工程化为面向 Claude Code 的工作流和插件。
它与本文存在明显汇合:
- 都不认为提示词本身足以维持长程任务中的约束
- 都强调人保留关键决策
- 都将规格、测试和 mutation 作为控制机制
- 都试图防止 Agent 在长程实现中偏离原始目标。
本文不主张这些组成部分由本项目首次提出。
二者的关注重心也并不完全相同。
DAE 更接近一套较完整、工具化的纪律化 Agent 工程流水线;本文则更集中于一个较窄的问题:
当 Agent 在不完整意图下拥有较高实现自由时,如何由人保留产品解释权,并将具体产品承诺转化为可检查的语义边界、背叛性 mutation 和具有明确证明范围的版本证据?
本文尤其关注:
- 产品解释权
- 长期原则和阶段护栏的区分
- 单一事实源
- 理解检查点
- 语义 diff
- 面向产品承诺的语义边界 mutation
- Git 决策证据
- 一项验证证明什么,以及不证明什么
- 失败如何回灌下一轮控制回路。
这些差异目前只由一个单人、单项目案例支持,尚不能证明它们能够稳定迁移到其他项目和团队。
相关工作链接
这不是答案,而是一个面向复现的实验基线
我暂时把这套做法称为 Agent 协作控制回路:
人定义产品 -> 外化长期原则、阶段护栏与接口契约 -> 理解检查点暴露 Agent 的当前理解 -> Agent 在实现自由区内工作 -> 人审查语义 diff -> 测试与 mutation 验证已知退化 -> 文档、Git 与 release evidence 保存证据 -> 失败报告补强观测点 -> 回到下一轮理解检查点
这只是一个单人、单项目样本。
它不能证明对所有模型、团队或项目都有效,也不适合所有任务。
对于低风险、小规模、无需交付的一次性任务,完整流程可能过重。
当使用者无法判断产品边界,也不准备参与关键决策时,文档、测试和检查点同样不能自动生成正确判断。
但我认为,它碰到了一个值得继续验证的问题:
当 Agent 不只是补全代码,而会主动解释需求、补全语义、生成测试、同步文档并修改系统时,我们如何防止它把产品悄悄写成另一个东西?
我把项目、文档、测试、mutation 和 Git 历史公开出来,不是为了宣布答案,也不是为了证明它是一套普适方法。
我希望它首先成为一个可以被检查的实验起点:
- 别人可以复现
- 可以指出它在哪里失效
- 可以提交相反证据
- 可以比较不同 Agent、项目和团队中的成本与收益
- 可以记录哪些控制机制真正生效,哪些最终只成为形式主义。
如果你在自己的项目中尝试了这套做法,欢迎同时记录:
- 它在哪里有效
- 在哪里失效
- 哪些偏移仍然没有被提前发现
- 哪些流程产生了不必要的成本
- 你是如何发现和修复这些缺口的。
成功案例说明控制回路在哪里生效。
失败报告则说明,它接下来应该在哪里继续生长。
具体的操作模板、理解检查点、约束分类表、语义边界 mutation 设计方法、release evidence 模板和 failure report 模板,将在配套的结构化方法材料中单独发布。
许可证说明
本文正文采用 CC BY-SA 4.0 协议发布。
分享、转载、翻译或改编时,请按照许可条款提供适当署名、许可链接并注明修改;在合理可行时,也请保留原文链接和版本日期。基于本文正文的改编内容应使用相同协议共享。
配套的结构化方法材料和核心术语文档是独立作品,采用 CC BY-NC-SA 4.0,以各自文件中的许可声明为准。
转载、引用或改编不代表作者认可转载方、改编方或其产品。请勿将阶段性结论包装为通用定论。
项目名称、Logo 和官方版本标识不包含在上述 Creative Commons 许可范围内;除非另有书面授权,不授予商标、背书或官方代表权。