Agent 不是失忆,是找不到入口——大型项目文档分层设计(一)

本文是「Agent-first 大型项目文档分层」系列的第一篇。素材来自某企业级支付审批系统 7 个月的 AI 协作开发:我和 Claude Code、Codex、Kimi 一起写代码、写测试、写文档。系列只讲文档这条腿,测试方法论另开专题。

一、两次封板,和一份早就写好的规范

9 月 16 日,某企业级支付审批系统在同一天打了两个版本的 tag。这两个版本的更新日志和交接说明当时都没有写。

两天后,仓库里出现了一个提交,一次做了三件事:新建一份独立的封板规范(封板:冻结版本、跑全量测试、打 tag、出包);在入口文件里加一张「操作 → 必读」表;补写这两个版本漏掉的 release 文档------给业务用户的更新日志和给运维的交接说明。表上方的引言把教训写进了给 Agent 的入口规则:「封板未读全量定义、漏写 release 文档」。

可「全量」的定义并不是这一天才有的。规范早就有:「全量测试固定包含以下六套,缺一不可」早已写在项目测试规范里;文档总入口的目录说明提到过「六套全量纪律」;更早两次封板的变更计划也都要求过六套测试证据。规范本身是够用的,缺的是从入口走到它的那条路。

再看立规之前的入口文件------每次会话都会被自动加载的那份 Markdown------410 行,关于发版只有 Git 分支约定里夹着的一段打 tag 规则,「全量」「封板」两个词一次都没出现。从入口出发,没有任何一处路标把封板引向那份测试规范。

新的封板规范写了六套全量定义、封板 checklist 和 release 两件的必含项;入口文件那张表的第一行是这样的(节选,已脱敏):

操作 必读规范
封板 / 出包 / 打 tag / 说「全量绿」 封板规范(六套全量定义 + checklist + release 两件模板)

注意第一列最后一项:说「全量绿」。说一句话也被登记成了一个操作------Agent 想说出这三个字之前,必须先去读那份规范。(第二列写的是「模板」,规范正文其实是让 Agent 照一份历史文档仿写,这个坑留到第③篇。)截至 10 月 14 日,立规后 v2.0.5 至 v2.3.1 这 9 个版本,更新日志和交接说明每版齐全,第②篇细说。

所以「Agent 老是忘事」这个说法找错了对象。Agent 没有失忆,它本来就没有跨会话的记忆,每次会话都是一个从入口走进来的新人;问题在于入口没有把它带到该去的地方。这个系列的核心论点只有一句:在 Agent 写大部分代码的项目里,文档的第一读者是 Agent,文档体系本质上是一套给 Agent 用的路由系统。 本篇讲总模型,后三篇分别讲路由、寻址和生命周期。

二、文档的第一读者换了

先看量级。这个项目 7 个月里共 3614 次提交,触及文档或入口文件的有 1373 次,其中至少 547 次带着 Agent 署名(按署名统计的下限)。文档目录下截至 10 月 14 日有 832 份 Markdown、51 个结构化变更 bundle(一次变更的需求、设计、实现记录、用例同放一个目录,称为 bundle),还有 301 份早期的单文件 plan。

这些文档谁在读?每次会话开始,Agent 自动加载入口文件,开工前按入口指引读规范,进入模块时翻计划和交接记录。我的立场是:文档现在首先写给 Agent,人读的是 Agent 在决策点上产出的东西,而不是从头通读。

开场那句论点要落到 Agent 的特性上:整套机制都从下表推出来。后文每条原则都会标出它对应哪一个特性。

Agent 特性 对文档的要求 对应机制
无跨会话记忆 每次从入口进,入口必须是路由表 L0 + 「操作 → 必读」表
上下文有限且有成本 token 是第一成本,按需下钻 L0--L3 分层、薄索引
按字面执行、不会意会 状态、替代关系、适用范围显式写出;「建议参考」改为「执行前必须先读」 状态机、Replaced by、必读表
能 grep / glob / 跑脚本 命名可模式匹配、元信息可解析、规则可 lint 编号体系、元信息块、doc-lint
同时是主要作者 约定靠自觉必漂移,必须机器强制 hook / CI 校验
多个 Agent 并存 单一入口;教训进 repo 而非个人记忆 唯一入口文件(AGENTS.md)

