芥末
发布于 2026-04-12 / 0 阅读
0
0

AI Agent 记忆系统架构:OpenClaw、Claude Code 与 Hermes Agent 的工程取舍

设计 AI Agent 时,模型能力当然重要,但真正决定 Agent 能不能长期稳定工作的,往往是记忆系统。

原因很简单:大语言模型(Large Language Model,LLM)本身没有运行时状态。每一次调用模型,模型只看本次请求里提供的上下文;如果某个事实、规则、历史步骤没有进入上下文,模型就无法可靠地使用它。

所以,Agent 框架必须在模型外面补一套记忆系统,用来解决四个问题:

问题设计含义
存什么哪些事实、规则、偏好、经验需要保留,哪些内容应该丢弃
存在哪放在文件、数据库、向量库,还是直接放进上下文窗口
怎么取通过路径加载、关键词搜索、语义检索,还是由 Agent 主动调用工具
怎么管如何更新、压缩、淘汰,避免记忆越积越乱

OpenClaw、Claude Code、Hermes Agent 分别代表了三种典型路线:

  • OpenClaw 把文件系统当作记忆的事实来源。
  • Claude Code 把上下文窗口当作稀缺资源来调度。
  • Hermes Agent 把记忆按访问模式分层,并把情景记忆作为能力积累的核心。

这三种设计没有绝对优劣,关键在于目标不同:有人要透明可控,有人要 Token 使用高效,有人要 Agent 随时间积累经验。

Agent 记忆的四个层次

讨论具体框架前,需要先把“记忆”拆开。Agent 记忆不是单一概念,至少可以分成四层。

flowchart TB
    A[Agent 记忆系统] --> B[上下文记忆<br/>In-context Memory]
    A --> C[外部记忆<br/>External Memory]
    A --> D[情景记忆<br/>Episodic Memory]
    A --> E[参数记忆<br/>Parametric Memory]

    B --> B1[当前 Prompt 和对话历史<br/>访问最快,容量有限]
    C --> C1[文件、数据库、向量库<br/>可跨会话保存]
    D --> D1[任务过程、工具调用、结果反馈<br/>用于从经验中学习]
    E --> E1[模型权重中的知识<br/>运行时不可直接修改]
记忆类型存储位置访问方式生命周期主要问题
上下文记忆当前 Token 窗口模型直接读取单次会话或单次请求容量有限,长对话会被压缩
外部记忆文件、数据库、向量库检索后注入上下文可持久化检索质量决定可用性
情景记忆结构化经验记录按任务召回可长期积累如何判断哪些经验有复用价值
参数记忆模型权重模型内部调用随模型存在运行时无法更新,可能产生幻觉

真正需要架构设计的,主要是外部记忆和情景记忆。上下文记忆受 Token 窗口限制,参数记忆又不受 Agent 框架直接控制;外部记忆和情景记忆才是 Agent 框架可以主动塑造的部分。

OpenClaw:文件系统就是记忆事实来源

OpenClaw 的核心约束很直接:长期状态必须写进磁盘文件。没有写入文件的内容,只能算临时上下文,不能算被 Agent 真正记住。

典型目录结构类似这样:

~/.openclaw/workspace/
├── MEMORY.md              # 长期记忆:稳定事实、用户偏好、工作规范
├── SOUL.md                # Agent 身份定义
└── memory/
    ├── 2026-04-12.md      # 当日日志:短期工作记录
    ├── 2026-04-11.md      # 昨日日志
    └── ...

这种设计把 Markdown 文件同时当作三种东西:

  • 记忆存储介质;
  • 人类可读的调试界面;
  • 人机协作时可以直接修改的控制面。

为什么用文件,而不是数据库

文件系统的查询能力不如数据库,但 OpenClaw 选择文件并不是为了查询性能,而是为了透明性和可控性。

存储方式优点代价
Markdown 文件可读、可改、可 Git 版本控制、容易审计查询能力弱,复杂关系难表达
关系型数据库精确查询强,结构化能力强人类直接查看和修改不方便
向量数据库适合语义召回可解释性弱,精确匹配不稳定

当用户想知道 Agent 到底记住了什么,MEMORY.md 可以直接打开;当某条记忆不准确,也可以手动改掉;当记忆发生变化,还可以用 Git 查看修改历史。

