记忆与技能:让 Agent 跨会话变聪明、把经验沉淀成文件
导读: 用户第三次纠正同一个错误,你的 Agent 依然"失忆"。这是所有自主 Agent 的痛点:跨会话记忆缺失。本文拆解 Hermes 架构的 s07 记忆系统与 s08 技能系统------记忆解决"跨会话记住",技能解决"把做法沉淀成文件"。读完你将掌握:该存什么、不该存什么、如何用 2200 字符上限 + 冻结快照 + 后台审查机制,让 Agent 真正越用越聪明。
为什么你的 Agent 每次都像第一次合作
假设你正在用 Agent 写代码。第一次,你告诉它"这个项目用 pytest"。第二次,你纠正它"别用 mock,写真实集成测试"。第三次,它又用 mock 了。第四次,你差点把电脑砸了。
不是 Agent 笨,是它真的不记得。
每次新会话,系统 prompt 从头组装,上下文归零。用户偏好、纠正过的错误、项目约定、外部资源位置,全部丢失。
阶段 1 的 Hermes 已经能跑:循环、工具、持久化、压缩、容错。现在进入阶段 2(智能层),先解决记忆与技能。
记忆的边界:不是什么都存
先说最重要的原则。memory 不是"把一切有用信息都记下来"。
只有那些跨会话仍然有价值,而且不能轻易从当前项目状态直接推出来的信息,才适合进入 memory。否则 memory 变成垃圾堆,Agent 开始依赖过时记忆。
这个原则必须最先立住。很多初学者把 memory 当垃圾桶,什么都往里扔,结果 Agent 被自己的记忆污染。

两个文件:MEMORY.md 与 USER.md
s07 的真实代码里,记忆系统拆成两个文件:
python
MEMORY_DIR = HERMES_HOME / "memories"
MEMORY_FILE = MEMORY_DIR / "MEMORY.md" # 通用知识(项目/环境)
USER_FILE = MEMORY_DIR / "USER.md" # 用户画像
ENTRY_SEP = "\n\n§\n\n" # 用罕见的 § 做分隔符
MEMORY_CHAR_LIMIT = 2200 # 字符上限
USER_CHAR_LIMIT = 1375
MEMORY.md 是 Agent 的工作笔记本,记"这个项目/这个环境是怎么回事"------项目用 pytest、机器是 macOS ARM、auth 重写是合规要求。
USER.md 是用户画像,记"这个人是谁、怎么和他合作最顺"------偏好 tabs、讨厌 mock、回答要简洁。
分隔符用 § 而不是常见的 ---。为什么?因为 --- 可能出现在正文里,而 § 几乎不会。这是细节,但决定了解析的稳定性。
字符限制 2200/1375 不是随便定的。memory 会进每次会话的 system prompt,无限增长会吃掉上下文窗口。空间不够时,Agent 自己决定删哪条------FIFO pop 末尾。
判断存哪里的规则很简单:问"如果换了一个完全不同的项目,这条信息还有用吗?" 有用 → USER.md(描述用户);没用 → MEMORY.md(描述当前环境)。
边界案例:"用户讨厌 mock 测试"存 USER.md。这是用户偏好,换项目依然成立。
该存 vs 不该存:一张清单
该存:
- 用户偏好("回答要简洁")
- 用户纠正("别用 mock")
- 项目约定("commit 前必须跑 lint")
- 外部资源位置("部署文档在 wiki 的 deploy 页")
不该存:
- 文件结构/函数签名------可重读代码
- 当前任务进度------这是 task/plan 的职责
- 当前分支/PR 号------很快过时
- 修 bug 的具体代码------代码和提交记录才准确
- 密钥密码------安全红线
这条边界很关键。很多人把"当前任务进度"存进 memory,结果 Agent 在新会话里拿着过时的进度继续干,越干越偏。
最小实现:四个动作搞定记忆
s07 的核心实现非常克制,只有四个函数加上一个 action 分发:
python
parse_entries / render_entries / load_memory / save_memory
handle_memory 负责 action 分发,支持 add / remove / read 三个操作,target 区分 memory 和 user。
真实行为:
- read →
=== MEMORY (N entries) ===+ 条目列表 - add → 追加条目,超限自动 pop 末尾 + 返回 "Trimmed to N entries to stay within X char limit"
- remove → 关键字(不区分大小写)匹配删除,返回删除数量
没有向量检索,没有评分机制。一个文件、一个分隔符、一个字符上限,就解决了跨会话记忆。
冻结快照:写入即时,生效延迟
这是 Hermes 最容易被误解的设计。
会话开始时,系统把 MEMORY.md / USER.md 读进来,冻结成 system prompt 的一部分。会话中间修改会立刻写磁盘,但不会改变当前会话的 system prompt。
为什么?因为改了会破坏 Anthropic prompt cache。
prompt cache 是性能命脉。system prompt 只要变一个字符,缓存就失效,每次请求都要重新处理全部 token。所以 Hermes 选择:写入即时、生效延迟。新 memory 下次会话才生效。
这不是 bug,是设计。很多初学者在会话中间改了 memory,期望立刻生效,发现没反应就以为系统坏了。

