当 Agent 进入中大型项目:我为什么做了一个“决策治理”Skill


从 DeepSeek Harness 的 .agents/ 目录,到一个可以真正落地的 Agent Notes Governance Skill。

很多人第一次让 AI 参与写代码时,关注的是一个很具体的问题:它能不能把函数写出来,能不能把测试补上,能不能少犯几个语法错误。

但当项目从几十个文件长成几百个模块,从一次性脚本变成持续迭代的产品,真正困难的事情会换一批:为什么当初选择这个接口?为什么没有采用另一个方案?谁依赖这个行为?这个删除会不会伤到某个动态消费者?几个月后,新的 Agent 如何知道哪些决定已经落地,哪些只是讨论?

代码告诉我们“现在是什么”,测试告诉我们“什么不能坏”,任务看板告诉我们“接下来做什么”。可是,很多高影响决策最重要的部分——为什么这样做,以及为什么放弃其他可能——往往只留在一次聊天、一段 PR 评论,或者某个已经被压缩的上下文里。

我做 agent-notes-governance 这个 Skill,正是从开发和持续迭代手头几个长期项目的需求出发,尝试解决一个问题:让 Agent 在复杂项目里工作时,不只会改代码,还能把关键决策沉淀成可审阅、可验证、可追踪的项目资产。

项目参见Github:suye0620/agent-notes-governance

我从 DeepSeek Harness 的 .agents/ 看到了什么

DeepSeek Harness 的仓库给我一个很强的启发:.agents/ 不是一个“给 AI 看的杂物目录”,而是一层项目级的 Agent 协作基础设施。

在它的结构里,.agents/ 至少承担两类职责:

  • .agents/notes/:保存影响代码库的设计决策、提案、缺陷修复和过程约束;
  • .agents/skills/:把重复的协作流程包装成可调用的技能,例如归档 Agent Notes、代码审查、文档同步、推送前检查等。

这两个目录放在一起,形成了一个很有意思的闭环:Notes 保存“为什么”,Skills 负责“如何稳定地执行”。

更重要的是,DeepSeek Harness 没有把所有 Note 简单堆在一个 DECISIONS.md 里,而是用路径表达状态和类别。活跃的 Note 有 proposedimplementedrejected 等生命周期;历史记录进入 archived;架构、缺陷修复、简化、流程、测试等决策又有自己的分类。

这看起来只是目录设计,实际上解决了三个长期问题:

第一,当前决策和历史决策不会混在一起。阅读者可以先看 active tree,知道今天什么仍然有效,再按需进入归档寻找历史背景。

第二,决策类型有边界。架构选择和 bug fix 的写法、审查重点、未来价值并不相同,把它们分开后,Agent 更容易按对的方式处理。

第三,归档不是“把旧文件丢进一个 archive 文件夹”。DeepSeek Harness 的规则强调:真正有历史价值的 Note 要冻结;完整的语言版本和 sidecar 要一起移动;部分 supersession 需要保留并交叉链接;归档后的内容不能再被随意改写。

这背后是一种很朴素但很重要的认识:复杂项目的历史不是垃圾,它是下一次决策的输入。

Agent 需要的不是更多提示词,而是更清晰的边界

长期项目里的 Agent 协作,最容易陷入一个误区:用更长的甚至反复的提示词,试图告诉 Agent 所有规则。

但微小幻觉产生的小失误也会在多轮对话中不断放大,进而演变成问题或者BUG。

但提示词无法替代项目内的事实,也无法替代一个可检查的状态机。真正稳定的做法,是把职责分层:

  • 当前代码和测试决定运行时事实;
  • API 文档决定对外使用方式;
  • schema 或架构合同决定结构性不变量;
  • Agent Note 保存决策理由、替代方案和后果;
  • 任务系统保存执行顺序;
  • Skill 把重复检查和操作变成流程。

DeepSeek Harness 的 .agents/notes/AGENTS.md 里有一句很值得借鉴的规则:每次新增 Agent Note,都要做 supersession check,检查是否已经存在覆盖同一决策或机制的旧 Note。它把“不要重复造一个看似新的决策”从一句提醒,变成了每次写 Note 都要经过的动作。

同样,dsh-archive-agent-notes 并不按照“文件太旧”或“字数太长”来归档,而是判断一条决策是否仍然有未来价值;dsh-pre-push-checks 也不是机械地跑全套测试,而是根据 outgoing diff 选择最小但足够的证据。

这给我的启发是:好的 Agent 工程,不是让 Agent 变得无所不知,而是让它知道什么事实由谁负责、什么动作必须经过哪一道门

当前 Skill 做了什么,又刻意没有做什么

当前这个 Skill 不是 DeepSeek Harness 的复制品。它是一个更小的 v1,先把最有普适性的部分抽出来:用 Agent Note 记录高影响决策,并用脚本保证结构不会在迭代中悄悄失真。

