明天开新会话,它怎么知道这个项目的规矩?
本课一问:今天在这个仓库里干完活,明天开一个新会话,它怎么知道这个项目的规矩——日期写成什么格式、测试文件能不能改?
这一课先不碰:agent 自己在干活时学到的东西怎么记(第 2 课);几十份技能说明怎么放(第 3 课);会话本身怎么存(第 4 课)。这里只讲人写好的规矩。
从已有出发
A5 结束在两件事上:压缩是给接手者写交接文档;完整历史留在磁盘上,只是不在窗口里。可那都发生在一个会话之内。明天开新会话,messages 从空列表开始,昨天窗口里的东西一条都不在。
这一章用项目线专门录的 T10:两天会话。任务仓 tasks/sessions 就是贯穿任务 parse_date 的那份代码,多了一个仓库根的 AGENTS.md:
- 第一天:还是修
test_parse_date,任务说明里写着「不许改测试文件」,并要求「学到后面的会话用得上的东西,就用 remember 记下来」(第 2 课用)。 - 第二天:开一个新会话,任务是「Add a function format_date(d) to dateparse.py that takes a datetime.date and returns it as a string in this project's di…」。它说「按项目的显示约定」,却没说是哪种约定——答案只写在 AGENTS.md 里。
为什么这一章换了第二天的任务:贯穿任务 parse_date 的答案不依赖任何没写进任务的规矩,测不出「它记不记得」;第二天的任务专门设计成「只有读到项目规矩才能做对」,验收脚本检查 format_date(2024-03-05) 是否输出 05/03/2024。
先自己设计:把规矩写进第一条消息
最省事的做法:任务说明里顺手写上规矩。第一天的任务就是这么写的,原文:
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
第一条消息在会话里的命运,A5 已经见过:本课项目的截断特意保住了 system 与任务说明,但交给摘要模型的旧轮次里,规矩只在任务说明里出现过一次,写进摘要的就所剩无几。下面回放那次运行,展开任意一份摘要全文,找找「test file」:
数据:本课项目 miniagent 的真实运行(src/data/project/agent-traces.json,2026-09-26 导出;模型 Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数取自服务端 usage;思考截取约 600 字、正文与工具输出约 900 字(保留头尾)。耗时受本机同时运行的其他任务影响,只能粗看;token 与轮数才可比。
Claude Code 的官方文档对同一件事的说法是:压缩用摘要替换旧消息,所以「specific instructions from early in the conversation may not be preserved」(Agent SDK《How the agent loop works》,2026-09-26 读取)。
规矩不该跟着对话走,而要跟着项目走:写进仓库里的一个文件,harness 在每个会话开始时从磁盘读,拼进每一个请求。压缩也挤不掉它——因为它根本不在被压缩的历史里,压缩之后会再从磁盘注入一次。下面看它在真实请求里长什么样。
改进:指令文件,每个请求都重新注入
本课项目 miniagent 的 M6 实现只有几行:会话开始时读仓库根的 AGENTS.md,把 system prompt 拼成固定的三块——基础 prompt、# Project instructions (AGENTS.md)、# Memory from previous sessions(第 2 课)。稳定的放前面,为的是前缀缓存(第 2 课展开)。
第二天两组会话的第一轮,模型都没有读任何文件,就已经知道约定:
- 刷新组第二天第 1 轮,模型的思考开头(英文原文):
which is day-first with zero padding, like DD/MM/YYYY
——「with zero padding」是 AGENTS.md 里的原词;这一轮它只调用了list_dir。 - 冻结组第二天第 1 轮,模型直接写下
format_date,用的格式串是%d/%m/%Y;验收脚本输出05/03/2024,「按 AGENTS.md 输出」通过。不过冻结组第一天记下的那条记忆恰好也写着 DD/MM/YYYY(第 2 课),这一组有两个来源;刷新组的记忆只关于怎么跑测试,上一条才是更干净的一例。 - 刷新组第二天写的格式串同样是
%d/%m/%Y,但它把format_date缩进写进了parse_date函数体里,验收脚本找不到这个函数,判为没通过——失败在代码结构,与规矩无关。
对照组(T10b):同一任务、同一 seed、同一起点,只把 AGENTS.md 与记忆从 prompt 里拿掉(AGENTS.md 文件仍在仓库里),重复 3 次。结果:按 AGENTS.md 的格式输出 0 / 3,验收通过 0 / 3;3 次里没有一次主动用工具去读 AGENTS.md(读到的次数 0),写出的格式都是 ISO 的 %Y-%m-%d——唯一能被验收脚本调用到的那一次,输出是 2024-03-05,而 AGENTS.md 要的是 05/03/2024。所以现在可以说:在这个任务上,规矩没进 prompt,模型就不会自己去找,也猜不到。样本只有 3 次、同一个 8B 本地模型,只当例子读,不当比率读 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条3 次同 seed 重复;另外两次还把 format_date 缩进写进了 parse_date 里,验收脚本调用不到,失败原因不止格式一项。 出处agent-t10-sessions.json › control(2026-09-27 录制) ↗ 。
数据:本课项目 miniagent 的 T10 录制(Qwen3-8B-MLX-4bit,本机 mlx_lm.server,2026-09-26)。「上下文里」取自每一轮真实请求的 system 段;「磁盘上」由录制里 remember 工具写入 MEMORY.md 的时刻推出(写入在那一轮执行工具时落盘)。token 数用录制模型的 tokenizer 重算;缓存命中是服务端报告的 cached_tokens。
试三件事:① 看最上面一条泳道 AGENTS.md:紫色竖线出现在哪几列?(每个会话开始各一次,fork 也算)② 点 day2 的第 1 列,读下方「上下文里」那一行:这一轮请求里 AGENTS.md 块占多少 token?③ 再看最下面的「缓存命中」:day2 每一轮都重发 AGENTS.md,它有没有让命中率变低?
三家怎么做:同一个想法,三种样子
指令文件几乎所有编码 agent 都有,分歧在细节:什么时候读、放在请求的哪个位置、会话中途文件变了怎么办。
| 什么时候读 | 放在请求的哪里 | 多个文件 | 中途改了文件 | 大小 | |
|---|---|---|---|---|---|
| Claude Code(官方文档) | 会话开始读一次;压缩后从磁盘重新注入 | system 之后的「项目上下文」层 | 从文件系统根到工作目录逐级拼接;子目录的在读到那里的文件时才加载;有 CLAUDE.md 时默认不读 AGENTS.md | 不生效,到 /clear、/compact 或重启才加载新内容 | 建议每个文件 200 行以内;单个文件最多 4 MiB |
Codex(pin 8cabf5a) | 会话初始化时加载一次,按环境缓存 | user 角色的一条消息 | 项目根到 cwd 逐级拼接 | 模型看到的版本与加载到的不同时,在末尾追加一条「以下取代之前全部」,不改前面的历史 | 32 KiB 共享预算,超出硬截断 |
OpenCode(pin v1.18.32) | 每一步都从磁盘重新读 | system | 同名 first-match:找到 AGENTS.md 就不再找 CLAUDE.md;读子目录文件时懒加载沿途的 AGENTS.md | 下一步就生效(system 也就变了) | — |
Hermes(pin 56dc01d9) | 会话内缓存 system,只在压缩或恢复时重建 | 三段式 system 的 context 段 | — | 到下一次重建 | — |
| 本课项目 miniagent | 会话开始读一次 | system 的第二块 | 只读仓库根 | 不生效(下一个会话) | — |
表里 pin 源码那几行,逐行可以点开看。Codex 把「文件变了」做成了追加,而不是改写:
加载本身在会话初始化时做一次、按环境缓存(agents_md_manager.rs:31-44);预算与截断:
OpenCode 的选择相反:每一步都重读(prompt.ts:1257-1268 在循环里调用 instruction.system(),instruction.ts:90-92 的 read 没有缓存),改了马上生效;同名文件只取第一个:
Claude Code 官方文档(《How Claude Code uses prompt caching》,2026-09-26 读取)写明:项目根与用户级的 CLAUDE.md 在会话开始时读一次、留在内存里;中途编辑不会让缓存失效,但也不会生效,要到下一次 /clear、/compact 或重启。这不是所有 harness 的共同行为:OpenCode 每一步都重读,改了下一步就生效,代价是 system 变了、前缀缓存从那里断开。先问清楚你用的 harness 是哪一种。
官方 memory 文档原话是它们被当作上下文,不是强制配置(treats them as context, not enforced configuration);要无论模型怎么想都拦住某个动作,用 PreToolUse hook——那是 A3 的 hook 与权限层的事。第二天的任务允许改测试文件,所以本章的录制没有测「规矩被违反」这一面。
极小真数字:第二天第一个请求由什么组成
冻结组第二天第 1 轮,服务端报告 prompt 1,138 token。用录制模型的 tokenizer 与 chat template 把同一个请求重算一遍,按块拆开,加起来正好是这个数:
官方文档对同一件事的表述是:CLAUDE.md 在会话开始加载,每个请求都带着全文,但会被前缀缓存,所以只有第一个请求付全价。上面最后一行就是这句话在本机上的样子——本机服务的缓存跨会话共享,连第二天的第一个请求也命中了;云端缓存有 5 分钟到 1 小时的存活时间,隔夜的第一个请求要重新写入。
🔍 AGENTS.md 和 CLAUDE.md 同时存在,听谁的?
Claude Code 默认只读 CLAUDE.md:工作目录或上层目录里有任何一个 CLAUDE.md / CLAUDE.local.md,就不读 AGENTS.md;没有时才读 AGENTS.md(要 v2.1.277 以上)。想两份都读,把设置 Project instructions 改成 claude-md-and-agents-md,或在 CLAUDE.md 里写一行 @AGENTS.md 导入(官方 memory 文档)。OpenCode 按 AGENTS.md → CLAUDE.md 的顺序找,第一个命中的胜出。Codex 只认 AGENTS.md(可以配置备用文件名)。同一个仓库给多家 agent 用,最稳的是只维护一份 AGENTS.md,再让 CLAUDE.md 导入它。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 规矩不随对话被压缩、不随会话结束而消失 |
| ② 为什么一定有效? | 指令文件不在被压缩的历史里,每个请求都由 harness 从磁盘拼进去;新会话的第一个请求就带着它 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条机制性质:只要 harness 每次组请求都读它,它就在;与模型无关。各家的读取时机不同,见上表,每一格都有 pin 源码或官方文档出处。 出处待补 |
| ③ 实验上呢? | 「CLAUDE.md 每个请求都重新注入并被缓存」满票核实 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条调研档案 DR §11,2026-09-26 重新读取原文确认。 出处Claude Code 官方文档 · 满票(3-0) ↗需联网 ;T10 第二天在读任何文件之前写出了 AGENTS.md 规定的格式 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条项目实测:Qwen3-8B、每组一次运行、没有「不注入 AGENTS.md」的对照组——说明注入的内容被用上了,不能说明没有它会怎样。 出处待补 |
演进地图:你在这里
- 最省事的做法:任务说明里顺手写上「不许改测试文件」「日期用 DD/MM/YYYY」解决了不用改 harness,当场就生效。卡在A5 的分层压缩运行里,规矩写在任务说明中,17 份真实摘要只有 2 份还带着「不许改测试文件」;换一个新会话更彻底:T10 day2 的第一个请求只有 2 条消息、1,138 token,来自前一天对话的是 0 条。
- Claude Code 的 CLAUDE.md、Codex / OpenCode 的 AGENTS.md(Codex 从项目根到 cwd 逐级叠加、32 KiB 上限;OpenCode 同名文件 first-match)、Hermes 三段 prompt 的 context 段解决了会话开始从磁盘读一次,拼进每个请求;压缩之后从磁盘重新注入。T10 day2 的每一轮都带着 AGENTS.md 块(87 token),验收「按 AGENTS.md 的格式输出」:通过。卡在它只装人写好的规矩。agent 自己在干活时学到的东西(比如这个仓库怎么跑测试)写到哪、什么时候生效?
- 本课项目 miniagent 的 refresh 模式(T10 刷新组)解决了本会话马上就能用上刚学到的东西。卡在T10 刷新组 day1 第 7 轮:上一轮命中 2,072 token,刷新后这一轮 2,300 token 只命中 294——前缀在记忆块处断开,后面的工具定义与全部历史都要重算。
- Hermes MEMORY.md(2,200 字符)/ USER.md(1,375 字符);miniagent frozen 模式;仓库准则「Per-conversation prompt caching is sacred」解决了会话内 system 字节不变:T10 冻结组 day1 的 8 个后续请求里,8 个把上一轮的请求整个读回了缓存(最多差 1 个模板 token);day2 开新会话,第一个请求就带着前一天写的记忆。卡在流程性知识越攒越多:Hermes 随包的 77 个 SKILL.md 全文合计 215,494 token,是录制模型窗口(40,960)的 5.3 倍,全塞进快照放不下。
- Pi(name + description + location)、Hermes(描述截到 60 字符)、Claude Code(单条 1,536 字符上限)、agentskills.io 规范;分发:MCP Skills Extension(SEP-2640,2026-09-13 Final)解决了同样 77 个 skill,Hermes 实际注入的索引 2,209 token;模型觉得相关时再读正文。卡在知识的问题解决了,会话本身怎么存?想回到三轮前换条路:T10 从 e0005 分叉,线性列表只能删掉它后面的 15 条(原来那条成功的路径)。
- Pi(id / parentId,一个 JSONL,/tree /fork /clone)、Claude Code(/branch、--fork-session)、Codex rollout(append-only,压缩写成 CompactedItem)、miniagent session.py解决了两条分支并存,压缩有损、历史无损;分叉的第一个请求与原分支共享前缀:T10 fork 第 1 轮 1,236 token,实测命中 1,197。卡在一个会话、一个窗口的边界还在:一件事大到一个 agent 的上下文装不下、或需要别人把关时 → 下一章 A7 子 agent。
数字口径:A5 的摘要来自 pd-v2-layered-s1 的真实运行(a05-context.json);其余来自本课项目 miniagent 的 T10 两天会话(Qwen3-8B-MLX-4bit,本机 mlx_lm.server,2026-09-26),缓存命中是服务端报告的 cached_tokens;skill 的 token 数由录制模型的 tokenizer 在 Hermes pin 的 77 个 SKILL.md 上现算(a06-memory.json,2026-09-26)。
一句话
先停在这里:记住三件事——指令文件跟着项目走,不跟着对话走;每个请求都带着它,但放在前缀里,只有第一次付全价;各家在「中途改了怎么办」上做法不同。
再深一层:去实验课把两组、两天的每一轮都点开看。
下一个缺口
agent 自己学到的东西,什么时候写进 prompt? → 第 2 课:什么时候把记忆写进 prompt
指令文件装的是人写好的规矩。第一天修 bug 的过程中,模型自己也学到了东西(比如这个仓库怎么跑测试)。它记下来之后,是马上塞进 system prompt,还是等一等?第 2 课会看到「马上」在真实的缓存数字上付出的代价。
你可能会问
- 「Codex 为什么用 user 消息而不是 system 来放 AGENTS.md?」 源码能看到的是结果:它是一条带标记(
# AGENTS.md instructions…</INSTRUCTIONS>)的 user 消息,变了就在末尾追加一条新的,前面的历史一个字节都不动。这样做和前缀缓存是一致的;Codex 仓库里没有写下选择 user 角色的理由,这里不替它补。 - 「AGENTS.md 越长越好吗?」 不是。它每个请求都在,占窗口、也分散注意力:Claude Code 官方建议每个文件 200 行以内,Codex 默认只给 32 KiB。长的领域知识该做成按需加载的 skill(第 3 课)。
- 「如果不注入 AGENTS.md,第二天会写成什么格式?」 已经录了对照组(T10b,见上文):3 次都写成 ISO 格式
%Y-%m-%d,没有一次按 AGENTS.md 的 DD/MM/YYYY,也没有一次主动去读 AGENTS.md。