后两行多说一句。「同时是主要作者」:写规范的是 Agent,违反规范的也可能是下一次会话里的同一个 Agent,所以能机器检查的约定最终都要变成检查(hook / CI 是通用形态,我们项目选了另一种强制方式,留到第④篇)。「多个 Agent 并存」:Claude Code、Codex、Kimi 轮流上,只存在某个 Agent 个人记忆里的教训,换一个 Agent 就等于没有。

代码、测试、文档,三条腿

代码、测试、文档三者同等重要,缺一不可。 代码由 Agent 写,测试证明代码是对的,文档告诉 Agent 该怎么写、写完往哪回写。三者互相咬合:路由表把 Agent 引向测试规范;用例索引把测试反向关联到需求;doc-lint 就是「文档的测试」。开场那次事故里,测试和测试规范一直都在,缺的是文档体系里把 Agent 引到测试规范面前的那一跳。

三条推论

推论 1:人的角色从读者变为裁决者。(对应「同时是主要作者」)Agent 既然是主要作者,人就不必再是文档的日常读者和维护者。人出场的地方是裁决点:批准 plan、拍板、封板验收、确认交付物;人读 Agent 为裁决点准备的摘要、diff 和对话总结,不翻文档树,也不维护文档。这也意味着裁决的质量取决于 Agent 摆上桌的东西:Agent 没读到的规范,不会出现在它准备的摘要和 checklist 里。让 Agent 读到该读的那份,是人能裁决的前提。

推论 2:交付物 ≠ 工程文档。(对应「上下文有限且有成本」)用户手册、更新日志、部署交接说明的读者是业务用户和运维,属于产品交付物。它们由 Agent 维护,但不进 Agent 的路由体系,否则只会白白消耗上下文;路由表只登记「什么操作之后必须更新它」。不登记的代价我们付过:某条审批流程 7 月 7 日上线,用户手册 7 月 15 日才补上,中间 8 天手册里的流程清单是错的。

推论 3:一份文档只服务一种读者。(对应「上下文有限且有成本」)同时服务人和 Agent 的文档必然变胖。我们的 plan 索引本该只回答「这个变更的文档在哪」,却被当成进度看板来写,「下一步」一列平均 98 字,最长一行 1128 字,Agent 每查一次索引都要先吞下一整面进度叙述。索引只给 Agent;人看进度,由 Agent 现场汇总或脚本生成视图。

三、六个症状

「Agent 老是忘事」是个笼统的抱怨。拆开看是六种不同的失败,每一种在这个项目里都有现场。

症状 1:不知道该读哪份,就不读。 开场的封板事故是典型:规范在,路径不在。Agent 不会因为「好像应该有个规范」就去全库搜索,路径不在入口里,它就只能凭常识开工。

症状 2:读到过期的计划,当成当前事实。 文档模型规范开头的 AI Quick Path 写着:文档显示已被替代时,必须先跳到 Replaced by 指向的新文档。可 51 个 bundle 里有 35 个没写 Replaced by,16 个连 Stable ID(文档的固定编号)都没有。规则在,字段不在,规则就空转。对按字面执行的 Agent 来说,没写「已被替代」就等于「仍然有效」。状态本身也不可靠:规范定义 8 个状态,索引里实际出现 9 种写法。

症状 3:读得太多,被历史淹没。 入口文件涨到 457 行,混着大量实案叙事;roadmap 顶部堆了 34 条日期横幅(以日期开头的进展公告,一条压一条),成了第二份更新日志。Agent 为一件小事开工,先要装下几百行无关的故事,真正相关的约束反而被稀释了。

症状 4:用例找不到来源需求。 用例主索引自称「唯一事实源」,实际是 28 段按批次写的叙述,另有两个派生视图靠人工同步,三份共 909 行。Agent 想知道「这条用例为哪个需求而写」「这个需求有没有用例覆盖」,都只能通读叙述去猜。