它的基本目录是:

.agents/notes/
├── proposed/
├── implemented/
├── rejected/
└── archived/

每条 Note 采用固定的元数据和章节,包括:

  • StatusGovernanceDate
  • 决策类型、影响等级、负责人;
  • SupersedesSuperseded by
  • Problem、Proposal/Decision、Constraints and invariants;
  • Alternatives considered、Consumer impact、Consequences;
  • Evidence 和 Acceptance criteria。

这里的核心不是“表格填得漂亮”,而是逼自己回答几个经常被跳过的问题:

这个问题是真实存在的吗?

哪些消费者会被影响?

哪些关系必须保持不变?

我拒绝了什么替代方案,为什么?

什么可观察证据能证明它已经完成?

Skill 里有两个标准脚本。new_agent_note.py 用于生成统一模板,避免每个 Agent 自己发明格式;validate_agent_notes.py 则检查目录、状态、日期、决策类型、章节、引用路径和生命周期关系。

这个项目自己的开发过程也验证了一个事实:校验器本身就是产品的一部分,不能只写出来,必须被反向攻击。

最初的校验器曾经会把正文里普通的一行 Status: implemented 当成顶部元数据;文件名日期和正文日期不一致时也能通过;引用字段甚至可以指向仓库外的普通文件;旧 Note 和 successor 没有双向链接时,校验器也不知道。

这些问题不是靠“再提醒 Agent 小心一点”解决的,而是通过回归测试锁住:正文里的 metadata-like 文本不能覆盖头部;非法决策类型必须失败;空章节必须失败;引用必须留在 Note 树中;supersession 必须双向一致,并且只有归档旧 Note 才能拥有 successor。

换句话说,这个 Skill 的第一批经验不是“模板写得多漂亮”,而是:每一个曾经被误判的边界,都应该变成下一次不会再犯的测试。

一个实际的使用场景:缓存所有权

假设一个项目里出现了这样的变化:缓存模块有两个写入者,它们都可以发布结果,但没有统一的顺序和来源约束。短期内功能似乎还能跑,长期却会出现很难复现的兼容问题。

这不是普通的格式调整,也不是一次机械重命名,而是一个需要记录的架构决策。可以先生成一条 proposed Note:

uv run --isolated --python 3.12 python \
  .codex/skills/agent-notes-governance/scripts/new_agent_note.py \
  --root .agents/notes \
  --status proposed \
  --type architecture \
  --slug result-cache-owner \
  --title "Result cache ownership" \
  --scope "cache module and public API" \
  --owner "project maintainer" \
  --impact high

然后在 Note 里明确写下:采用一个 cache controller 作为唯一写入者;所有 cache write 必须经过 controller;读者只能看到 controller 发布的值;保留多写入者作为替代方案,但因为来源和顺序不可解释而放弃。

注意,这条 Note 不是任务清单。它不应该写成“新增 controller、修改三个文件、补两个测试”。那些属于执行计划。Note 要保存的是在半年后仍然有价值的判断:为什么所有权必须集中,哪些不变量不能破坏,谁会受到影响,以及什么证据能证明决策已经落地。

实现完成后,再把 Note 迁移为 implemented,补上真实的测试、代码路径和验证结果;未来如果这个缓存策略被新的持久化方案替代,就创建 successor,双向链接,再把旧 Note 归档。这样,Agent 看到的不是一串失去上下文的 commit,而是一条能解释演进的决策链。

什么时候应该使用这个 Skill

我建议在下面几类变更前使用它:

  • 修改公共 API、schema、持久化格式或兼容边界;
  • 选择算法、依赖、缓存所有权或性能策略;
  • 改变安全姿态、生命周期或资源管理方式;
  • 删除接口、替换 provider、移除兼容路径;
  • 多个 Agent 将并行修改同一个共享契约;
  • 这次决定一旦错了,回滚代价会很高。

也不要把它用成“所有事情都写一条 Note”的仪式。格式化、机械重命名、局部拼写修正和一次性实验,不值得留下长期决策记录。

一个简单的判断方法是:六个月后,如果另一个维护者问“为什么不能直接改掉它”,你是否希望项目里有一份答案? 如果答案是肯定的,就应该考虑写 Note。

如何把它用好:不是填完一张表,而是跑完一条证据链

这一节真正想说明的,不是“写 Note 有五个固定动作”,而是:一个高影响决策,只有经过一条完整的证据链,才不会在 Agent 协作中变成一句没有上下文的口号。

沿用前面的缓存所有权例子。两个模块都在写缓存时,最危险的不是少写了一行代码,而是大家对“谁拥有写入权”有不同理解。Skill 的价值,就是把这个隐含冲突逐步变成一份能讨论、能验证、能追溯的合同。

1. 先判断:这是不是值得留下的决策

