从0打造编码Agent 岔路 阅读 ≈ 15 分钟
A6 记忆与会话:明天它还记得吗? 岔路课

文件记忆和 RAG 谁更好?——一个还没有证据的问题

文件记忆还是 RAG
常有人问:编码 agent 的记忆该用文件整份注入,还是用检索(RAG)按需找?本课程的调研档案里,两者的比较性证据是零。这一课只做一件诚实的事:把能讲清的设计维度摆出来——谁决定加载、什么时候注入、注入到哪、会不会打穿前缀缓存——并用 pin 源码里的三种真实做法对照:冻结快照、让模型从目录里挑、外部 provider 每轮预取。
❓

本课一问:编码 agent 的记忆,是写成文件整份注入好,还是做成检索(RAG)、每次按需找几条好?

这一课先不碰:向量检索本身的算法(不在本课程范围);也不给「谁更好」的结论——因为没有证据。这一课练的是:没有证据的时候,还能说清什么。

从已有出发 · 累计数据流 点已学环节看它回答什么
每一轮循环都经过

先说证据:零

本课程的调研档案盘点过这个问题(A 报告 U7):

所以下面所有的比较都是设计维度,不是效果结论。

🔎

证据状态:无比较证据。没有一份公开材料在同一批编码任务上,用同一个模型,对比过「文件整份注入」与「检索式注入」的成功率或成本。看到「RAG 记忆让 agent 更聪明」或「文件记忆完胜 RAG」这类说法,先问它比的是什么、在哪些任务上、谁测的。

能讲清的:四个设计维度

Birgitta Böckeler 在《Context Engineering for Coding Agents》(2026-02-05)里给过一个好用的切法:谁决定加载这段上下文——模型(不确定它会不会在该加载的时候加载)、人(可控但不自动)、还是 agent 软件本身(在确定的时刻触发)。再加上本章前两课的三个问题,就能把几种做法放进同一张表:

做法谁决定加载什么时候注入注入到哪前缀缓存真实例子
冻结快照(整份)agent 软件会话开始一次;压缩重建时再读system会话内不变Hermes MEMORY.md / USER.md;Claude Code 的 auto memory;本课项目 frozen 模式
让模型从目录里挑模型(另花一次调用)每次用户发话system(每次发话重建)两次发话之间可能变learn-claude-code s09
外部 provider 每轮预取agent 软件取、provider 决定返回什么每次用户发话当轮 user 消息的发送副本不动 system 与之前的历史Hermes 的外部记忆插件(默认关闭)
按需读取正文模型用到时工具结果 / user 消息追加在末尾第 3 课的 skills

表里第四列「注入到哪」决定了第五列。第 2 课的真实数字就是「注入到 system、会话中途变了」的代价:刷新组第 7 轮,2,300 token 只命中 294。任何每轮都可能不同的内容(检索结果、按查询挑出的记忆),如果放进 system,就会在每次变化时付这个代价。

源码里的第二种:让模型挑

learn-claude-code s09 在每次用户发话时,把「记忆目录」(每条一行:名字 - 描述)交给模型,让它返回相关条目的序号,最多 5 条;挑出来的原文合计不超过 20,000 字符,拼进这一次发话的 system:

code.py learn-claude-code@0dcafa2:s09_memory/code.py:720-728需联网 MIT
720def agent_loop(messages: list):
721 relevant_memories = load_memories(messages)
722 system = build_system(relevant_memories)
723
724 while True:
725 response = client.messages.create(
726 model=MODEL,
727 system=system,
728 messages=messages,
👆 带下划线的行可以点:点哪行,讲哪行。

它是「选择式」注入,但选择靠的是模型读目录,不是 embedding 相似度——调研档案的勘误(L12)专门写明:它和 Hermes 冻结快照一样,都不是向量 RAG。

源码里的第三种:每轮预取,只拼进当轮的 user 消息

Hermes 的内置记忆是冻结快照(第 2 课)。它另外留了一个外部记忆 provider 的插件接口,配置里默认为空:

config_defaults.py hermes-agent@56dc01d9:hermes_cli/config_defaults.py:1711-1716需联网 MIT 最新版已变化 · 勘误表 H5
1711 # External memory provider plugin (empty = built-in only).
1712 # Set to a provider name to activate: "openviking", "mem0",
1713 # "hindsight", "holographic", "retaindb", "byterover".
1714 # Only ONE external provider is allowed at a time.
1715 "provider": "",
1716 },
👆 带下划线的行可以点:点哪行,讲哪行。

开了之后,每次用户发话,Hermes 先按这句话向 provider 预取,把结果拼到这一轮 user 消息的发送副本后面——不进 system,也不改之前的任何消息;并且把发出去的原样字节存下来,以后每一轮重放同样的字节:

turn_context.py hermes-agent@56dc01d9:agent/turn_context.py:53-86需联网 MIT
53def compose_user_api_content(
54 content: Any,
55 ext_prefetch_cache: str,
56 plugin_user_context: str,
57) -> Optional[str]:
58 """Compose the API-bound content of the current turn's user message.
59
60 Sources: memory-manager prefetch + ``pre_llm_call`` plugin context with
61 target="user_message" (the default). Both are appended to the *API copy*
62 of the user message only — the stored content stays clean.
63
64 This is the single source of that composition. The prologue stamps the
65 result onto the live message as ``api_content`` (persisted alongside the
66 clean content) and the ``api_messages`` build in ``conversation_loop``
67 sends the same helper's output, so the persisted sidecar can never drift
68 from the bytes on the wire — which is the whole prompt-cache invariant:
69 what turn N sends must be what turn N+1 replays.
70
71 Returns ``None`` when nothing is injected (multimodal/non-string content,
72 or no ephemeral context), meaning the message is sent as-is.
73 """
74 if not isinstance(content, str):
75 return None
76 injections = []
77 if ext_prefetch_cache:
78 fenced = build_memory_context_block(ext_prefetch_cache)
79 if fenced:
80 injections.append(fenced)
81 if plugin_user_context:
82 injections.append(plugin_user_context)
83 if not injections:
84 return None
85 return content + "\n\n" + "\n\n".join(injections)
86
👆 带下划线的行可以点:点哪行,讲哪行。

这是一个值得记住的设计:每轮都可能变的内容,放在会变的位置(当轮消息),而且一旦发出就冻结。它不回答「检索式记忆好不好」,只回答「如果要用,怎么用才不打穿缓存」。

⚠
常见误解一:「文件记忆一定比 RAG 好——Claude Code 都用文件。」

「谁在用」不是证据。Claude Code 官方文档说明了它怎么做,没有说明为什么比检索式更好,也没有公开对比数据。

常见误解二:「RAG 一定更先进——文件整份注入太原始。」

同样没有证据。冻结快照小(Hermes 两个文件合计 3,575 字符上限)、确定、对缓存友好;检索式能装下更多,但加载与否取决于检索或模型的判断。哪边在编码任务上更好,没人测过。

常见误解三:「Hermes 的记忆就是 RAG。」

错,这是本课程的事实红线。Hermes 内置记忆是会话开始注入一次的冻结快照;外部 provider 是默认关闭的可选插件,即使打开,也不能把它说成「Hermes 的记忆会自动检索」。

证据三问

问本课的回答
① 它解决什么问题?记忆多到不能整份注入时,只放一部分进上下文
② 为什么可能有效?设计维度能说清各自的取舍:谁决定、什么时候、放在哪、缓存代价
③ 实验上呢?没有比较证据

演进地图

这一课在演进链的旁支上:它不是被某个真数字逼出来的下一代,而是一个还没有答案的问题。

记忆与会话的演进:每一代都被上一代的某个真数字逼出来
  1. 最省事的做法:任务说明里顺手写上「不许改测试文件」「日期用 DD/MM/YYYY」
    解决了不用改 harness,当场就生效。
    卡在A5 的分层压缩运行里,规矩写在任务说明中,17 份真实摘要只有 2 份还带着「不许改测试文件」;换一个新会话更彻底:T10 day2 的第一个请求只有 2 条消息、1,138 token,来自前一天对话的是 0 条。
  2. 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 自己在干活时学到的东西(比如这个仓库怎么跑测试)写到哪、什么时候生效?
  3. 本课项目 miniagent 的 refresh 模式(T10 刷新组)
    解决了本会话马上就能用上刚学到的东西。
    卡在T10 刷新组 day1 第 7 轮:上一轮命中 2,072 token,刷新后这一轮 2,300 token 只命中 294——前缀在记忆块处断开,后面的工具定义与全部历史都要重算。
  4. 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 倍,全塞进快照放不下。
  5. 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 条(原来那条成功的路径)。
  6. 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。
旁支 按相关性挑选记忆注入,或检索(RAG) 你在这里 没有比较证据
learn-claude-code s09(每次用户发话先让模型从目录里挑相关记忆,最多 20,000 字符);Hermes 的外部记忆 provider 插件(默认关闭,每轮预取、只拼进当轮 user 消息)
和文件式冻结快照谁更好?档案里没有任何比较性证据——能讲清的只有设计维度:谁决定加载、什么时候注入、会不会打穿前缀缓存。

数字口径: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)。

一句话

📌
文件记忆和 RAG 谁更好,目前没有证据;能比的只有设计维度——谁决定加载、什么时候注入、会不会打穿前缀缓存。

回到主线

流程性知识(skills)也这样全量注入吗? → 第 3 课:几十个技能说明,全塞进 prompt 吗?

你可能会问