Hermes 双记忆库并行架构与迁移 SOP¶
本文目标:解答一个高频疑问——明明把
memory.provider设成了 openviking,为什么内置记忆库还会满? 并给出官方源码层面的机制考证,以及一套「顶格时做减法而非调限额」的迁移 SOP。典型误区:以为
provider是「替换」内置记忆系统,于是满了就调大限额——结果是每个会话的系统提示词固定膨胀,全部上下文为冷门事实买单。
一、根因:provider 是叠加层,不是替换层¶
翻 hermes-agent 源码(agent/agent_init.py 的 _init_memory),注释写得很直白:
External memory provider plugin (one at a time, alongside built-in)
即 Hermes 实际是两套记忆并行:
| 系统 | 内容 | 注入方式 | 限额 |
|---|---|---|---|
| 内置记忆库 | MEMORY.md(agent notes)+ USER.md(用户画像) |
每个会话启动时作为冻结快照全量注入系统提示词 | memory_char_limit + user_char_limit |
| 外部 provider(openviking) | 知识库注入块 + 语义检索召回(viking_search 按需) |
独立注入块,不占内置限额 | 无对应限额 |
所以「记忆库满」永远指内置库顶格,与 OpenViking 是否在位毫无关系。官方默认限额 2200/1375 字符(源码注释:约 800 tokens @ 2.75 chars/token),可在 config.yaml 的 memory 段覆写。
顶格时系统提示词里会标注如 99% — 2992/3000 chars,同时写入会被拒绝,逼 Agent 做「先删后加」的整理。
二、为什么不建议无脑调大限额¶
内置库的每一字符都是固定常驻成本:
- 调到 6000/4000,等于给所有会话固定加上约 3600 tokens 底座——哪怕这个会话根本用不到里面 90% 的内容;
- 会话越长、并发子代理越多,这笔底座被重复支付的次数越多;
- 而其中大部分内容(项目运维细节、历史排障记录)其实是「低频可检索」的,并不需要每轮都在场。
正确形态是分层:高频必带事实留内置,低频细节进检索层。
2.1 折算率取决于语言,且各厂商分词器不同(2026-09-09 实测修正)¶
早期文档常用 2.75 chars/token 这一粗略估算,但它既没区分语言、也没区分厂商。实测结果如下:
| 分词器 | 中文 | 英文 | 中文/英文倍率 |
|---|---|---|---|
Gemini(gemini-3.8-flash,countTokens 实测) |
≈ 0.59 token/字 | ≈ 0.29 token/字符 | 1.10x |
OpenAI o200k_base |
— | — | 1.34x |
OpenAI cl100k_base(旧) |
— | — | 2.08x |
厂商不可外推:Gemini 1.10x 与 OpenAI 1.34x 相差约 22%。用 tiktoken 估算 Gemini 开销会高估约 20%,务必用厂商自己的计数接口(零配额测法见《提示词成本实测:字符限额与 token 账单的错位陷阱》)。
2.2 由此产生的陷阱:限额按字符,账单按 token¶
配置里的 memory_char_limit 单位是字符,而计费单位是 token。中文省字符却费 token,英文省 token 却费字符(约需 2.5 倍字符表达同等信息)。
直接后果:把满额中文库整体译成英文,字符数会暴涨约 45% 而直接撑爆限额,写入被拒。
正确顺序是「先减法、后翻译」:
2.3 收益要按真值重算¶
本机 2026-09-09 落地(三文件全量译英 + 压缩):
| 文件 | token 变化 | 字符占用 |
|---|---|---|
MEMORY.md |
1525 → 740 | 2992 / 3000 |
USER.md |
1079 → 464 | 1996 / 2000 |
若按 o200k 的 1.34x 估算会得出「省 1426 token/轮」,但按 Gemini 真值 1.10x,实际只省约 200 token/轮(相对每轮约 21.7k 底座约 0.9%)。
所以换语言的真正理由是腾出限额空间(同样预算可容纳更多事实),而非省下那点 token。
2.4 缓存前缀:别为小钱反复改 SOUL.md¶
SOUL.md 位于系统提示词 cache 前缀最头部,任何改动都会使其后整段前缀失效一次,属一次性重算成本。为省几十 token 反复改它是净亏的——改一次,改对,然后别动。
三、分层准则¶
| 去向 | 内容类型 | 判断标准 |
|---|---|---|
| 内置库(高频必带) | 身份与环境拓扑、铁律与拍板决定、工作偏好、正在活跃使用的项目要点 | 「每个会话开头就该直接知道,检索会打断工作流」 |
| OpenViking(低频可检索) | 运维细节、排障全过程、变更历史、版本号/路径/配置值、一次性结论 | 「用到时搜一下就够了」 |
一个实用的自检问题:这条事实如果不在场,我会先想到去搜它吗? 会搜 → 检索层;必须直接看见 → 内置库。
四、迁移 SOP(防丢失顺序是关键)¶
顺序错了会把记忆弄丢,严格按以下四步:
- 先迁后删:将低频条目逐条
viking_remember提交到 OpenViking,确认全部返回 accepted; - 抽验召回:用
viking_search以不同关键词抽查 2~4 条,确认能召回且内容完整。OpenViking 的记忆抽取是异步的,且可能对来源做合并/改写,不抽验就删内置条目存在丢失风险; - 删内置:用
memory工具的 batchoperations(remove + 对保留条目的 replace 压缩)一次调用完成,避免多次往返; - 收尾:确认双库占用百分比回落,重要定案(如本次架构决定)在内置库留一条精简指针。
五、实测数据(2026-09-07 首轮迁移)¶
- 迁移 7 条低频事实:反代运维细节、浏览器扩展治理终局、配额插件架构、TG 频道运营参数、跨端测试规范、桌面 token 双口径、架构杂项归并;
- 抽验 4 条全部高分召回(如 TG 频道实体条目 0.747、浏览器治理事件文档 0.74);
- 内置库占用:
MEMORY.md99% → 49%,USER.md99% → 77%; - 迁移后在内置库新增一条「记忆架构」条目,防止未来会话重新踩「满了就调限额」的坑。
六、速查表¶
| 场景 | 正确动作 |
|---|---|
| 内置库顶格(99%) | 按分层准则做减法迁移,不是调限额 |
| 新增一条事实 | 默认先想 OpenViking;确认高频必带才进内置 |
viking_remember 后删内置 |
必须先 viking_search 抽验召回成功 |
| 要调限额 | 仅当高频必带事实本身超限时;同时评估每会话固定 token 成本 |
| 怀疑记忆丢了 | viking_search 多关键词交叉检索;OpenViking 抽取可能改写措辞,搜关键词而非原文 |