文件方案牺牲了一部分自动化检索能力,换来的是调试友好和人类控制权。

短期日志和长期记忆分层

OpenClaw 没有把所有内容都塞进一个文件,而是拆成短期层和长期层。

flowchart LR
    A[对话与任务过程] --> B[当日日志<br/>memory/YYYY-MM-DD.md]
    B --> C[近期上下文自动加载<br/>今天和昨天]
    B --> D[更早日志按需检索]
    B --> E[人工或自动提炼]
    E --> F[长期记忆<br/>MEMORY.md]
    F --> G[每次会话固定加载]

短期日志负责“不丢信息”。当天发生的对话、任务进展、临时线索,可以先追加进去,不要求立刻整理。

长期记忆负责“高频可用”。稳定事实、长期偏好、项目规则、反复使用的约定,应该沉淀到 MEMORY.md,因为它每次会话都会加载。

这解决了一个常见矛盾:Agent 需要记住很多东西,但上下文窗口不允许无限加载。短期层允许信息先落盘,长期层保证真正重要的信息常驻上下文。

问题也随之出现:哪些内容应该从日志升级成长期记忆?OpenClaw 的答案偏向人类主导,自动化机制只做辅助。这样记忆质量更可控,但用户需要参与维护。

混合检索:语义搜索加关键词搜索

只靠语义搜索会遇到一个问题:它擅长找“意思相近”的内容,却不擅长找“字面精确匹配”的内容。

比如用户搜索一个 API 路径、函数名、错误码,向量相似度可能返回一批语义相关但不准确的片段。OpenClaw 用混合搜索降低这个风险:

flowchart LR
    A[查询请求] --> B[语义搜索<br/>向量相似度]
    A --> C[BM25 关键词搜索]
    B --> D[候选片段]
    C --> D
    D --> E[排序与合并]
    E --> F[注入当前上下文]

BM25 是一种经典关键词相关性算法,适合处理精确词命中;向量搜索适合处理表达方式不同但含义接近的内容。两者并行,可以覆盖更多检索场景。

OpenClaw 的向量索引可以放在 SQLite 里,通过 sqlite-vec 这类扩展实现,不必额外部署独立向量数据库。这样安装和迁移更简单,但大规模数据下性能通常不如专用向量库。

Context Compaction 的风险和 Memory Flush

长会话会不断消耗 Token。接近上下文窗口上限时,系统通常会压缩历史对话,也就是 Context Compaction。

压缩本身没问题,但它会制造一个危险场景:

sequenceDiagram
    participant U as 用户
    participant A as Agent
    participant C as 上下文历史
    participant F as 文件记忆

    U->>A: 以后生成代码都遵守某条规则
    A-->>U: 好的
    A->>C: 规则只存在对话历史中
    Note over C: 会话变长,触发压缩
    C->>C: 历史被摘要替换
    A->>F: 没有写入文件
    Note over A: 规则丢失,后续行为可能违反约定

用户以为 Agent 已经记住了规则,但规则只停留在对话历史里。压缩发生后,原始对话被摘要替换,重要细节可能消失。

OpenClaw 用 Memory Flush 处理这个问题:在压缩前插入一个静默轮次,让 Agent 把当前上下文里的重要信息写入文件,再执行压缩。

flowchart TD
    A[检测到上下文即将压缩] --> B[触发静默 Memory Flush]
    B --> C[Agent 扫描当前上下文]
    C --> D[把重要事实、规则、进展写入日志或 MEMORY.md]
    D --> E[执行 Context Compaction]
    E --> F[继续会话]

这个机制把“写进文件才算长期记住”的原则变成系统保障,而不是完全依赖用户提醒。

Dreaming:自动沉淀长期记忆

OpenClaw 的 Dreaming 机制可以定期扫描日志,对内容进行评分,再把高价值内容提升到 MEMORY.md。它试图把人工整理的一部分工作自动化。

难点在于“高价值”很难用简单规则定义。某句话是否应该长期保存,取决于上下文、用户偏好、任务类型和后续复用概率。自动沉淀如果过于激进,会把长期记忆变成噪音堆;如果过于保守,又起不到积累作用。

