记忆与会话:明天它还记得吗?
本章一问:明天它还记得吗?
主线定位
上游(A5 上下文):一个会话里的历史越滚越大,压缩把旧轮次写成交接文档;完整历史留在磁盘上,只是不在窗口里。
本章(A6 记忆与会话):跨会话怎么记住——人写的规矩、agent 学到的事、流程性的技能分别放在哪、什么时候进 prompt;会话本身在磁盘上怎么存。
下游(A7 子 agent):一个 agent 的上下文装不下整件事、或需要别人把关时,怎么分工?
下面是累计数据流,本章点亮「记忆与会话」。它不在每一轮的循环里,而在循环之外:会话开始前决定往 prompt 里放什么,会话结束后决定留下什么。
本章问题链
每一课只回答一个问题;上一课的失败,逼出下一课的问题。
| 课 | 这一课的问题 | 朴素方案在哪个真数字上失败 | 一句话 |
|---|---|---|---|
| 第 1 课 | 明天开新会话,它怎么知道这个项目的规矩? | 规矩写在第一条消息里:A5 的 17 份真实摘要只有 2 份还带着它;第二天第一个请求 2 条消息,来自前一天对话的 0 条 | 规矩放进每轮重注入的指令文件,不放在会被压缩掉的第一条消息 |
| 第 2 课 | agent 自己学到的东西,什么时候写进 prompt? | 写入就刷新 system:刷新组第 7 轮 2,300 token 只命中 294,不刷新应命中 2,219 | 记忆写进磁盘可以立刻,写进 prompt 要等下一个会话——一切为前缀缓存让路 |
| 岔路 | 文件记忆和 RAG 谁更好? | 没有朴素方案可比:两者的比较性证据是零 | 目前没有证据;能比的只有设计维度——谁决定加载、什么时候注入、会不会打穿前缀缓存 |
| 第 3 课 | 几十个技能说明,全塞进 prompt 吗? | 全量加载:Hermes 随包 77 个 SKILL.md 共 215,494 token,是录制模型窗口的 5.3 倍 | 索引常驻,正文按需 |
| 第 4 课 | 想回到三轮前换条路,会话该存成列表还是树? | 列表存储:从 e0005 换路要删掉后面 15 条,原路径消失 | 会话是树,「当前对话」只是其中一条路径 |
| 实验课 | 两天会话:冻结 vs 刷新、第二天读到了什么、一次分叉 | — | 用自己的 trace 定生效时机 |
演进地图
本章的「现代做法」是被一连串真实数字逼出来的。每一课都会用这张图标出「你在这里」。
- 起点 把规矩写进第一条消息最省事的做法:任务说明里顺手写上「不许改测试文件」「日期用 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)。
贯穿任务:修好 test_parse_date,第二天再来
全课的贯穿任务是让失败的 test_parse_date 通过。这一章把它放进两天里:项目线录制了 T10(04-实践项目/scripts/record_t10.py,2026-09-27,Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。
- 任务仓
tasks/sessions就是 parse_date 那份代码,多了仓库根的AGENTS.md(日期一律写成 DD/MM/YYYY)。记忆写在仓库之外的MEMORY.md。 - 第一天:修
test_parse_date,任务要求「学到后面的会话用得上的东西,就用 remember 记下来」。两组:冻结组(会话开始拍快照,写入只落盘)与刷新组(写入后下一轮就重建 system)。冻结组还在第 1 轮的工具结果之后做了一次分叉,换一条路再修一遍。 - 第二天:开一个新会话,任务是「按项目的显示约定,给 dateparse.py 加一个 format_date」——任务里不说是哪种约定。为什么换任务:parse_date 的答案不依赖任何没写进任务的规矩,测不出「它记不记得」;第二天的任务专门设计成只有读到项目规矩才能做对。
- 本章给贯穿 trace 加上会话树:两天的会话、记忆与 AGENTS.md 的注入时刻、一次分叉。
每一轮发出的请求都由会话树重建、用录制模型的 tokenizer 与 chat template 重算:50/50 轮与服务端 usage 一致;缓存推算与服务端报告的命中数 50/50 一致(生成脚本 src/data/precomputed/gen_a06_memory.py)。
三层交互
| 层 | 本章里的组件 | 在哪一课 |
|---|---|---|
| 概念动画 | 演进地图(每一代卡在哪个真数字上);记忆注入泳道(磁盘上的版本 vs 上下文里的版本,逐轮) | 每一课;第 1、2 课 |
| 算法模拟器(真算法、小数据) | 前缀缓存:token 级最长公共前缀与依据官方文档实现的 Anthropic 计费规则,反事实「第 k 轮刷新」可拖;Hermes 60 字符截断的移植;Pi 会话路径算法的移植,列表 / 树两种存储下「从这里继续」 | 第 2、3、4 课 |
| 真实数据 | T10 两天 50 个请求的缓存实测;Hermes 随包 77 个真实 SKILL.md 在五种注入方式下的 token;T10 两棵真实会话树;把你自己的 Claude Code / Pi 会话文件拖进来,只在浏览器里解析 | 第 2、3、4 课、实验课 |
所有计算都在浏览器里,不联网。移植都配了自检:缓存推算对服务端实测,会话路径对 bun 在 Pi pin 源码上跑出的结果,截断对 Hermes 原函数的输出,Anthropic 计费对一份独立的 python 实现;组件底部显示一致的条数。
项目推进:里程碑 M6 · AGENTS.md 注入 + 冻结快照记忆 + 树形会话
对应 04-实践项目/MILESTONES.md 的「A6 · 记忆与会话(M6)」。实现已经在 miniagent/memory.py 与 miniagent/session.py。代码 AI 来写,决策你来做。
- 指令文件每轮重读,还是会话开始读一次?本项目:会话开始读一次。对照第 1 课的表:OpenCode 每一步重读(改了马上生效,system 也跟着变);Codex 加载一次、内容变了在末尾追加一条替换通知;Claude Code 读一次,压缩后从磁盘重注入。
- 记忆写入后什么时候生效?两种都实现了,T10 给了数字:刷新那一轮命中从 2,072 掉到 294。还有第三种:只在压缩重建 system 时刷新(Hermes 的做法,第 2 课)。写下你的选择与依据。
- system prompt 的布局?本项目是「基础 → AGENTS.md → 记忆」,但录制模型的 chat template 把工具定义接在 system 文本之后,记忆一变工具定义也要重算。要不要把记忆挪出 system(比如像 Hermes 的外部 provider 那样拼进当轮 user 消息)?
- 会话存成列表还是树?本项目:树。代价是重建消息要沿 parent 走回根。
- 记忆写入过不过权限层?本项目:过。
remember是普通工具,由configs/rules-extended.json显式放行。
验收标准(MILESTONES.md 原文 ①-⑤,后面附本章用 T10 核对的结果):
- ① 同一会话内 system 前缀各轮字节一致:
tests.test_memory_session.MemoryModes.test_frozen_system_prefix_is_byte_identical_across_rounds。T10 冻结组 day1 各轮请求的 system 段:全部是同一段 token(逐 token 一致)。 - ② 真实运行第 2 轮起
cached_tokens > 0:T10 五次运行的 45 个后续请求全部满足(本机服务只报告命中,不报告写入,导出为 null)。 - ③ day2 新会话的第一个请求里包含 day1 写入的记忆:两组第二天 都是(
memory_in_first_request)。 - ④ 会话 JSONL 支持从任一节点 fork,原分支保留:
SessionTreeTest.test_fork_keeps_the_original_branch;T10 冻结组的会话文件有 3 个叶子,day1 原路径 15 条记录完好。 - ⑤ 导出
agent-t10-sessions.json:已导出。 - 建议补的两条(本章发现的):① ✅ 已补录「不注入 AGENTS.md、不带记忆」的第二天对照 T10b(同任务、同 seed、3 次):3 次都没按 AGENTS.md 的格式输出、也没有一次主动去读它(第 1 课);② 在
DECISIONS.md写下布局决定,并用第 2 课组件的反事实数字论证。
坑与带走
本章拦截的误解| 误解 | 纠正 | 在哪一课 |
|---|---|---|
| 会话中途改了 CLAUDE.md 马上生效 | Claude Code 在会话开始读一次,中途编辑不生效也不打穿缓存,到 /clear、/compact 或重启才加载;OpenCode 相反,每一步都重读 | 第 1 课 |
| 写进 CLAUDE.md 就是强制执行 | 官方原话:它们是上下文,不是强制配置;要一定拦住,用 hook 与权限层 | 第 1 课 |
| 冻结快照 = 记忆写不进去 | 写入立刻落盘;只是本会话的 system 不改 | 第 2 课 |
| Hermes 的记忆会自动检索相关事实 | 内置记忆是会话开始注入一次的冻结快照,没有检索;外部 provider 是默认关闭的插件 | 第 2 课、岔路 |
| 文件记忆一定优于 RAG / RAG 一定更先进 | 两边都没有比较性证据 | 岔路 |
| skill 就是工具 | skill 是按需加载的说明文本;工具是有 schema、可执行的接口 | 第 3 课 |
| 描述写得越详细越好 | 索引有预算:Hermes 截到 60 字符,Claude Code 单条 1,536 字符、列表约占窗口 1% | 第 3 课 |
| 压缩掉的就永远没了 | 压缩改的是发给模型的那一份;树形会话里压缩也只是一条记录 | 第 4 课(回扣 A5) |
| 换条路就得从头付全价 | 分叉与原路径共享前缀:T10 fork 第 1 轮 1,236 token 命中 1,197 | 第 4 课 |
| 症状 | 后果 | 怎么办 |
|---|---|---|
| 规矩写在第一条 prompt | 压缩后丢失;新会话里根本没有 | 放进每个请求都重注入的指令文件 |
| 写入记忆立即刷新 system prompt | 前缀缓存在记忆块处断开,后面全部重算 | 冻结到下一个会话,或只在压缩重建时刷新 |
| 只看自己拼的 system 字符串排布局 | 模板可能把工具定义排在记忆后面,记忆一变工具定义也重算 | 看最终渲染出来的 token 序列;必要时把会变的内容挪到当轮消息 |
| 分钟级时间戳进 system prompt | 每次重建都打穿缓存 | 只精确到日期(Hermes 的注释原话) |
| 全量加载所有 skill 正文 | 前缀比窗口还大 | 索引常驻,正文按需;描述短、关键用途放最前 |
| 会话存成线性列表 | 回退重试会丢历史 | 树形存储,或至少先复制再截断 |
| 把 Hermes 记忆讲成 RAG | 事实错误(本课程红线) | 讲冻结快照;外部 provider 另说 |
带走这些句子(各课「一句话」):
- 规矩放进每轮重注入的指令文件,不放在会被压缩掉的第一条消息。
- 记忆写进磁盘可以立刻,写进 prompt 要等下一个会话——一切为前缀缓存让路。
- 文件记忆和 RAG 谁更好,目前没有证据;能比的只有设计维度。
- 索引常驻,正文按需。
- 会话是树,「当前对话」只是其中一条路径。
深入这一章
按你愿意花的时间选。每条都实际打开核对过(2026-09-26);站外链接都需要联网,离线包里只保留链接。
🎮 想更直观 10–30 分钟
🔬 想吃透原理 1–3 小时
📄 想读原文 按需
我替你排除了什么:win4r/cc-notebook 的记忆系统一篇(给出「召回 5 条」等细节,基于逆向或泄露代码,未经官方文档核实)· Hermes README 里「FTS5 session search with LLM summarization」的说法(已过时,源码写明不调用模型)· 「Hermes 有 40+ 个自动创建的 skill」(仓库里不存在这个说法)· 「Claude Code 是 grep-first」的各种转述(没有官方直证)· 「Anthropic 把 Claude Code 系统提示词砍掉 80%」的二手文章(只选作者本人的讲座)· 把 Hermes 外部记忆插件说成「Hermes 自带 RAG」的介绍文(与源码不符,也触碰本课程的事实红线)· 讲「向量数据库做 agent 记忆」的通用教程(和编码 agent 的上下文与缓存问题脱节,且没有与文件式的对照)。