症状 5:干完不回写。 18 个已完成的 bundle 没有 implementation.md------实际怎么做的、哪里偏离了计划,只存在于当时那次会话里。下一个接手的 Agent 只读得到计划,不知道计划没有被照做。推论 2 里那份滞后的用户手册,是同一个症状落在交付物上。

症状 6:多个 Agent 各读各的入口。 9 月 3 日之前入口文件叫 CLAUDE.md,按 Claude Code 的约定命名,换个工具未必会被自动加载。另有一批项目级通用教训存在某个 Agent 的个人记忆里,其他 Agent 看不见。同一个仓库,三个 Agent 看到的「项目规则」不是同一套。

把症状和 Agent 特性对一下,它们并不神秘:

症状 背后的 Agent 特性
不知道读哪份就不读 无跨会话记忆
读到过期 plan 当事实 按字面执行、不会意会
被历史淹没 上下文有限且有成本
用例找不到来源需求 能 grep,却没有可 grep 的关联字段
干完不回写 同时是主要作者
各读各的入口 多个 Agent 并存

四、根因:入口文件成了百科全书

六个症状追到底,是两组区分没有做。

第一组:常驻与按需。 入口文件每次会话都被自动加载,是整个文档体系里最贵的位置,它的每一行在每一次会话里都要付 token。治理前的入口文件里放着 9 条「Code review 检查项」、7 处「立规」标注和它们背后的实案故事;一个只改按钮文案的会话,也要把代码审查清单、数据库迁移的坑统统装进上下文。反过来,真正该常驻的路由------比如「封板前去读哪份」------当时(9 月 18 日之前)却不在。

第二组:当前事实与历史。 当前事实回答「现在是什么样」,要信;历史回答「当时为什么这样」,要查证。我们项目里两者混在一起:项目概览停在 9 月 3 日,之后发了好几个版本都没改;重构专题的 roadmap 里,8 项标题打着 ✅,正文却写着「待启动」。人读到这种矛盾会去问,Agent 只会在两句话里挑一句信。

两组区分都没做,结果就是:入口文件成了百科全书,而不是路由表。 百科全书回答「是什么」,路由表回答「去哪读」。百科全书式的入口越尽职就越长,越长就越难找到要紧的那一条;开场那次事故,正是百科全书没收录、路由表又不存在的结果。

五、三条原则

从根因倒推,总模型建立在三条原则上,每条都能一句话复述,括号里是对应的 Agent 特性。

原则 1:入口只路由,不承载。(对应「无跨会话记忆」)入口是 Agent 唯一确定会读到的东西。除少数硬约定外,它的职责是告诉 Agent「这类工作去读哪份」,而不是把那份的内容搬进来------搬进来的内容会过期、会膨胀,最后把路由本身淹掉。

原则 2:常驻层只放违反了就来不及的约定,每条带一句理由,其余按需下钻。(对应「上下文有限且有成本」;理由那半句对应「按字面执行、不会意会」)判断一条内容该不该常驻,只问:如果 Agent 这次没读到它,会不会在被发现之前就造成损害?会,就常驻;不会,就放到 L1 及以下,由路由表在需要时引过去。理由不能省:只有命令没有理由,字面一不匹配 Agent 就会放过;有了理由,它才能在规则没覆盖到的边界情况下推断出正确做法。完整实案下沉到 L2 / L3,入口只留链接。

原则 3:当前事实和历史分开放,状态与替代关系写成字段。(对应「按字面执行、不会意会」)人能从语气里读出「这份好像是旧的」,Agent 不能。一份文档是否有效、被谁替代,必须是可解析的字段,而不是需要意会的叙述。写成字段还顺带让 Agent 能 grep、脚本能校验,这在第③④篇展开。

六、总模型:L0--L3 四层

三条原则落到文档结构上,就是四层:

层 内容 何时读 约束
L0 常驻 入口文件硬约定 + 路由表 每次会话自动加载 必须短
L1 按工作类型 规范(开发/测试/DB/封板/手册/部署) 开工前按路由表只读一份 通用层 + 项目层
L2 按模块/变更 plan bundle、长期专题、交接文档 进入该模块时 有状态、有替代关系
L3 证据 封板测试报告等执行报告、审计输出 需要证据时下钻 不承担入口职责