所以 OpenClaw 的整体风格仍然是:人类可见、可改、可审计,自动化能力围绕这个原则展开。

Claude Code:上下文工程优先

Claude Code 的记忆系统不像 OpenClaw 那样强调“外部存储”,它更像是一套上下文工程系统。

核心判断是:Token 窗口是稀缺资源,不能只看窗口最大长度,还要考虑信息放在上下文里的位置。

语言模型存在 Lost in the Middle 现象:上下文开头和结尾的信息更容易被模型关注,中间内容更容易被忽略。也就是说,把大量记忆粗暴堆进上下文,不等于模型就会稳定使用这些记忆。

Claude Code 的重点不是“存更多”,而是“在正确时间,把正确内容放到正确位置”。

系统提示分层注入

Claude Code 每次调用模型前,会组装系统提示。这个组装过程不是简单拼接,而是按层次和条件注入。

flowchart TB
    A[模型调用前组装 Prompt] --> B[固定注入层]
    A --> C[条件注入层]

    B --> B1[Agent 身份与行为规范]
    B --> B2[编码原则]
    B --> B3[工具调用规则]
    B --> B4[风险操作确认逻辑]

    C --> C1[CLAUDE.md 规则文件]
    C --> C2[Git 状态快照]
    C --> C3[Skills 名称与描述]
    C --> C4[Token 预算指令]

固定注入层可以利用 Prefix Cache。Prefix Cache 指模型服务对稳定前缀做缓存,后续请求复用相同前缀时可以减少计算和费用。条件注入层则按需加载,避免每次调用都浪费 Token。

这种设计把记忆系统变成了 Token 预算分配器:哪些信息常驻,哪些信息按需进入,哪些信息只保留索引。

用路径编码相关性

Claude Code 的规则文件采用分层作用域:

~/.claude/CLAUDE.md          # 用户级:所有项目加载
~/project/CLAUDE.md          # 项目级:进入项目加载
~/project/src/CLAUDE.md      # 目录级:进入目录加载

当前工作目录决定加载哪些 CLAUDE.md 文件。路径结构本身就表达了相关性。

flowchart LR
    A[当前工作目录<br/>~/project/src] --> B[加载用户级 CLAUDE.md]
    A --> C[加载项目级 CLAUDE.md]
    A --> D[加载 src 目录级 CLAUDE.md]
    B --> E[组装系统提示]
    C --> E
    D --> E

这是一种非常工程化的设计:不依赖复杂检索算法,直接用文件路径做规则选择。查找成本低,行为也容易预测。

代价是动态性不足。路径能回答“当前目录适用哪些规则”,但不能很好回答“当前任务语义上需要哪些历史经验”。如果一个规则跨目录复用,或者一个任务需要过去某次类似任务的经验,单靠路径层级就不够。

Token 预算三档预警

Claude Code 会监控上下文消耗,并设置分级阈值:

Token 使用比例系统行为目的
约 70%提醒压缩可能接近让 Agent 提前调整计划
约 85%提醒压缩即将发生让 Agent 优先处理关键步骤
约 90%自动压缩,或停止并提示防止上下文溢出

更关键的是,Token 使用状态会进入 Agent 自身上下文。这样 Agent 在规划任务时可以知道“剩余预算”:

flowchart TD
    A[Token 使用率上升] --> B[预算状态注入上下文]
    B --> C[Agent 感知剩余 Token]
    C --> D[调整任务计划]
    D --> E[优先处理关键文件或关键步骤]
    E --> F[必要时准备压缩前总结]

这让记忆系统不只是被动后端,而是 Agent 推理的一部分。Agent 可以根据剩余上下文预算决定要不要继续读文件、是否先做总结、是否把关键中间结论保存下来。

Claude Code 的边界

Claude Code 对工程任务非常适合,尤其是规则清晰、项目结构明确、任务边界相对独立的代码场景。

它的短板也很明确:缺少强情景记忆。它可以加载规则,可以读取当前项目状态,可以管理上下文预算,但不会像 Hermes Skills 那样把“这类任务怎么做更好”沉淀成可迭代经验。

换句话说,Claude Code 更像一个擅长上下文调度的工程助手,而不是一个持续积累任务经验的学习型 Agent。

Hermes Agent:按访问模式分层,情景记忆是核心

Hermes Agent 的记忆设计更系统化。它不把所有记忆混在一起,而是按访问模式分成多层。

核心原则是:不同访问频率、不同稳定性、不同用途的信息,应该放在不同介质里,用不同方式访问。

flowchart TB
    A[Hermes Agent 记忆系统] --> B[热记忆<br/>每次会话固定注入]
    A --> C[历史归档<br/>按需搜索]
    A --> D[情景记忆 Skills<br/>任务经验]
    A --> E[用户建模<br/>可选增强]

    B --> B1[MEMORY.md<br/>环境事实]
    B --> B2[USER.md<br/>用户偏好]
    C --> C1[SQLite + FTS5]
    D --> D1[Markdown Skill 文件]
    E --> E1[Honcho 用户模型]

热记忆:小而稳定

Hermes 的热记忆通常放在两个文件中:

~/.hermes/memories/
├── MEMORY.md    # 环境事实,约 800 token 上限
└── USER.md      # 用户偏好,约 500 token 上限

这两个文件每次会话都会直接注入系统提示,不需要检索,访问延迟为零。

上限很小是一个关键设计。热记忆越大,越容易退化成垃圾桶;每条新内容都应该接受筛选:它是否足够稳定?是否经常用到?是否有资格占用系统提示空间?

小热记忆还有一个工程收益:更利于 Prefix Cache。固定且短的系统提示前缀更容易命中缓存。

历史归档:不自动加载,由 Agent 主动搜索

Hermes 会把会话历史存进 SQLite,并使用 FTS5 全文索引。FTS5 是 SQLite 内置的全文搜索能力,适合做关键词检索。

~/.hermes/state.db    # SQLite,包含会话历史与 FTS5 全文索引

历史归档不会每次自动进入上下文。Agent 判断当前任务可能需要历史时,才调用 session_search 工具搜索。

sequenceDiagram
    participant A as Agent
    participant DB as SQLite 历史归档
    participant L as 轻量 LLM 摘要
    participant C as 当前上下文

    A->>A: 判断当前任务需要历史信息
    A->>DB: 调用 session_search
    DB-->>A: 返回相关历史片段
    A->>L: 请求压缩摘要
    L-->>A: 返回摘要
    A->>C: 注入当前上下文

按需检索的好处是系统提示不会随着使用时间膨胀。历史只有在相关时才进入上下文。

缺点是 FTS5 语义理解弱。搜索 “auth service” 不一定能找到记录里写的“身份验证微服务”。如果任务大量依赖语义相似召回,可以用向量搜索增强这一层。

Skills:真正的情景记忆

Hermes 最有特点的是 Skills 系统。Skills 不是存事实,而是存经验。

事实记忆回答“用户喜欢什么”“项目目录在哪”;情景记忆回答“遇到这类任务时,怎么做效果更好”。

目录结构类似这样:

~/.hermes/skills/
├── research-workflow.md      # 研究类任务流程
├── image-generation.md       # 图片生成任务经验
└── data-analysis.md          # 数据分析任务方法

一个 Skill 文件可以记录:

  • 适用任务类型;
  • 推荐执行步骤;
  • 常用工具组合;
  • 过去遇到的问题;
  • 解决方法;
  • 哪些做法效果不好。

Hermes 会在复杂任务完成后评估执行过程。如果任务包含多次工具调用,并且过程有复用价值,Agent 可以把经验写成 Skill。

渐进式披露:先加载索引,再加载全文

如果每个 Skill 都完整注入上下文,很快就会撑爆 Token。Hermes 用渐进式披露解决这个问题。

flowchart TD
    A[会话开始] --> B[加载 Skill 名称和描述]
    B --> C[Agent 判断当前任务是否相关]
    C -->|不相关| D[不加载完整内容]
    C -->|相关| E[加载完整 Skill]
    E --> F[按 Skill 执行任务]
    F --> G[发现更好做法]
    G --> H[更新 Skill 文件]

平时只加载 Skill 的名称和描述,Token 占用很低。只有当前任务和某个 Skill 相关时,才加载完整内容。

这让 Agent 可以积累几十个甚至上百个 Skill,而不会让上下文失控。

Skill 自我更新

Hermes 的情景记忆还有一个重要点:Skill 可以在使用中更新。

Agent 使用某个 Skill 执行任务,如果发现某个步骤不够好,或者某个工具组合更稳定,就可以修改 Skill 文档。这样经验会随着任务执行而迭代。

这是情景记忆和普通历史记录的区别:

类型记录内容是否指导未来任务
会话历史过去发生了什么需要检索和总结后才能使用
事实记忆稳定事实和偏好能提供约束,但不描述过程
Skill某类任务怎么做直接指导下一次执行

Hermes 因此更适合长期运行、重复任务多、希望 Agent 越用越熟悉流程的场景。

深度用户建模

Hermes 还可以接入 Honcho 做用户建模。用户建模不只是记录“用户说过什么”,还会尝试从长期交互中推断用户偏好、表达习惯、决策风格。

这一层很重,需要额外服务,也会带来隐私和数据治理问题。对于个人长期助手、强个性化 Agent,它可能有价值;对于普通工程任务,热记忆和 Skills 通常已经够用。

三种架构的核心差异

三种框架的差异可以压缩成一张表。

维度OpenClawClaude CodeHermes Agent
核心理念文件系统是长期记忆事实来源Token 预算和上下文调度优先按访问模式分层,积累情景经验
主要介质Markdown 文件、SQLite 向量索引分层规则文件、动态系统提示Markdown、SQLite、Skills、可选用户模型
长期记忆MEMORY.mdCLAUDE.md 规则文件MEMORY.mdUSER.md
短期记忆日志文件当前上下文和项目状态会话历史数据库
情景记忆Dreaming 方向的实验机制不突出Skills 是核心能力
检索方式语义搜索 + BM25路径层级加载,按需注入FTS5 搜索,Agent 主动调用
人类可控性很强,文件可直接编辑中等,规则文件可编辑中等偏强,Skill 和记忆文件可编辑
系统复杂度中等中等偏低较高
主要代价需要维护记忆质量缺少强经验积累架构复杂,需要长期使用才能体现价值

OpenClaw 的取舍

OpenClaw 把可见性放在第一位。记忆写在文件里,用户可以直接看、直接改、直接版本控制。

适合场景:

  • 用户希望完全知道 Agent 记住了什么;
  • 记忆错误需要快速人工修正;
  • 调试和审计比自动化程度更重要;
  • 个人 Agent 或小规模工作区。

不适合场景:

  • 需要复杂结构化查询;
  • 记忆量很大,检索性能要求高;
  • 用户不愿意维护长期记忆文件。

Claude Code 的取舍

Claude Code 把上下文窗口当作核心资源来管理。它不追求无限记忆,而是尽量让当前任务需要的信息准确进入 Prompt。

适合场景:

  • 代码工程任务;
  • 项目规则可以按目录组织;
  • 任务之间相对独立;
  • Token 成本和上下文质量都很重要。

不适合场景:

  • 需要从长期执行经验中持续学习;
  • 任务规则不是目录相关,而是语义相关;
  • 需要跨项目复用复杂经验。

Hermes Agent 的取舍

Hermes 追求长期积累。热记忆、历史归档、Skills、用户建模各自承担不同职责,避免所有记忆混成一团。

适合场景:

  • 长期运行的个人助手;
  • 重复性任务很多;
  • 任务流程可以不断改进;
  • 需要让 Agent 从过去执行中学习。

不适合场景:

  • 一次性任务;
  • 不希望引入复杂存储和后台评估;
  • 对隐私和数据驻留要求非常严格,但没有配套治理能力。

设计自己的 Agent 记忆系统时,先做三组选择

如果要从零设计一个 Agent 记忆系统,不必一开始就做成 Hermes 那样完整。更稳妥的方式是先回答三组问题。

透明性还是自动化

如果用户必须能直接审计和修改记忆,文件方案更合适;如果系统希望自动组织大量历史,数据库和检索系统更合适。

目标推荐设计
人类可读、可改、可 Git 管理Markdown 文件
精确查询、结构化管理SQLite 或关系型数据库
大规模语义召回向量库或 SQLite 向量扩展
复杂经验积累Skill 类情景记忆

常驻记忆还是按需检索