两套触发机制:nudge 与 flush
Agent 不会主动记忆,需要触发。Hermes 设计了两套机制:
1. 定期后台审查(nudge)
_memory_nudge_interval = 10,每 10 轮用户对话触发。响应已返回用户后,后台线程创建独立"审查 agent"(max_iterations=8、quiet_mode=True、共享 memory store),用 MEMORY_REVIEW_PROMPT 扫描对话:"用户透露了关于自己的事吗?表达了对我行为的期望吗?"
这个设计的好处:不竞争主任务注意力,用户完全无感。
2. 压缩前紧急冲刷(flush)
_memory_flush_min_turns = 6,对话即将压缩/会话重置前,直接在当前对话注入一条消息:"会话即将压缩,保存值得记住的内容------优先用户偏好、纠正、重复模式"。
模型调用 memory 工具保存后,flush 消息从历史删除,不留痕迹。
两套机制配合:平时后台默默审查,关键时刻强制冲刷。保证记忆不遗漏,也不打扰主流程。
另外,多会话同时写 MEMORY.md 时用 fcntl.flock 文件锁保证原子性。外部记忆提供者(向量数据库等)通过 plugin 接入,内容不进 system prompt(破坏缓存),注入 user message。
过渡:知道什么 ≠ 怎么做
memory 解决了"知道什么",但存不了"怎么做"。
代码审查要查哪些项?数据分析怎么处理缺失值?这类程序性知识("做这类任务应该按什么步骤?有哪些注意事项?")塞进 system prompt 会臃肿,不保存每次从零摸索。
这就是技能系统 s08 要解决的问题。
skill vs tool vs memory:三张卡
| 维度 | tool | skill | memory |
|---|---|---|---|
| 本质 | 硬编码能力 | Markdown 文件 | 声明性事实 |
| 谁写 | 开发者 | Agent 自己 | Agent |
| 改法 | 写代码 | 编辑文件 | add/remove |
| 存什么 | 可执行函数 | 一套做法 | 一条事实 |
一句话:一条事实 → memory;一套做法 → skill。
tool 是硬编码在 Python 里的能力,加新工具要写代码。skill 是 markdown 文件(SKILL.md),Agent 自己可以创建/编辑/删除,不需要改代码。

