实验课:两天会话——冻结 vs 刷新、第二天读到了什么、一次分叉
本课要做的:用 T10 的真实数据给本课项目的里程碑 M6 拍板——指令文件什么时候读、记忆什么时候生效、会话怎么存——并确认页面上的每个移植都与原实现一致。
需要的时间:约 45 分钟。全部计算在浏览器里完成,不联网,也不需要跑模型。
数据从哪来
- 真实运行:本课项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit(本机 mlx_lm.server)录制的 T10,两组各两天,冻结组第一天另有一次分叉,共 5 次运行、50 个请求(脚本
04-实践项目/scripts/record_t10.py,导出src/data/project/agent-t10-sessions.json,2026-09-27)。 - 每一轮发出的 token 序列:由会话树重建每一轮的 messages,再用录制模型的 tokenizer 与 chat template 重算。请求条数与录制一致 50/50,token 数与服务端 usage 一致 50/50。
- 缓存推算:「在录制顺序里最近 10 个请求中找最长公共前缀,封顶长度减一」与服务端报告的 cached_tokens 一致 50/50(本机服务 LRUPromptCache,容量默认 10)。只看「与上一轮」的话一致 44/45:差的那一轮是刷新组第 7 轮,它从另一组更早的请求里多读到 1 个 token。
- 移植与自检:会话路径是 Pi
buildSessionPath的移植,与 bun 在 pin 源码上跑出的结果比对(5/5 个叶子);skill 截断是 Hermesextract_skill_description的移植;Anthropic 缓存计费按官方文档实现,另用 python 独立实现一遍比对(生成脚本src/data/precomputed/gen_a06_memory.py(+ gen_a06_memory.ts 的 bun 半边),2026-09-26)。
实验台一:记忆在磁盘上,还是在上下文里
数据:本课项目 miniagent 的 T10 录制(Qwen3-8B-MLX-4bit,本机 mlx_lm.server,2026-09-26)。「上下文里」取自每一轮真实请求的 system 段;「磁盘上」由录制里 remember 工具写入 MEMORY.md 的时刻推出(写入在那一轮执行工具时落盘)。token 数用录制模型的 tokenizer 重算;缓存命中是服务端报告的 cached_tokens。
实验台二:每一轮的前缀缓存
口径:本课项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit 在本机录制的 T10(2026-09-26)。每轮请求用录制模型的 tokenizer 与 chat template 重算成 token 序列,长度与服务端 usage 逐轮一致;「实测」是服务端返回的 cached_tokens(本机服务只报告命中数,不报告写入量)。Anthropic 规则与价目表依据官方文档实现(prompt-caching 与 pricing 页,2026-09-26 抓取);把本地模型的 token 数按云端价目表计费只是换算,真实的 Claude 请求 token 数会不同。耗时不作比较。
实验台三:会话树
把本机 Claude Code(~/.claude/projects/…/*.jsonl,每条记录有 uuid / parentUuid)或 Pi(~/.pi/agent/sessions/…/*.jsonl,id / parentId)的会话文件拖到这里,或点按钮选择。只在你的浏览器里解析,不会上传;关掉页面就没了。
数据:本课项目 miniagent 的 T10 会话文件(traces/sessions/t10-frozen.jsonl,一个 JSONL,每条 entry 有 id / parent_id;2026-09-26 录制,已脱敏)。token 是这条内容本身的 token 数(录制模型 tokenizer,不含模板);带 usage 的回复另标出那一轮真实请求的 prompt 与缓存命中。
数据:本课项目 miniagent 的 T10 会话文件(traces/sessions/t10-refresh.jsonl,一个 JSONL,每条 entry 有 id / parent_id;2026-09-26 录制,已脱敏)。token 是这条内容本身的 token 数(录制模型 tokenizer,不含模板);带 usage 的回复另标出那一轮真实请求的 prompt 与缓存命中。
实验台四:77 个真实 skill
Hermes 随包的 77 个 skill:勾选「这一轮真的用到」的,它的正文加进「按需」那一条
口径:语料是 hermes-agent@56dc01d9 的 skills/ 目录(安装时由 skills_sync.py 拷进 ~/.hermes/skills/)。三种索引的原文都由 pin 源码的原函数排出:Hermes 用 build_skills_system_prompt(在临时 HERMES_HOME 里跑,macOS),Pi 用 formatSkillsForSystemPrompt(bun;location 按 Pi README 的约定目录构造)。token 用录制模型 Qwen/Qwen3-8B-MLX-4bit 的 tokenizer 数,正文按整个 SKILL.md 文件计;换成别的模型,绝对数会变,比例大体不变。规范上限据 agentskills.io 与 Claude Code skills 文档(2026-09-26 抓取)。
第二天验收了什么
第二天的验收脚本在 agent 看不到的地方,检查 format_date 存在、按 AGENTS.md 输出、测试全过、加了测试:
| 运行 | 轮数 | 记忆在第一个请求里 | format_date 存在 | 按 AGENTS.md 输出 | 测试全过 | 验收 |
|---|---|---|---|---|---|---|
| t10-frozen-day2 | 15 | 是 | ✓ | ✓ | ✓ | 通过 |
| t10-refresh-day2 | 12 | 是 | ✗ | ✗ | ✗ | 没通过 |
刷新组第二天没通过,原因在代码:它写的格式串也是 %d/%m/%Y,但把 format_date 缩进写进了 parse_date 的函数体里,模块顶层没有这个函数,验收脚本判「不存在」,测试也就跟着失败。这是模型写代码的失误,和记忆何时生效无关;冻结组第二天反复发了多次一模一样的 edit(工具返回「old_string 与 new_string 相同」),也是同类问题——A9 会专门给失败分类。
任务
任务 1 · 看分叉时的记忆:实验台一默认选中 fork 的第 1 轮。为什么这一列「磁盘上」有记忆、「上下文里」却是空的?把原因写成一句话(提示:fork 沿用的是哪一刻的 system)。
任务 2 · 定生效时机:实验台二切到「反事实」,把 k 从 1 拖到 8,记下每个 k 下「比不刷新多重算」的 token 数;再换成 Anthropic 规则、Claude Opus 5.5,记下多花的输入费用百分比。写下你为 miniagent 选的生效时机(冻结、刷新,还是「只在压缩时刷新」),并引用这组数字。
任务 3 · 看布局的代价:同一个反事实下,勾上「显式断点」,读刷新那一轮读回了多少 token。对照本机规则下的断点位置,说明录制模型的 chat template 把工具定义放在 system 文本之后,对「记忆放在 system 最后」这个布局意味着什么。
任务 4 · 你自己的会话:把一份你自己的 Claude Code 会话文件拖进实验台三的虚线框。它有几个根、几个叶子?有没有分叉点?如果你用过 /rewind,分叉点在哪?
任务 5 · 读自检:四个实验台各有一行自检。哪几行对照的是服务端实测,哪几行对照的是 pin 源码的原函数,哪一行对照的是另一份独立实现?
把结果写进项目:在 04-实践项目/DECISIONS.md 记下 ① 指令文件的读取时机(本项目:会话开始读一次);② 记忆的生效时机与任务 2 的数字;③ 会话存储选树的理由,以及树的代价(重建要走回根)。这三条是本章里程碑的验收材料(见章首页)。
读数时注意
- 本机缓存跨会话共享:第二天的第一个请求也能命中前一天的前缀,因为它们都还在本机服务的 LRU 里。云端缓存按 TTL 过期(Anthropic 5 分钟或 1 小时,OpenAI GPT-5.6 起 30 分钟),隔夜的第一个请求要重新写入 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条TTL 来自两家官方 prompt caching 文档(2026-09-26 抓取)。 出处待补 。
- 本机服务不报告缓存写入量:导出里
cache_creation_input_tokens记为 null;组件里「写入缓存」一栏只在 Anthropic 规则下出现,是按文档推算的,不是录制值。 - 云端价格是换算:token 数是录制模型 Qwen3-8B 的计数,真实的 Claude 请求 token 数会不同;按价目表算出的美元数只用来比较同一段历史的两种做法。
- 单一模型、每组一次:两组记下的记忆内容不同(第 5 轮起分岔),第二天的成败混着模型写代码的失误;本章能确定的是缓存数字(规则是确定的)与会话结构,不能确定「冻结组更容易成功」。耗时本页不作比较(本机有其他任务同时占用,只能粗看),token 与轮数可比。
- 逐轮的 trace 回放:A1 定型的共享回放组件读的是
agent-traces.json,T10 还没有并进去;需要逐轮看模型思考与工具输出,暂时请在实验台三点选节点,或读导出文件。
- 起点 把规矩写进第一条消息最省事的做法:任务说明里顺手写上「不许改测试文件」「日期用 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,决定了前缀缓存能不能保住。会话本身存成树,换路不丢历史,分叉还能共享前缀。用你自己的数字把三个选择写进 DECISIONS.md。
下一章:到这里,一个 agent 能记住、能换路,但始终只有一个窗口。一件事大到一个窗口装不下、或者需要另一个 agent 把关时,怎么分工?A7 讲子 agent。