常驻记忆访问最稳定,但会占用 Token;按需检索节省上下文,但依赖检索判断。

一个实用分层可以这样设计:

flowchart TB
    A[所有候选记忆] --> B{是否每次都需要?}
    B -->|是| C[热记忆<br/>固定注入]
    B -->|否| D{是否需要长期保存?}
    D -->|否| E[只留在当前上下文]
    D -->|是| F{是否描述任务过程?}
    F -->|是| G[情景记忆 Skill]
    F -->|否| H[历史归档或外部记忆]

热记忆一定要小。可以给自己设硬限制,例如:

hot_memory:
  environment_facts_max_tokens: 800
  user_preferences_max_tokens: 500

archive:
  storage: sqlite
  search:
    - fts5
    - vector_optional

episodic_memory:
  format: markdown_skill
  load_strategy: progressive_disclosure

记忆更新由谁负责

记忆更新可以由人负责,也可以由 Agent 自动负责,或者采用混合模式。

更新方式优点风险
人工维护准确、可控成本高,容易忘记维护
自动写入省人工,覆盖更多上下文容易写入噪音或错误结论
自动建议 + 人工确认平衡质量和效率交互链路更复杂
自动写入 + 可审计回滚适合长期系统需要日志和版本管理

比较稳的做法是:短期日志自动写,长期记忆谨慎写,关键规则变更可人工确认。

常见坑

把所有历史都自动注入上下文

历史越多,模型越不一定用得好。过多无关信息会稀释关键指令,还会触发 Lost in the Middle。

更好的做法是:热记忆固定注入,历史按需检索,检索结果再摘要压缩。

只用向量搜索

向量搜索适合语义召回,但对函数名、路径、错误码、配置键这类精确内容不稳定。Agent 记忆系统通常需要混合检索:

flowchart LR
    A[用户问题] --> B[关键词搜索]
    A --> C[语义搜索]
    B --> D[合并排序]
    C --> D
    D --> E[去重、摘要、注入]

不处理压缩前的记忆落盘

长会话压缩前,必须把关键事实、约定和中间结论保存到外部记忆。否则用户刚刚交代的重要规则可能在压缩后消失。

长期记忆没有上限

长期记忆不是越多越好。热记忆尤其要严格限制大小。可以定期做三件事:

  • 删除过时事实;
  • 合并重复规则;
  • 把低频内容移入可检索归档。

没有纠错入口

Agent 可能会记错。如果用户无法查看和纠正记忆,系统会逐渐失去可信度。即使使用数据库,也应该提供可审计、可编辑、可回滚的入口。

一个简化版落地方案

综合三种架构,可以设计一个轻量但可扩展的 Agent 记忆系统:

~/.agent/
├── memories/
│   ├── MEMORY.md          # 热记忆:稳定事实,限制 token
│   └── USER.md            # 热记忆:用户偏好,限制 token
├── logs/
│   ├── 2026-06-07.md      # 短期日志
│   └── ...
├── skills/
│   ├── code-review.md     # 情景记忆
│   └── data-analysis.md
└── state.db               # 历史归档与索引

运行流程可以设计成这样:

flowchart TD
    A[新请求进入] --> B[加载热记忆]
    B --> C[加载当前任务上下文]
    C --> D{是否需要历史?}
    D -->|是| E[搜索 state.db]
    E --> F[摘要后注入]
    D -->|否| G[跳过历史检索]
    F --> H{是否匹配 Skill?}
    G --> H
    H -->|是| I[加载完整 Skill]
    H -->|否| J[正常执行]
    I --> J
    J --> K[写入短期日志]
    K --> L{是否产生可复用经验?}
    L -->|是| M[创建或更新 Skill]
    L -->|否| N[结束]

这样的系统吸收了三种思路:

  • 像 OpenClaw 一样保留可读文件;
  • 像 Claude Code 一样控制常驻上下文;
  • 像 Hermes 一样把经验沉淀为 Skill。

记忆系统的目标不是让 Agent 什么都记住,而是让它在需要的时候拿到正确的信息,并且能把重复任务中的有效做法沉淀下来。OpenClaw、Claude Code、Hermes Agent 的差异,本质上就是在透明性、上下文效率和长期学习能力之间做取舍。


评论