L0 常驻 只放两类内容:违反了就来不及的约定(每条一句规则、一句理由、一个链接),以及告诉 Agent 每类工作该去 L1 哪一份的路由表。它每次都被加载,所以唯一的约束是短。多个 Agent 并存时 L0 只能有一份:唯一入口文件 AGENTS.md,其他工具约定的入口文件只做转发(对应「多个 Agent 并存」)。

L1 按工作类型:开发、测试、数据库、封板、用户手册、部署,每类工作一份规范,开工前按路由表只读对应那一份。L1 分通用层和项目层:项目层声明继承哪份通用规范,只写本项目的填充、现状、例外和坑。

L2 按模块 / 变更 :一次变更的 plan bundle、一个长期专题、一份交接文档,进入该模块时才读。L2 天然会过期,所以必须有状态、有替代关系。

L3 证据 :封板测试报告、审计输出、事故报告,需要证据时才下钻。L3 不承担入口职责------规则如果只写在某份报告的结论里,就只在有人碰巧读到它时才生效。

一个 L0 入口文件的骨架大致如下(只放关键部分,完整模板留给系列末尾的 skill):

markdown 复制代码
# AGENTS.md ------ 唯一入口(每次会话自动加载,保持短)

## 硬约定(始终生效)
- <一句话规则>。理由:<一句话>。细则:<L1 链接>

## 操作 → 必读
| 操作 | 必读规范 | 一句话要点 |
|---|---|---|
| 封板 / 出包 / 打 tag / 说「全量绿」 | <封板规范> | <全量口径的一句话> |
| DB 结构 / 跨环境数据变更 | <项目数据库规范> | <最关键的一条约束> |

## 分层阅读原则
L0 = 本文件,始终生效;L1 = 上表按工作类型定址,开工前只读对应行;
L2 = 模块的 plan / 专题 / 交接文档,进入该模块时再读;L3 = 证据,需要时下钻。
判断不了读哪份时,先读上表,再到对应规范开头找下一层索引。

L2 文档头部则一律带一段可解析的元信息,这是原则 3 的落点:

markdown 复制代码
> Status: approved
> Stable ID: <T>-YYYYMMDD-NNN
> Replaces: none
> Replaced by: none

Status 只能取固定词表里的值,不许自由发挥;文档被替代时,Replaced by 填新文档的 Stable ID,Agent 读到就跳转。

这个模型留下三个问题,正好是后面三篇:

问题 落在哪 篇目
该读哪几份? L0 → L1 的路由:路由表、统一入口、两层继承 ② 路由
去哪找、信哪份? 目录、编号、bundle、索引、状态与替代链 ③ 寻址
写到哪、怎么不烂? 写回闭环、机器强制、记忆分层、新鲜度 ④ 生命周期

七、这套体系是被事故逼出来的

我得坦白:上面的四层模型不是一开始设计好的,而是 7 个月里一次次事故之后,回头才看清的形状。

3 月 4 日项目初始化,入口文件 CLAUDE.md 128 行;当天就加上了后来影响最大的规矩「Plan 驱动开发」:功能先写 plan,确认后才动代码,完成后回填实际执行记录,同时维护 roadmap。文档从此跟着开发节奏生长------那种早期单文件 plan 到今天有 301 份,其中 218 份回填了「实际执行记录」。

5 月 26 日入口文件 302 行,8 月 20 日 406 行。增长的部分有很大一块是实案叙事:踩了坑,就在入口文件里加一段,写清楚发生了什么、以后怎么做。每段单独看都有道理,合起来,入口文件成了一本不断加厚的事故手册。

9 月 3 日是第一个转折点:一次文档信息架构重整动了 235 个文件。散在四处的测试资料合成一处;plan 收成结构化变更 bundle、长期专题、早期单文件 plan 三类;改为 AGENTS.md 唯一入口,CLAUDE.md 只做转发。这次缓解了「各读各的入口」和「放在哪」,但没解决「该读哪份」。