SKILL.md 格式与目录结构
SKILL.md 用 frontmatter 开头:
markdown
---
name: code-review
description: 执行一次完整的代码审查,检查安全、性能、可维护性
version: 1.0.0
---
## 审查流程
1. 先看安全漏洞
2. 再查性能瓶颈
3. 最后检查可维护性
...
目录结构:~/.hermes/skills/<name>/SKILL.md,可带 references/templates/scripts/assets 子目录。
渐进展示:三层加载
技能系统最核心的设计是渐进展示(progressive disclosure),分三层:
目录层 :只显示 name+description,几十个技能几百 token,平时就在 system prompt 里。
正文层 :模型需要时 skill_view 加载 SKILL.md 全文。
附件层:参考文件/模板/脚本,需要时再加载。
平时 system prompt 只放目录。
这意味着什么?20 个技能,每个技能正文 2000 字,如果全塞 system prompt 就是几万 token。渐进展示把常驻成本压到几百 token,其余按需加载。
最小实现:三个函数
python
_parse_frontmatter:解析 --- 分隔的 key:value 元数据
discover_skills:只读 frontmatter 不读 body,返回 name/description/path
handle_skill_view:按名加载 SKILL.md 全文
handle_skill_manage:create / edit / delete
discover_skills 只读 frontmatter 不读 body,因为目录扫描要便宜。create 时如果已存在则报错提示用 edit。
稳定层 + 按需层:为什么技能正文不进 system prompt
系统输入分两层:
稳定层 :身份/记忆/项目规则/工具定义/技能目录。每轮都在,决定 prompt cache 命中率。
按需层:技能正文/附件。进 tool_result,不进 system prompt。
技能正文不塞 system prompt 的原因和冻结快照一样:稳定层越固定,缓存越有效。
这是一以贯之的设计哲学:凡是能被缓存的东西尽量稳定,凡是需要变化的东西尽量走 tool_result。

Hermes 独特:Agent 自己创建技能
大多数框架的 skill 是只读的,开发者定义好,Agent 只能调用。Hermes 反其道而行:Agent 自己可以创建、编辑、删除技能。
工作流变成:"上次这样做效果好 → 存成技能 → 下次直接用。"
但 Agent 创建的技能和 Hub 安装的一样,要经过安全检查------扫描可疑命令注入、恶意指令。同名时用户技能优先。
技能改进也有 nudge:每隔一定轮数提醒审视是否有技能值得创建/改进。nudge 不是自动操作,只是提醒。
避坑:两章 10 错精选
记忆系统的 5 个坑:
- 把代码结构存进 memory------可重读代码,别存
- 把任务进度存进 memory------这是 task/plan 的职责
- 把 memory 当绝对真相------可能过时,优先相信眼前真实状态
- 不设字符限制------无限增长吃掉上下文窗口
- 会话中间改了 memory 期望立刻生效------冻结快照是设计不是 bug
技能系统的 5 个坑:
- 所有 skill 正文永远塞 system prompt------20 个技能几万 token
- 把 skill 和 memory 混成一类------怎么做 vs 知道什么
- Agent 创建的技能没有安全检查------这是安全红线
- 技能目录描述太弱------模型不知道何时加载
- 把 skill 当绝对规则------是推荐做法不是铁律
小结与预告
记忆系统和技能系统,让 Agent 从"能跑"走向"聪明"。
记忆解决"跨会话记住":两个文件、一个分隔符、字符上限,加上冻结快照和两套触发机制,让 Agent 越用越懂你。
技能解决"把做法沉淀成文件":渐进展示三层、稳定层加按需层、Agent 自主创建,让经验不流失。
这两个系统的设计逻辑是相通的:能缓存的尽量固定,能按需的绝不常驻。
下一篇(第 7 篇):安全审批与子 Agent 委派。当 Agent 能自己创建技能、自己改记忆,安全问题就浮出水面------怎么防止 Agent 被恶意指令劫持?怎么让子 Agent 安全地执行委派任务?
如果你正在构建自己的 Agent,最想先解决哪个问题------跨会话记忆,还是经验沉淀?欢迎在评论区聊聊你的方案。
参考文献
- Hermes Agent 教学仓库:
agents/s07_memory_system.py(本文代码素材,25116 字节真实可运行) - Hermes Agent 教学仓库:
agents/s08_skill_system.py(本文代码素材,29756 字节真实可运行) - Hermes Agent 教学仓库:
docs/zh/s07-memory-system.md与docs/zh/s08-skill-system.md(冻结快照、nudge/flush、渐进展示详解)
📥 源码获取 :如需本系列全部源码,请在以下链接克隆: