在 dsh 仓库里扒到的宝藏工作流:详解 .agents/notes 决策沉淀系统

最近,DeepSeek 开源的 deepseek-harness (dsh) 实在是太火了,除了完善的harness 工程,其一切皆插件的原则也是值得我们学习的架构。而我在翻看源码的时候,翻到根目录下的 .agents/notes 时,发现了一套很有意思的设计。

用 Claude Code、Cursor 或者其他 Agent 写项目时,大家大概率都遇到过这几个抓狂的场景:

  1. AI 的"短期记忆" :昨天在新 Session 里跟它反复对齐的架构约束,今天换个窗口它就忘光了,又开始按自己的套路写。

  2. 反复扯皮 :遇到某个边界问题,AI 兴冲冲给出一个"显而易见"的解法,而这个解法其实团队三个月前踩过坑且明确否决过。

  3. 文档写完即腐烂:传统的 RFC 或设计文档,写的时候全是"计划支持 X"、"预计下周重构 Y",代码一合并就没人管了,半年后全成了误导 AI 的过时垃圾。

dsh 仓库里这套 .agents/notes 机制,本质上就是给 Agent 和人类团队搭的一套随代码一同演进的"外置架构记忆" 。它通过目录流转和自动化脚本门禁,把"当初为什么这么选、放弃了什么"变成了可执行的契约。

一、 它是怎么划分 Note 的?

dsh 中,所有 Note 文件的路径规则非常简单明确:

Plaintext 复制代码
{lifecycle}/{class}/YYYY-MM-DD-topic-title.md

整个目录由 生命周期(顶级目录)领域分类(二级目录) 交叉构成:

Plaintext 复制代码
.agents/notes/
├── proposed/       # 方案提案期(写给未来:准备做,但还没落地)
├── implemented/    # 代码落地期(写给现在:已经合入,真实代码的现状)
├── rejected/       # 方案否决期(写给后来者:被枪毙的路线,附带否决理由)
├── archived/       # 过时封存期(写给历史:陈旧的稳定决策,只读冻结)
├── AGENTS.md       # 给 Agent 读的行为指导
└── README.md       # 目录规范契约

1. 四个生命周期(Lifecycle)

  • proposed/ :方案评审期。全部使用将来时态,记录痛点、预期方案、验收标准(Acceptance criteria)与潜在风险。

  • implemented/ :已上线生效。必须全部使用现在时态 ,描述当前代码的真实逻辑,严禁残留"计划"、"预计"等推测性字眼。

  • rejected/ :被明确否决的方案。在 Header 里写清楚一句话拒绝原因,永久保留。

  • archived/ :通过脚本迁入的陈旧决策,迁入后计算哈希并只读锁定,防止干扰日常检索。

2. 六个封闭分类(Class)

为了防止大家建出一堆 temp/misc/ 之类的模糊目录,dsh 用脚本直接锁死了二级分类,只允许这 6 种:

  • architecture:核心抽象、包依赖关系、跨模块通信契约。

  • feature:面向用户或模型的新能力、新 API。

  • bug-fix:疑难缺陷修复与事后复盘(Postmortem)。

  • simplification:删代码、收敛抽象、降低复杂度的重构(只做减法,不加新功能)。

  • process:CI 门禁、发版工具链、依赖管理规范(非业务运行时代码)。

  • testing:测试基础设施、单测/集成测试用例规范。

二、 为什么这么设计?几个很妙的细节

翻看他们的规范和 CI 脚本,能发现几个特别克制但非常实用的设计考量:

1. 为什么不用 INDEX.md

很多知识库喜欢在根目录维护一个总索引表。但在多分支、多 Agent 并行开发时,每次提 PR 大家都去改这个 INDEX.md,极其容易造成 Git Merge Conflictdsh 直接扔掉中心化索引,路径本身就是天然分类,找文档直接靠目录层级或全局全文搜索。

2. 状态变更靠 git mv,而不是改文件里的 Tag

当一个提案被实现时,操作是直接把文件从 proposed/ 移动到 implemented/。这样在提 PR 和看 Commit Log 时,一眼就能看出"这次变更让哪个提案正式落地了"。同时,Agent 检索当前代码规范时,只需扫描 implemented/,省去解析大量元数据的 Token。

3. 强制记录 Alternatives considered(手下败将)

这是整套体系最精彩的地方。dsh 强制要求每篇 Note 必须写"被否决的备选方案以及为什么否决"。

代码本身只能告诉你系统"现在是怎么跑的",但讲不清"为什么不用更简单的做法 B"。一旦把"做法 B 为什么会造成内存泄漏/安全穿透"白纸黑字记在 Note 里,后续接手的同事或者 AI 就不会再脑抽跑去提一个做法 B 的 PR。

4. 转正时必须抹除"计划语态"(Kill Spec-speak)

提案合入 implemented/ 时,必须把 ## Proposal 改写为 ## Decision,把将来时改成现在时,同时删掉验收标准,换成得失分析(## Consequences)。这样避免了文档里永远留着一堆"未来打算做某某事"的空话。

5. 靠 CI 脚本做门禁,不靠人的自觉

光靠口头约定,时间一长格式肯定会变形。dsh 写了几个非常直接的 CI 检查脚本:

  • verify-agent-note-format.ts:检查文件头、强制校验必须有 ProblemAlternatives、只要在 implemented/ 里搜到 ProposalAcceptance criteria 直接 CI 报错。

  • agent-note-tree.ts:只要发现 6 个预设分类之外的目录直接拦截。

