Auto memory(典型实现为 MEMORY.md)在每次 Session 启动时被作为”冻结快照”加载到系统提示词中,决定实际加载量的不是文件大小,而是两个硬约束——字符上限(memorycharlimit)与模型上下文窗口剩余容量。前者是配置层面的”硬天花板”,后者是运行时的”软天花板”,二者取较小值决定最终注入量。
一、为什么 Auto memory 要在启动时一次性加载
Auto memory 与”边聊边写”的会话内记忆不同:它跨 Session 持久保存,目的是让第二次启动的 Agent 不再”失忆”。Hermes Agent、Letta Agent SDK、AgentScope Java SDK 等主流实现都把 MEMORY.md 放在 ~/.hermes/memories/ 或类似目录,并在 Session 启动的第一刻读取、注入 system prompt,之后整个会话期间不再变更。
这种”冻结快照”模式是工程权衡:它保护了 LLM 的前缀缓存(prefix cache),让 system prompt 的前 N 个 token 在整个会话里保持稳定,从而显著降低推理成本与首字延迟。代价就是”会话内写入的新条目不会立即出现在上下文”,必须等下一次 Session 启动。
二、决定加载量的两个约束
很多人以为 MEMORY.md 多大就加载多大,实际上加载量由两个独立约束共同决定,取较小者。
| 约束类型 | 名称 | 默认值(典型实现) | 作用时机 | 谁来设置 |
|---|---|---|---|---|
| 硬约束 | 字符上限(memorycharlimit) | 2,200 字符(≈ 800 token) | 写入时即校验 | 配置文件 |
| 软约束 | 上下文窗口剩余容量 | 取决于模型与 system prompt 其它部分 | 启动加载时计算 | 运行时动态 |
硬约束是配置项,常以 memory_char_limit: 2200 的形式写在 ~/.hermes/config.yaml。一旦 MEMORY.md 写入超过这个上限,memory 工具会直接报错并拒绝写入,强制 Agent 在写入前做”整理—压缩—删旧”——这是设计上的”反囤积”机制。
软约束是运行时计算。注入 MEMORY.md 时,框架会先看 system prompt 里已经占了多少 token(角色说明、可用工具描述、用户首条消息等),再算出还剩多少上下文余量,然后按”字符数 / token 比例”折算后截断 MEMORY.md。如果系统提示词中工具说明特别长,剩余容量可能只够塞下 MEMORY.md 的一半。
三、写入侧的硬约束:字符上限的工程意义
字符上限的存在不是为了”省空间”,而是为了让 Agent 主动维护记忆密度。MEMORY.md 塞满”用户今天说了什么”的流水账远比”用户的项目用 Go 1.22、测试命令是 make test”这种紧凑表述价值低。强制设上限后,Agent 必须在每条新记忆写入前问自己:这条比已有的某条更值得留吗?
实操中常见的”满—整理”循环是这样:
- Agent 调
memory(action="add", ...)准备写入新条目; - 框架计算”现有字符数 + 新条目字符数”是否超过
memory_char_limit; - 超过则直接拒绝写入,返回错误,附上当前所有条目清单;
- Agent 看到错误后,调
memory(action="replace", ...)把某条旧条目改写得更紧凑,或调remove删除低价值条目; - 整理出空间后再 retry 写入。
这种”硬失败 + 自整理”机制是 Auto memory 比传统数据库写入聪明的地方:它把”什么时候丢什么”的决策权交给 Agent 自己,而不是悄无声息地截断或丢最新写入。
四、加载侧的软约束:上下文窗口的余量博弈
字符上限是”理论上能写多少”,上下文窗口余量是”实际能注入多少”。后者受三件事影响:
- 所选模型的上下文窗口大小(4K / 8K / 32K / 128K / 200K 等);
- system prompt 中除 MEMORY.md 之外的部分(角色说明、工具 schema、安全规则、用户首条消息)占用的 token 数;
- 本次会话已累积的对话历史 token 数。
软约束的工程后果是:同一个 MEMORY.md,在 200K 上下文模型下能完整加载,在 4K 上下文模型下可能只加载前几百字符。框架通常采用”从最新条目向前回填”的策略——优先保留最近写的,截断最旧的部分——而不是粗暴地从头截断。
# 典型配置:把字符上限调高
memory:
memory_enabled: true
memory_char_limit: 4000 # 默认 2200,提高到 4000
user_char_limit: 2000
调高字符上限会让”硬约束”放宽,但软约束依然由模型上下文窗口决定——如果模型只有 8K 窗口,调到 8000 字符也不会真的全部注入。
五、两个约束的协作关系
把两个约束放在同一张图里看,能更清楚它们怎么互相牵制。
| 场景 | 硬约束 | 软约束 | 实际加载量 |
|---|---|---|---|
| 200K 模型 + MEMORY.md 1800 字符 | 2,200 | 充足 | 1,800 字符(全部) |
| 8K 模型 + MEMORY.md 1800 字符 | 2,200 | 紧张 | 截断到余量允许的字符数 |
| 200K 模型 + MEMORY.md 2400 字符 | 2,200 | 充足 | 写入即失败,加载量不变 |
| 8K 模型 + MEMORY.md 2400 字符 | 2,200 | 紧张 | 写入失败 + 即使写入成功也会被截断 |
这张表想说明的事是:两个约束不是”先满足谁”,而是”都满足才生效”。硬约束管”能不能写”,软约束管”写进去后能不能被看到”。
六、配置与调优建议
要让 Auto memory 在生产中真正发挥价值,建议按下面顺序调优:
- 先按”使用频率 + 上下文窗口”反推
memory_char_limit:高频小上下文场景保持 2,200,长上下文可放宽到 4,000–6,000; - 写入侧启用
write_approval: true,让人先看一眼 Agent 想记什么; - 启动时打印 system prompt 中 memory 段的字符计数与百分比,方便排查”为什么这条没注入”;
- 定期手编 MEMORY.md,删掉已经过期的项目事实(如”项目用 Node 18″升级后要改写);
- 严禁把 token、密码、API key 写进 MEMORY.md——它是明文落盘,且每次启动都会进 LLM 上下文。
常见问题(FAQ)
Q1:MEMORY.md 写得越多,Agent 越聪明吗?
不一定,超过字符上限的写入会被拒;软约束下超长还会被截断,密度比长度重要。
Q2:会话内写入的新条目什么时候能被看到?
下一次 Session 启动时;当前会话内不会回灌到 system prompt,但工具响应会显示最新值。
Q3:USER.md 是不是也有同样的两个约束?
是的,USER.md 走 user_char_limit 硬约束 + 同一软约束,加载逻辑与 MEMORY.md 平行。