9 月 18 日,开场那次封板事故之后,「操作 → 必读」表第一次出现,入口文件开始有了路由的形状。9 月 29 日,数据库迁移撞号,两条同号迁移都已在生产执行,搬移返工后立了「取号前先查所有并行分支和 worktree」。9 月 30 日,同名旧包被误部署,此后包名强制带时分秒。10 月 13、14 日,两次把浏览器自动化工具(MCP)的操作当成验收,立了「MCP 只定位不验收」并写进路由表。

10 月 14 日,路由表扩成 L0 / L1 / L2 的分层阅读原则------本文模型的前身;此时入口文件 457 行。同一天,项目立了一个专门的文档治理变更(编号 D-20261014-001),这个系列也同时动笔,治理结果见第③④篇。

排开来看,模式很清楚:后来加的规则,几乎每一条背后都站着一次事故。 体系是被事故逼出来的;写这个系列,是希望下一个项目不必再拿事故去换。

八、每类文档在哪一层、谁读、人做什么

总模型必须对所有文档类型一视同仁。下表把十四类常见文档放进四层。在 Agent-first 的体系里,人不是任何一类文档的日常读者,但每一类文档都有一个人必须出场的裁决点。

文档类型 属于哪层 谁读 何时读 人的角色
需求 L2 实施、审查该变更的 Agent 进入该变更时第一份读 批准前确认「要什么」,对范围拍板
设计 L2 实施该变更的 Agent 动手实现之前 批准 plan,对方案取舍拍板
实现记录 L2 接手、返工、追溯的 Agent 接手时;追问「当时为什么」时 读偏差摘要,确认偏离可接受
开发规范 L1 所有写代码的 Agent 开工前按路由表只读对应一份 实案立规时拍板新规则
重构专题 L2 改动相关模块的 Agent 进入该模块时先读历史教训 对专题优先级、搁置与收口拍板
测试规范 L1 写、改、审查测试的 Agent 动测试之前 拍板测试口径,如「全量」含哪几套
用例索引 L2 改模块、查覆盖的 Agent 改模块前查已有用例及来源需求 验收时确认覆盖缺口是否可接受
封板规范 L1 执行封板的 Agent 封板、出包、说「全量绿」之前 封板验收:按 checklist 看证据摘要放行
更新日志 交付物 业务用户 新版本上线后了解改了什么 确认交付物:范围、措辞与实际一致
用户手册 交付物 业务用户 使用系统、查操作流程时 确认交付物:抽查流程与界面一致
DB 规范 L1 改表结构、跨环境数据的 Agent 写迁移脚本之前 对兼容口径与例外拍板
迁移索引 L2 新增迁移、准备部署的 Agent 取号前;部署前列执行清单时 部署前确认 Agent 给出的执行清单
部署 runbook(操作手册) L1 执行部署的 Agent 部署之前 批准上生产;运维读另一份交接说明
事故报告 L3 排查同类问题、立规的 Agent 需要证据时下钻 确认根因,决定是否立规、立到哪层

三点值得注意。第一,「何时读」一列没有一格是「随时」,每类文档都绑在一个触发时刻上,这就是路由的含义。第二,更新日志和用户手册不在 L0--L3 里,Agent 维护它们但不靠它们获取工作知识。Agent 追溯「某版改了什么」时,读的是 L3 的封板测试报告等执行报告,不是写给业务用户的更新日志;同样,部署 runbook 给 Agent 读,运维读交接说明------一份文档只服务一种读者。第三,「人的角色」一列没有一格是「维护」或「通读」,全是批准、拍板、验收、确认。

九、这个模型会怎么坏

下面都不是假设,都是我们项目治理前的现场。

坏法一:L0 膨胀

入口文件的行数:128 → 302 → 406 → 457。注意最后一段:9 月 3 日改成唯一入口、9 月 18 日加了路由表之后,它仍然在涨。

原因很朴素:每次事故之后,最省事的动作就是在入口文件里加一段,根因一节数过的检查项和立规标注就是这样攒起来的。每段单独看都对,但 L0 的代价是乘法:行数乘以会话数。更糟的是,膨胀的 L0 会淹没路由表自己,让开场那次事故在路由表所在的文件里重演。还有个细节:入口文件里一个标题写着「四坑」,下面列了 5 条------这种不一致能一直留在每次都被加载的文件里,说明它已经长到没人逐行核对了。