三、 落库后的 Note 怎么跑起来?

平时的人机协作流转可以概括为很顺畅的一条线:

Plaintext 复制代码
[开工前] -> Agent 检索 implemented/architecture 与 rejected/ (避开暗坑)
[定方案] -> 在 proposed/{class}/ 起草 Note (理清 Alternatives)
[写代码] -> 完成业务实现与测试
[提 PR]  -> git mv 到 implemented/{class}/ + 改写为现在时事实
[走 CI]  -> 门禁脚本自动校验格式与时态
[过时后] -> 调用 skill 自动化归档到 archived/

四、 中文模板与实战案例

1. 翻译后的标准 Note 模板

Markdown 复制代码
# Agent Note: <简明标题>
Status: implemented

## 问题背景 (Problem)
说明此前系统存在的问题,解释促成此次技术决策的初始动因。

## 决策现状 (Decision)
使用现在时态客观陈述当前已交付代码的实际设计与运行机制。

## 技术细节 (Technical Details)
记录当前代码的实际接口契约、时序逻辑或实现要点。

## 备选方案与否决原因 (Alternatives considered)
- **<备选方案 A>** --- 已否决: 记录放弃该方案的关键技术考量(如内存泄漏、安全穿透等)。
- **<备选方案 B>** --- 已否决: 记录为什么不选社区流行库(如维护停滞、ABI 冲突等)。

## 代价与后果 (Consequences)
客观记录本次决策带来的收益与妥协代价(比如:换取了类型安全,但每次新增接口多了几行样板代码)。

2. 真实演练示例:跨进程 IPC 通道注册机制

以客户端开发为例,落地后的 Note 类似这样:

文件路径:.agents/notes/implemented/architecture/2026-08-17-strict-ipc-channel-registry.md

Markdown 复制代码
# Agent Note: 严格类型化的 IPC 通道注册机制
Status: implemented

## 问题背景 (Problem)
此前渲染进程与主进程通信采用动态字符串拼接方式,缺乏编译期类型校验,窗口销毁后未能及时注销监听器,导致多窗口切换时频繁触发内存泄漏与安全告警。

## 决策现状 (Decision)
所有 IPC 通信统一收口在 `src/main/ipc/registry.ts` 中显式注册。Preload 层仅通过 `contextBridge` 暴露由强类型白名单定义的 API 契约,严禁向前端暴露泛型透传的 `ipcRenderer.send(dynamicChannel)` 接口。

渲染进程窗口在触发 `before-unload` 时,由封装的 `useIpcListener` Hook 自动执行反注册流程。

## 备选方案与否决原因 (Alternatives considered)
- **封装全局泛型透传接口 `invoke(channel, ...args)`** --- 已否决: 虽然能减少样板代码,但完全绕过了安全沙箱的审查机制,且无法在主进程进行精确的生命周期回收。
- **引入第三方 RPC 封装库(如 electron-trpc)** --- 已否决: 该依赖引入了多余的 WebSocket 抽象层,增加了打包体积,且与现有的 Worker 线程模型不兼容。

## 代价与后果 (Consequences)
彻底根除了跨进程通信导致的内存泄漏与未授权 API 调用。代价是每新增一个 IPC 接口需要额外编写 5 行类型声明与胶水代码。

五、 如何在自己的项目中轻量引入?

如果想在自己的项目里试试这套体系,不必一开始就抄全套脚本,推荐分步跑通:

  1. 建目录 :在项目根目录建好 .agents/notes/{proposed,implemented,rejected,archived}

  2. 立规矩(Prompt 注入) :在项目的 CLAUDE.md.cursorrules 或 Agent 系统提示词里加上一句: "修改核心架构或公共契约前,先查阅 .agents/notes/;做出非直觉的技术决策时,必须在同一个 PR 中补充/更新对应 Note"

  3. 加个轻量 Pre-commit:写个几十行的小脚本,在 Git Hook 里校验一下 Note 文件头格式和时态,防止写跑偏。

  4. Code Review 时多看一眼:Review PR 时除了看业务逻辑,顺带看一眼 AI 是否把"否决的备选方案"写到位了。

相关源码出处参考

对底层实现感兴趣的朋友,可以直接看 dsh 仓库里的这几个文件:

相关推荐
iaku37 分钟前
Prompt 不是玄学:写给前端的 Prompt 工程指南
前端·人工智能
武子康38 分钟前
从 Pi 学习设计自己的 Agent Harness:一条可验证的垂直生产线
人工智能·llm·agent
ppshuX39 分钟前
Tool、Skill、MCP、Plugin,到底什么关系?
agent
喜欢睡觉42 分钟前
从"送花"讲懂 JavaScript:对象、数据类型与代理模式
前端
渣波43 分钟前
NestJS 企业级后端架构实战:从核心代码到工程化思维的深度重构
前端·typescript·nestjs
BreezeJiang43 分钟前
别再背工厂模式了:NestJS 第一行代码就是它的工业级落地
前端·javascript
光影少年43 分钟前
RN 常见性能问题:JS卡顿、UI卡顿、桥接通信耗时
前端·react native·react.js
liuxiaocheng1 小时前
文本生成的进阶:generateText / streamText 里迟早会撞上的东西
前端·后端·ai编程
渣波1 小时前
深度解析工厂模式:从蜜雪冰城到 NestFactory,彻底搞懂“创建与使用分离”
前端·javascript