文档不是给 Agent 全部读完的:按需加载、长期记忆与旧知识退出
随着项目发展,文档通常只会越来越多。最初只有 README;后来出现了架构设计、接口契约、测试策略、故障记录、Agent 工作规则,以及按日期归档的大量历史材料。
这本来是件好事。问题在于,Coding Agent 很容易把「文档很重要」理解成「开始任务前应该全部读完」。
假设现在只需要修复一个前端交互细节,而 Agent 在动手前依次读取投资计算模型、所有 Runtime 文档、完整项目历史和每一份归档 Spec。它确实获得了大量信息,却不一定更接近这次任务。
更危险的是,这些内容可能同时包含当前有效的约束、早已撤回的提案和过期的交接记录。上下文越长,不意味着工程事实越准确。
2026 年 10 月,我们还碰到了相反的问题:旧评测代码已经退役,开发文档却仍要求 Agent 执行原命令。读得太多会引入噪声,读到过时指令则可能把已经删除的机制重新实现出来。
因此,工程知识需要同时解决三个问题:当前任务该读什么,一份材料能证明什么,以及旧知识何时退出当前指导。 按需加载、长期记忆与退役治理,是同一条知识生命周期的不同环节。
一、先把 AGENTS.md 从百科全书变回地图
最直接的做法,是把所有重要知识写进 AGENTS.md。这能减少遗漏,却也产生固定上下文成本:不管当前任务是否相关,这些规则都会进入默认阅读范围。
一旦任务开始由多个子 Agent 并行研究、评审或测试,重复阅读的固定成本会随角色数放大。
因此我们重新定义了根工作规则的职责:全局文档保留跨任务的不变量与导航,详细知识由对应领域的文档承担。
例如,投资数据的权威来源、权限和确认边界需要在每次任务中可见;某个 MCP 供应商的字段缺失策略,则没必要在每个 UI 任务里常驻。
我们使用类似下面的导航:
| 任务方向 | 主要读取入口 |
|---|---|
| 前端交互 | 前端任务包与相关 UI 契约 |
| MCP 和数据源 | MCP 任务包、相关接口规范和适配说明 |
| 投资计算 | 核心投资约束与当前计算契约 |
| Agent Runtime | Runtime 架构、对应 Spec 与测试 |
| 历史回归 | 当前调用链,再追溯必要的历史记录 |
这不是删除知识,而是改变知识进入上下文的时机。
二、Hot、Warm、Cold:三种阅读温度
在仓库中,我们为开发时上下文引入了 Hot、Warm、Cold 路由。
在仓库中,我们为开发时上下文引入了 Hot、Warm、Cold 路由。
graph TD TaskRequest --> HotRules HotRules --> DomainRouter DomainRouter --> WarmPack WarmPack --> CurrentSpec CurrentSpec --> RelevantCode RelevantCode --> ScopedImplementation ScopedImplementation --> EscalationDecision EscalationDecision --> ColdHistory EscalationDecision --> FocusedVerification ColdHistory --> FocusedVerification
图中不是说每个任务必须访问 Cold。通常先读取 Hot 和当前领域的 Warm,再定位现行规范和代码;只有跨领域、遇到关键职责争议或需要追溯回归时,才有理由升级读取历史资料。
Hot:不该被忽视的最小约束
Hot 层包含基础工作约束、Bootstrap 和入口导航。它告诉 Agent:这是怎样的项目、什么绝对不能越界、下一步去哪找资料。
它不是一份包含所有模块状态的长篇手册。
Warm:匹配当前任务的知识包
Warm 层承载按领域划分的知识,例如前端、MCP、Memory、治理、测试和可观测性。
Agent 优先读取一个与任务匹配的主要任务包,再根据代码调用链和具体变更范围展开。跨领域任务可以再加入第二个包,不要求每次任务都从零读完全部领域说明。
Cold:重要,但不默认读取
Cold 层主要放旧版 Spec、归档历史、大型评测产物和过期实验资料。
Cold 不代表资料没有价值;它代表内容具有明确的时间性,或者只有在特定排查中才需要它。
比如 Agent 排查最近一次恢复机制的回归,就有理由读取旧设计及当时证据;普通组件样式修改通常没有这个必要。
Anthropic 的 Effective Context Engineering for AI Agents 也讨论了上下文选择的问题。我们将类似的按需消费思想用在开发 Agent 对仓库资料的读取,而不是假设把所有项目知识常驻一定会提高正确率。
当时实际测到了什么?
在 2026 年 8 月的一次开发上下文治理中,两份常驻 Agent 规则文件的总行数从 442 行下降到 164 行,并留下路由结构与定向测试记录。
这个数字只说明当时的入口文档体积发生了变化。它不能直接证明真实计费 Token 下降相同比例,也不能证明长期开发速度提高了多少。
更重要的工程变化是:默认执行路径从「先读完整背景」变成了「先选领域,再读精确契约,必要时扩大范围」。
三、找到文档之后,还要知道它能证明什么
上下文加载只是第一步。更复杂的问题是:不同文档如果互相矛盾,谁才拥有当前事实?
想象下面的演进:
一次 Proposal 提议使用 A 方案,之后设计讨论改成 B,最后代码只实现了其中一部分。几周后,另一个 Agent 搜到了最早的 Proposal。如果它把「曾经建议」理解成「现在已实现」,就可能沿着错误的方向修改代码。
因此,我们不是只按模块分目录,而是先区分知识的职责。
| 材料 | 它主要回答的问题 |
|---|---|
| 工作约束与领域模型 | 哪些不变量必须遵守?领域概念是什么? |
| Accepted Spec | 当前接受了怎样的行为契约? |
| Active Change | 目前正在尝试改变什么? |
| 当前代码和配置 | 实际版本如何运行? |
| 测试和执行证据 | 哪个版本、环境下观察到了什么? |
| ADR 与架构文档 | 为什么这样划分职责? |
| History | 当时发生了什么,哪些经验值得复用? |
| Handoff | 交接那一刻做到了哪里,下一步是什么? |
| 可复用方法与 Skill | 下一次可以怎样分析和处理,而非某个版本现在处于什么状态? |
这不是一张「永远由某种文件压过另一种文件」的简单优先级列表。问题不同,权威来源就不同。问运行时实际行为,要读代码和真实接线;问已接受的合同,要看 accepted spec;问当初为什么没有采用 A,要看设计记录或 History。
Diátaxis 按读者需要区分教程、操作、参考与解释类文档。我们的划分与它关注的问题相似,但侧重的是持续开发中的规范权威、版本时间和执行状态,而不是直接套用四种文档形式。
只有知道一份资料承担什么职责,搜索结果才不会把旧决策包装成新命令。
四、History 不应该成为另一个巨大的日志系统
当多个 Agent 持续开发时,文档膨胀的另一个来源是历史记录。
每次修复、每次测试、每次正式关闭,似乎都值得写一篇复盘。如果全部保留,很快就会出现大量相似内容:
今天 RED,明天 GREEN,后天回归,第四天 Close,第五天又补了一条记录。未来的 Agent 需要在许多流水账里找到真正改变工程判断的那一次。
我们因此将 History 拆成几个不同的层次。
Candidate:便宜的素材缓冲
某次任务有一个可能值得复用的经验,但当前证据不够,或者主题还没有独立价值,可以先留一条简短 Candidate。
Candidate 不是正式历史,更不意味着每次完成任务都要写。纯格式修正、重复验证和没有新判断的维护工作,默认可以不产生 Candidate。
Time:记录当时发生了什么
正式的 Time 是一份可追溯的历史正文。它要说明背景、关键判断、实际结果和证据边界。
我们不会因为「测试通过了」就自动晋升。更重要的是:是否存在新的、非显然的工程判断?未来遇到类似问题能否复用?结果是否有足够事实支撑?既有历史是否已经覆盖同样的经验?
Time 承载历史事实,但它不替代当前代码或现行规范。
Daily:每天只保留有意义的变化
Daily 不是 Time 文件目录的逐条副本。它将一天内同一主题的研究、修复、回归和关闭压缩成少量短卡。
例如,一个主题当天经历了四轮测试,最终判断仍然是「需要区分工具运输失败和数据本身不可用」,Daily 只需记录这一项判断的演进、结果范围和原始 Time 入口,不必分别写四次。
Type:跨时间精读一个主题
Daily 回答「这一天发生了什么」,Type 回答「这个主题跨时间真正改变了什么」。
它只保留核心判断、几个重要阶段、代表性历史和可复用检查点。新一次 Close 或测试计数变化不会自动生成新阶段。只有出现新主题、改变核心判断,或者发现更合适的代表案例时才更新。
整条知识生产与阅读路线是:
整条知识生产与阅读路线是:
graph TD EngineeringChange --> EvidenceLedger EvidenceLedger --> MaterialReview MaterialReview --> NoHistory MaterialReview --> Candidate MaterialReview --> CanonicalTime CanonicalTime --> DailyDigest DailyDigest --> TopicSummary DailyDigest --> WritingDiscovery TopicSummary --> WritingDiscovery WritingDiscovery --> ReadOriginalHistory ReadOriginalHistory --> VerifyCurrentFacts VerifyCurrentFacts --> TechnicalArticle
图中的分叉代表:没有长期价值的材料可以不晋升;暂时有用但证据不足的材料进入 Candidate;真正形成可复用判断的材料才进入 Canonical Time。Daily/Type 是历史的压缩与导航层,不是另外两份权威事实副本。
独立开源:History Skill 怎样落实“少量、可追溯、可维护”
四层组织不是另一套强制日报。我们把其中可移植的部分做成了 Engineering History Skill(v0.1.0,MIT):一个可以整体复制给 Agent 的 Skill,以及使用 Node 26.11.1–26.x、无运行时依赖的离线 CLI。它并不移植 ETF Agent 内部的完整任务关闭规则。
举个构造的教学例子:团队决定由解析器统一维护缓存,而不是让两个调用方各自缓存。现在值得保留的不是“缓存代码已修改”,而是重复缓存引发怎样的失效责任、为什么选择一个 Owner、哪些效果还没有测量。
npm install --save-dev github:hpoenixf/engineering-history-skill#v0.1.0
npx --no-install engineering-history init
npx --no-install engineering-history candidate \
--task "缓存归属决策" \
--summary "比较两处缓存与解析器统一缓存;记录取舍,性能收益未测量" \
--source-id "demo:cache:decision-1" \
--material-value share
npx --no-install engineering-history validateinit 在默认的 docs/history/ 创建基础索引,已有文件不会被覆盖。candidate 把材料写入 .work/history-candidates/;同一个 --source-id 即使跨日重试或改写摘要也会被识别为同一事件。日期默认按 UTC 计算,可用 --timezone Asia/Shanghai 等 IANA 时区指定事件的本地日期。
注意,--material-value share 只是供后续审阅的标签。CLI 不会根据任务名称推断“很重要”,sync: true 也不意味着晋升成功。真正形成 time/ 正文,仍需要人或 Agent 核对非显然的判断、实际观察、可复用性和与既有历史的重合;随后才把它压缩到 daily/,在确有理解变化时更新 type/。这些正文的提炼不是 CLI 自动完成的。
另一个容易忽视的问题是 Agent 自动更新摘要时覆盖手写内容。工具把 daily/、type/ 中的 <!-- BEGIN AUTO-REFINE --> 到 <!-- END AUTO-REFINE --> 视为可编辑区;区外原有字节受保护,没有标记的旧文件则整体受保护。修改前后可以这样检查:
npx --no-install engineering-history snapshot --out history-before.json
# 人或 Agent 审阅后,只编辑允许自动维护的摘要区域
npx --no-install engineering-history verify --snapshot history-before.json
npx --no-install engineering-history validatesnapshot 记录保护区摘要,verify 对比并报告被改动的受保护内容;它不自动修改或恢复历史文件。validate 检查索引和本地 Markdown 链接,不评判历史结论是否正确。已有写入锁或符号链接风险也会以错误返回,避免在不确定的文件状态下继续写入。
如果在 Coding Agent 中使用,还可以直接复制仓库内完整的 skills/engineering-history/ 目录;Skill 随包携带脚本和写作指引。这里可复用的是“模型判断哪些经历值得成为知识,确定性程序保护日期、身份、链接与人工文字”这一分工,而不是声称自动整理就产生了高质量的工程记忆。完整的安装步骤和示例见开源仓库。
五、四个真实反例:代码退出以后,旧知识还会从哪里回来?
知识分层只有在旧机制能够退出时才有意义。我们在 2026 年 10 月的清理与回看中遇到的几类问题,解释了为什么不能只删除源码,再让其他材料自行过期。
旧命令退役了,当前文档却仍要求执行
旧模型评测机制已被新测试路线取代,但仓库还保留着执行后只提示“已退役”的命令,当前开发说明也继续要求运行它。
对 Coding Agent 而言,这不是一段无害的旧文字。它可能先尝试旧命令,再把“恢复这个能力”当成完成新任务的前提。源码已经删除,并不等于旧机制失去了指导权。
清理时要沿真实使用关系检查命令、工具说明和当前开发文档。没有真实消费者的旧入口与兼容分支可以退出;仍有价值的历史记录不必随之消失。
历史提案被当成当前要求,旧结构可能被重新实现
一条市场数据处理路线曾因数据不完整而提出修复设计,也留下了部分测试。之后架构改用更简单的数据接入方式,旧处理路线已无继续实施的必要。
风险在于,原设计仍写着“系统应该新增这种能力”。若把它重新同步进当前规范,后续 Agent 就可能忠实地恢复已经被替代的结构。
正确的退役不是把未完成任务改成完成,也不是清除当时的失败。要保留设计为何提出、验证到哪里、哪些事情未完成,同时明确它不再构成当前实施要求。历史可以解释旧选择,不能自动恢复旧选择的效力。
测试兼容层替模型补动作,让旧结构继续活着
升级工具调用协议后,部分测试仍使用旧格式。兼容层会按旧约定补齐测试中未明确写出的授权请求、来源信息和完成信号。
于是,测试通过到底说明当前 Agent 做对了,还是辅助代码替它做了,变得难以区分。测试甚至可能为了保持绿色,要求生产代码继续兼容旧结构。
当时的调整是把模型动作直接写清,再经过真实执行边界验证。授权、来源和恢复等有效断言继续保留,替测试制造隐含动作的转换层退出。判断依据不是有多少测试引用旧模块,而是它们保护的是必要行为,还是旧实现方式。
历史检查器把正确删除误判为规范遗漏
维护规范的自动检查也曾把已经正确移除的要求报告为“当前缺失”。问题在于它没有正确区分新增与删除,也没有处理好要求生效的时间。
继续补齐这些“缺失”,反而会撤销一次正常的架构升级。我们只修正能够确认的解释错误,保留其他未解决的历史问题,没有为了检查全绿而重写旧失败。
这四个例子对应同一套维护动作:更新当前指导,迁移必要的行为检查,撤下过时的实施要求,保留原始事实。它不需要另一套永久审批系统,但必须进入实际变更范围;否则下一个 Agent 仍可能沿文档和测试把旧设计带回来。
这些案例只说明各自记录里的问题与调整,不证明全仓过时知识已经清零。
六、Handoff 和 History 不是同一类记忆
在长期 Agent 协作中,我们还特别区分了 Handoff。
History 解释为什么这样设计、过去发生了什么;Handoff 则保存交接时的可执行任务状态。
一份合格的 Handoff 应能让另一位 Agent 找到:当前任务及对应规范、当时的分支和工作区、已改文件、实际测试记录、剩余阻塞、下一条具体命令,以及哪些范围不能擅自修改。
但带日期的 Handoff 不等于当前任务状态本身。接手时仍须重新检查当前工作区、任务队列和 Evidence Ledger,不能因为两周前的一份交接说「下一步运行 X」,就认为现在一定还应该运行 X。
因此,我们让当前项目状态和任务/证据文件承担最新进度职责;旧 Handoff 保留它所描述的那个时间点,不能无限更新到假装它一直代表现在。
这解决了两种完全不同的需求:
- 短期连续性:一位 Agent 暂停后,下一位能恢复工作;
- 长期可复用性:几个月后,一个与本次任务无关的 Agent 能理解关键工程判断。
用同一份文档同时承担这两种职责,往往只会让它越写越长。
七、为什么我们没有一开始就把所有文档扔进向量数据库?
知识越来越多,最自然的提议是全部向量化,让 Agent 语义搜索。
这种方法可以改善资料发现,却无法独立解决我们面临的核心问题:语义相似度不知道一份文档是旧 proposal、已撤回设计、当前规范还是某次实验快照。
而且开发任务往往已经带有大量结构化线索:目标模块、错误类型、Spec 名称、代码路径、任务 ID、测试命令。对于这些明确目标,Router 与精确读取的成本和理解难度都相对较低。
所以我们先采用简单的导航、任务包与历史压缩。语义检索可以在确实遇到跨领域资料难以发现的问题时加入,但它也应该先负责找候选,而不是自动替候选资料赋予现行权威。
这是一种范围选择,不是对 RAG 的否定。
八、知识保存得越完整,越需要明确停止条件
这套体系仍然需要维护:路由可能过期,领域包需要随接口变化,重要设计变更需要更新对应的当前文档,历史晋升需要判断是否真的有新增价值。
新增与删除都需要停止条件。一次普通修复可以只留在所属任务的证据中,不必自动晋升为全局规则;一次机制退役则要同步撤下当前指令与无效依赖,不能只删实现。
我们没有让每个 change 都生成一篇 History,让每次新测试都更新 Daily,让每一次话题被提及都修改 Type。
技术分享同样采用漏斗式取材:先从 Daily/Type 发现值得写的主题,挑少量代表性 Time 理解当时的原因,再回到现行 Spec、代码和测试核实事实。旧文章可以帮助去重,却不能替代对当前实现的核查。
最后,我们得到的并不是「把文档全部自动化」的系统,而是一套比较清楚的责任关系:
当前规则在权威位置维护;Agent 按任务读取;实际执行进入证据;长期经验有选择地保存;旧要求随机制退出;未来的 Agent 从压缩后的入口重新发现并核实经验。
在历史样本里,我们能够观察到常驻文档明显瘦身和相应路由规则落地。至于跨项目的 Token 节约、长期开发时间和错误率改善,仍需要独立测量,不能由文件行数推断。
对长期 Coding Agent 项目来说,需要同时做到两件事:让有价值的经验容易找到,让已经失效的知识不再指导当前开发。当前规则指导今天,历史记录解释过去;保存历史不等于授予它永久决定权。
从旧命令和规范的退役到按需读取当前知识,我们实践的是方法 6「机制退出与工程知识」:历史留着解释过去,现行指令必须有唯一有效位置;旧实现删除以后,旧文档、Prompt 和测试兼容层也不应继续指挥新开发。
取材边界:文档行数来自 2026 年 8 月的治理窗口;退役案例来自后续工程记录。本文没有重跑这些历史实验,也不把原测试结果更新为本次通过。
本文亦发布于 个人博客。更多工程实践见 GitHub 技术文集;欢迎 Star 支持。