对策方向:L0 每条只留一句规则、一句理由、一个链接(原则 2),实案下沉;入口层的链接要有机器检查。路由表本身怎么写才不会长成入口文件,是第②篇的内容。

坏法二:层级边界模糊

四层的边界没人守,就会互相渗透:

  • L1 混进了模板和项目适配。 文档模型规范本身 1123 行,通用规范、项目适配和约 460 行模板混在一份 L1 文档里,恰恰违反了它自己定的「通用 → 项目」分层。
  • L2 长期专题装进了短期 plan。 重构专题本应只做主题导航,却装了 145 份单文件 plan 和一份 2613 行、带状态标记的 roadmap;根因一节那种「标题 ✅、正文待启动」的打架就出在这里。
  • L2 和 L3 不分。 roadmap 顶部的日期横幅记的是一次次发版的历史,本该下沉到实现记录和 L3 的执行报告,却堆在当前事实文档的开头;同一组封板数字在索引横幅、索引行、release、roadmap 四处重复,一处没更新,Agent 面对的就是四个版本的「事实」。

最刺眼的是第一个:定义分层的那份规范,自己没有遵守分层。

坏法三:写文档的 Agent 也会失忆

上面这些膨胀和越界,很多是 Agent 照着规范写出来的。它在写的那一刻遵守了读到的规则,但不记得上周另一个会话定下的约定,也不会回头检查整个体系是否一致。Agent 作为读者会「失忆」,作为作者同样会,靠自觉维持的分层一定会漂移。这是第④篇的主题:写回是变更的一部分,约定由机器强制、人只裁决,入口层零容忍、深层设基线只降不升。

十、小结

  1. Agent 不是失忆,它本来就没有跨会话记忆;每次会话都是从入口进来的新人。要修的是入口,不是 Agent。
  2. 文档的第一读者是 Agent,人从读者变为裁决者;交付物另算,一份文档只服务一种读者。
  3. 入口只路由不承载;常驻层只放违反了就来不及的约定;当前事实和历史分开,状态与替代关系写成字段。
  4. 四层总模型:L0 常驻(必须短)、L1 按工作类型(只读一份)、L2 按模块 / 变更(有状态、有替代)、L3 证据(不当入口)。
  5. 体系是被事故逼出来的,它自己也会坏:L0 会胖,边界会糊,写文档的 Agent 也会失忆。

系列导航

  1. Agent 不是失忆,是找不到入口------大型项目文档分层设计(一)(本篇)
  2. 让 Agent 只读该读的------文档路由设计(二)
  3. 让每份文档都有唯一的位置------文档寻址设计(三)
  4. 让文档不烂------Agent 时代的文档生命周期(四)
相关推荐
IllegalCreed1 小时前
让每份文档都有唯一的位置——文档寻址设计(三)
ai编程
blackappleD2 小时前
Claude Max 每月送 100 美元 API 额度:哪些能用、哪些不能用
ai编程
柯南46682 小时前
【AI工程师精讲】10:微调方案选型:全参微调、LoRA、QLoRA 到底怎么选
人工智能·ai编程
changshuaihua0012 小时前
LangChain · LangGraph · LangSmith 全景解析:LLM应用开发完整生态
人工智能·大模型·ai编程
六维空间2 小时前
让 AI 必须覆盖国内厂商:我把「选题偏好」写成了硬规则
ai编程
长安米粒贵2 小时前
Agent 接了工具之后,最危险的不只是幻觉:聊聊提示注入的 5 个边界
ai编程
用户842558130872 小时前
点下去为什么没反应:二次确认面板、hash 路由与一扇走错的门
ai编程
我命由我123453 小时前
Function Calling 模型
人工智能·python·学习·ai·学习方法·ai编程·ai写作
wengad4 小时前
AICoding质量门禁|SonarQube 核心度量项详细释义
人工智能·ai编程·代码质量·质量门禁