不是每次改动都需要 Note。格式化、机械重命名和一次性实验,记录下来只会增加噪声。真正适合使用 Skill 的情况,是改动公共 API、schema、兼容边界、算法、安全策略、缓存所有权,或者一个错误决定会带来很高回滚成本。

这一步的产物不是一份模板,而是一个明确的问题:如果六个月后有人问“为什么不能直接改掉它”,项目是否需要一份可以引用的答案? 如果需要,就创建 proposed Note;如果不需要,普通任务记录就够了。

2. 把隐含冲突写成决策边界

不要一上来写“新增 cache controller”。先写清楚问题:当前有两个写入者,它们可能发布不兼容的值,现有代码和测试无法解释顺序与来源。然后再写选择:让一个 controller 成为唯一写入入口,因为缓存所有权必须可追踪;多写入者作为替代方案,因为它会保留来源和排序歧义。

这一步对应 Note 的 ProblemProposalAlternatives considered。它的作用是防止 Agent 把“我想到的实现方式”误写成“项目真正要解决的问题”。问题边界不清楚,后面的代码很可能只是局部修补。

3. 把影响面和不变量画出来

决策确定后,继续问:哪些写入者要迁移?哪些读取者必须保持兼容?配置、示例、集成测试和动态查找会不会受到影响?然后把不能被破坏的关系写成不变量,例如“每一次缓存写入都必须经过 controller”,“读取者只能观察 controller 发布的值”。

这一步对应 Consumer impactConstraints and invariants。消费者地图避免“只搜到一个调用方就开始删除”;不变量则让评审和测试有明确靶心。消费者说明谁会受影响,不变量说明改完以后什么必须仍然成立。 两者缺一不可。

4. 先让提案接受挑战,再开始不可逆实现

proposed 不是“已经做完”,而是“方案已经足够具体,可以被挑战”。评审者需要追问:唯一写入者是否真的服务所有消费者?保留多个写入者是不是更便宜?这个不变量能不能被测试击穿?有没有重复的旧 Note?

只有当方案经过讨论,Agent 才进入实现。这样做的价值,不是让流程变慢,而是把最便宜的争论放在最早的阶段。代码已经改完之后才发现方案漏了一个动态消费者,代价通常远高于在 proposed 阶段补一句约束。

5. 用证据推动状态变化,而不是用感觉升级状态

实现完成后,Agent 需要回到 Note,填写真正的 EvidenceAcceptance criteria:哪些测试通过了,哪些调用方已经迁移,哪些文档或配置已经同步,哪些场景证明不变量仍然成立。然后把 Proposal 改写成描述当前事实的 Decision,把 Note 从 proposed/ 移到 implemented/

这里最容易犯的错,是把“代码开始写了”当成“决策已经实现”。当前 Skill 明确要求:没有直接验证证据,就保持 proposed。因此,状态变化不是进度条,而是证据链的结论。

6. 新决定要建立演进链,不要覆盖历史

如果半年后缓存策略被持久化队列替代,不要直接把旧 Note 改成新方案。创建 successor,写清新方案如何取代旧方案,双向链接两条 Note,再把旧决策移入 archived/。如果只是部分替代,就保留两条 Note,并说明各自仍然有效的边界。

这一步保护的是项目记忆的可信度:读者能看到当时为什么选择 A、后来什么条件促成了 B,而不是看到一份被事后改写、看起来永远正确的历史。归档不是删除,supersession 也不是覆盖;它们是让决策随着系统演进而保持可解释

所以,真正“用好”这个 Skill,不是把八个章节都填满,而是完成这条闭环:识别值得记录的决策 → 说清问题和取舍 → 找全消费者 → 写出不变量 → 用验证证据关闭提案 → 用 successor 管理未来变化。 Note 只是载体,证据链才是方法。

这不只是给 Agent 用的

虽然名字里有 Agent,但 Agent Notes 首先服务的是项目的长期记忆。

人类维护者可以通过它快速知道一个决定的范围和代价;新加入的 Agent 可以在动手前读取当前权威;代码审查者可以从 Alternatives、Consumer impact 和 Acceptance criteria 判断这是不是一个完整方案;未来的维护者也可以沿着 supersession 链理解项目为什么从 A 走向 B。

AI 让代码生产更快之后,项目真正稀缺的东西反而变成了上下文、边界和判断。没有决策记录,Agent 只是更快地重复遗忘;有了决策记录,Agent 才可能参与一个持续数月甚至数年的复杂工程,而不必每次从零猜测。

这就是我做这个 Skill 的动机:不是把 Agent 变成一个更会输出代码的自动补全工具,而是让它在复杂项目里拥有一种可审阅、可验证、可继承的工作方式。

如果你的项目已经从“做出来”进入“长期维护、不断替换、多人和多 Agent 协作”,那么下一步值得补上的,可能不是更多提示词,而是一个能回答“为什么”的 .agents/


延伸阅读


文章作者: 苏烨
文章链接: /article/19/
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 PyGeek!
  目录