资讯详情

Hermes Memory双闸机制:跨会话记忆与用户画像的工程实践

📅 2026/10/14 4:49:42 | 华诺云谱 👁 阅读
Hermes Memory双闸机制:跨会话记忆与用户画像的工程实践
1. Hermes Memory 这套记忆方案的出发点做 Agent 久了大家基本都会撞上同一个尴尬场景用户早上说完“我喜欢极简风格的装修”下午换了个频道聊起家常晚上再回来问“刚才说的那个方案呢”系统一脸茫然仿佛这段对话从来没发生过。这就是典型的跨会话记忆缺失。我这次整理的项目是某开发者为解决这个问题设计的记忆层项目代号Hermes Memory。它的定位很明确在会话引擎和用户画像之间加一层可控、可追溯、可持久化的记忆通道核心是两个闸门——有界闸与冻结闸。先说清楚这个方案解决的三类问题会话重启后关键上下文不丢失用户不用重复交代背景和偏好对用户的描述从“一次性聊天记录”升级为“可积累的画像资产”后续对话能够基于历史偏好做个性化回复同时避免无限堆积的信息导致检索失控、画像漂移、旧错新错互相污染。这套设计适合谁参考一句话任何正在做个人助理、陪伴型Agent、垂类客服机器人、或者大模型对话系统的人。如果你已经过了“单轮问答”阶段开始思考“怎么让系统越来越懂用户”那 Hermes Memory 的“有界冻结”思路会给你不少启发。我个人为什么觉得“双闸”值得单独拿出来写因为市面上大部分记忆方案只解决了“存储”和“召回”没人认真回答“哪些记忆该留、哪些该锁、哪些该修剪”。Hermes 有意思的地方在于它把记忆当成了一套需要经营的数据资产而不是一条流水账。2. 有界闸给记忆划定一个物理天花板2.1 为什么记忆必须“有界”很多做记忆系统的第一反应是多存点总没错。实际跑起来会发现记忆无限膨胀的后果非常现实检索结果严重稀释。向量召回 TOP-K 时相关的记忆片段被大量“寂寞”的聊天记录挤掉真正有用的画像信息反而排不到前面上下文窗口撑爆。每次组装 Prompt 时如果历史记忆无边界地塞进去token 成本飞快上涨响应延迟也在涨模型注意力涣散。几千字的无关历史混在里面LLM 很容易被带偏答非所问的比例直线上升。所以 Hermes 的有界闸本质上做的就一件事给每条记忆、每一层存储都划定明确边界超出边界的记忆要么被压缩、要么被淘汰、要么被归档。这个设计理念和生活里“断舍离”是一个道理。房间只有这么大硬塞进去库房里的所有旧报纸真正常用的东西反而找不到了。2.2 有界闸的三级容量控制Hermes Memory 把记忆分成三层每一层都有独立的容量上限互不侵占工作记忆短期层容纳最近 N 轮对话的原始要点容量阈值为 200 条记忆项一旦超出最早的非核心项触发压缩流程长期记忆画像层从工作记忆中提炼出来的用户偏好、事实描述、行为模式容量阈值为 1000 条超出时按重要性和置信度分档淘汰低价值项核心档案固化层用户主动声明或系统多次确认的信息容量只有 50 条超出时进入人工/复审流程而不是自动删除。我听到有人会说“50条太少了吧”这里解释一下核心档案装的是“用户真实姓名、不可变更的偏好、禁忌事项”这类高权重信息刻意把容量做小是为了强制系统持续做减法保证每一条都是精兵而不是什么都往里面塞。注意边界的意义不只是控制体积更在于约束“召回路径”。有界之后检索的时候可以直接按层过滤不需要每次在全量记忆海里捞针。2.3 压缩与淘汰的策略选择有界闸不只是一个“满了就删”的简单动作。Hermes 里用了三档处理手段按优先级从低到高第一档摘要化压缩。多条同主题、同场景的记忆被合并成一条浓缩记录。比如 10 条“中午吃了麻辣烫”“下午想喝冰美式”细节会被压缩成“用户偏好川辣口味日常饮食重油重辣忌口香菜”。这个操作我建议放在每晚的闲时任务里跑避免影响在线响应。第二档时效性衰减。每条记忆带一个last_accessed_at时间戳长期没有被调用的记忆分数逐渐下降。这里我用的衰减公式是score base_score * pow(decay_factor, days_since_last_access)衰减因子设 0.95也就是说大约 14 天没被激活的记忆分数会降到原来的一半左右。这个参数不是拍脑袋定的我实测过几组对比0.90 太激进容易误杀周期性偏好比如“每周末去爬山”0.98 又太保守失效记忆占着坑位不吐出来。第三档负面淘汰。如果一条长期记忆在后续对话中反复被新事实推翻系统直接标记为“过期”并移出活跃区。例如用户搬家后新地址确认过三次旧地址记忆就进入淘汰候选区。这套三级处理合起来的逻辑是能压缩的不删除能衰减的不硬删真正被证伪的才移出。2.4 有界闸的参数配置与调优从工程落地角度我最想提醒的是有界闸的三级阈值应该独立可配而不是一套参数全局生效。工作记忆容量取决于单次会话平均轮数 × 每轮需要保留的关键信息数。比如你的 Agent 平均每次聊 20 轮、每轮产生 8 个信息点那工作记忆 200 条就是为 10 次会话余量设计的长期记忆容量结合实际召回精度的衰减拐点来定。我在实验里看到超过 800 条之后向量召回 TOP-10 时相关项占比从 82% 下降到 64%收益递减明显所以 1000 上限算是留了缓冲击穿的余量核心档案容量这个保守一点宁缺毋滥50 条足够覆盖一个自然人长期稳定的关键画像维度。实操心得参数不要一配置就上线先跑一周模拟数据重点观察“淘汰命中率”——有多少次淘汰操作是真正把低价值记忆移出去的。如果命中率低于 60%说明阈值定得不对边界内装了大量本该压缩的冗余项。3. 冻结闸让高价值记忆不可被篡改3.1 为什么要设置“冻结”机制记忆系统有个隐蔽的坑画像漂移。举个例子。用户第一次说“我喜欢深色系风格”系统记住了。第二次用户随手发了一句“黑色看着有点闷”系统如果把这个理解为“用户不喜欢深色”就会把画像从“偏好深色”悄悄改成“排斥深色”。第三次用户再回来聊装修系统给的建议就变得非常拧巴。这不是模型理解能力的问题而是记忆系统缺少“哪些信息可以更新、哪些必须锁定”的权限边界。Hermes 的冻结闸就是针对这个痛点设计的一旦某条记忆经过充分确认进入核心档案并冻结普通对话内容没有权限修改它必须走专门的解冻流程才能变更。生活化类比书架上用玻璃罩罩住的那本旧相册你可以看、可以引用但随手拿记号笔往上写字是不行的。真要改里面的照片得打开罩子确认是“本人要换照片”再操作。3.2 冻结对象的三个层级我落地时把“可以冻结的东西”分了三种它们的作用和操作权限完全不同用户声明事实用户主动说出的、明确性的个人信息例如“我住在杭州”“我家里三只猫”“我对花生过敏”。这类信息冻结优先级最高不可覆盖跨轮重复确认的偏好同样或高度相似的偏好出现在 3 次以上不同会话中系统自动提升为候选冻结项等待二次确认后才锁定系统推导的高置信结论比如通过用户行为数据推断出的“阅读习惯更偏好短文”如果置信度超过 0.9 且连续多轮验证无冲突也可以进入冻结区。这类冻结可以解冻但必须经过完整复核流程。我在设计时的原则是用户直接说的 行为推导的 单次对话里的临时印象。前两者可以冻结第三个永远不进冻结区。3.3 冻结管理的“锁状态机”冻结闸的真功夫在状态管理上。Hermes 给每条记忆设计了简单的状态机四个状态pending - confirmed - frozen ↑ | └-- unfrozen -┘pending新提取的记忆未经确认可修改可删confirmed经过一次以上确认权重较高修改需要触发逻辑校验frozen核心档案内锁定普通写入操作直接拒绝unfrozen临时解冻状态只在显式变更流程内存在变更完成后重新进入frozen。这里最容易踩的坑是设计成“一旦冻结就永不可改”。但真实场景里用户一定会搬家、换工作、改口味所以必须留有受控的解冻通道。Hermes 的做法是解冻操作必须记录unfreeze_reason和来源对话 ID所有变更留痕方便追责和回溯。3.4 冻结与有界的联动规则两个闸门不是孤立的它们在运行时协同工作这也是 Hermes 记忆设计比较扎实的地方有界闸的淘汰流程不能越过冻结闸处于frozen状态的记忆不参与衰减评分不会因为长期未访问而被自动清理。如果容量压力迫近上限系统优先压缩和淘汰未冻结的低分项冻结闸的新增来源必须经过有界闸一条记忆不能直接写入冻结区它得先出现在工作记忆里经过提炼、确认、再确认才可能升格为冻结项容量冲突时走人工降级当核心档案满 50 条且需要新冻结某条信息时Hermes 不自动替换而是生成“冻结冲突告警”交由运营人员判断是否解冻旧项。我最想提醒的是双闸设计最难的不是实现状态机而是想清楚“谁有权限打破规则”。我见过不少系统把冻结设计成硬编码最后用户改个手机号都改不动体验非常糟糕。正确的做法是保留受控的变更通道而不是把所有入口都封死。4. 跨会话记忆与 USER 建模的工程联动4.1 从记忆到用户画像的数据流水线记忆系统的价值最终要体现在“系统更懂用户”上。Hermes 里的 USER 建模不是单独建一个表存用户属性而是从记忆数据里实时汇总画像。我给的流水线结构是对话原始记录 - 信息抽取 - 记忆条目写入(工作记忆) ↓ 主题分类 实体链接 ↓ 画像槽位更新(长期记忆/核心档案)关键点在于画像槽位不是直接由对话内容覆盖而是由记忆条目累加而成。每一个槽位比如“饮食偏好-辣度”背后挂着来源记忆列表。查询画像时系统给出的是综合多条记忆的结论而不是孤零零一条记录。这样的好处是一条与旧画像冲突的新记忆进来时不会瞬间推翻旧结论而是先引起“置信度波动”等多次冲突确认后才触发画像槽位的重写。这个机制天然缓解了前面说的画像漂移问题。4.2 画像槽位与记忆条目的双向映射USER 建模需要定义好“画像维度”我用三四层标签体系来组织基础属性层年龄区间、地域、职业类型这些来自用户声明冻结度高行为偏好层内容偏好、消费习惯、活跃时段这些来自行为观察更新频率高交互风格层回复长度偏好、语气偏好、敏感话题禁忌这些来自多轮对话统计中等冻结度临时状态层最近情绪、当前关注热点这些存在工作记忆即可不进入长期层。每一层与记忆条目的映射关系维护在字典表里例如content_preference.reading_length这个槽位可能由十几条pending或confirmed状态的记忆共同支撑。4.3 跨会话召回时的组装策略用户发起新会话时Hermes 会按以下顺序组装跨会话上下文拉取核心档案中所有冻结条目直接填入系统提示词从长期记忆中按画像槽位召回 Top-K 条相关记忆附加时间戳从工作记忆中提取近期 N 轮摘要作为对话背景临时状态层的信息走轻量注入不占用主要 token 预算。这套顺序排下来理论上token 分配比例大致为 冻结画像 35%、长期偏好 40%、近期摘要 20%、临时状态 5%。我踩过的坑早期把召回权重全部押在“相关度”上结果一些冷门但高价值的画像比如过敏信息反而排不进去。后来调整为先按画像槽位过滤再在槽位内排序相关度和高价值信息都能照顾到这个改动对体验提升非常明显。4.4 用户建模中最容易被忽略的“负向记忆”大部分建模系统只记录“用户喜欢什么”却很少系统性地记录“用户讨厌什么”。Hermes 里专门留了一类负向记忆独立存放独立加权。负向记忆包括用户明确反感的表达、拍板否决的方案、连续纠正过两次以上的内容。我建议把负向记忆的权重乘一个 1.2 的惩罚系数宁可用错也不用触雷。比如用户说过一次“别再给我推荐短视频了”那这条负向记忆在后续两周内都保持高召回优先级直到用户主动点开三次短视频后才降权。5. 实操落地我实现 Hermes Memory 的完整过程5.1 存储选型与表结构设计这块我认为是最值得抄作业的环节。实际实现时我用的是组合存储而不是单一数据库元数据与状态用关系型库存储字段包括memory_id、user_id、layer、state、content_type、decay_score、created_at、last_accessed_at向量化内容用向量库存储保存内容的 embedding用于语义检索画像槽位表单独的宽表一行一个用户每列对应一个画像维度值里存的是记忆 ID 列表。关键表的大致结构CREATE TABLE memory_items ( memory_id VARCHAR(64) PRIMARY KEY, user_id VARCHAR(64), layer ENUM(working,longterm,core), state ENUM(pending,confirmed,frozen,unfrozen), content TEXT, content_hash VARCHAR(64), importance FLOAT, decay_score FLOAT, source_dialog VARCHAR(64), created_at TIMESTAMP, last_accessed_at TIMESTAMP, INDEX idx_user_layer (user_id, layer), INDEX idx_user_state (user_id, state) );字段content_hash是我后来加的。因为同一个事实可能在多轮对话里被重复抽取System Prompt 里会塞进重复信息。加了这个字段之后写入前先查哈希命中就直接更新权重不再新增记录干净很多。5.2 记忆写入主链路实现写入流程的核心代码思路如下我贴的是简化版伪代码def write_memory(user_id, dialog_turn): extracted_items extract_memories(dialog_turn) for item in extracted_items: # 1. 哈希去重 existing find_by_hash(item.content_hash) if existing: existing.decay_score min(existing.decay_score 0.1, 1.0) existing.last_accessed_at now() continue # 2. 候选冻结判定 if is_declared_fact(item) or repeat_count(item, threshold3): state pending else: state pending # 3. 写入工作记忆 insert_memory(user_id, working, state, item) # 4. 有界闸容量检查 if working_memory_count(user_id) 200: compress_or_evict(user_id, working)值得注意的是写入主链路里没有“直接写冻结区”的路径。所有冻结项都要从pending经过确认升级再进入冻结。这个设计是我故意做严的防止模型对话中出现“用户随口开玩笑说要住在月球”这种信息被误冻结。5.3 冻结闸的确认升级与解冻流程升级逻辑上我设定了一条记忆从pending到frozen最少需要经历 2 次独立确认。所谓“独立确认”指的是不同会话轮次中对同一事实的复述或强相关表达。用伪代码表示def confirm_and_maybe_freeze(user_id, memory_item): if memory_item.state pending: memory_item.confirm_count 1 if memory_item.confirm_count 2 \ and memory_item.layer longterm: memory_item.state confirmed if memory_item.state confirmed: if is_core_attribute(memory_item) \ and core_frozen_count(user_id) 50: memory_item.state frozen memory_item.layer core解冻入口我做了两个一是用户主动发出修改指令二是系统在对话中发现矛盾次数超过阈值时自动发起复核。从我跑过的测试数据看99% 的场景走的是第一个入口用户直接说“其实我搬家了现在住苏州”系统定位到旧地址记忆确认后执行frozen - unfrozen - 修改 - frozen的完整链路。5.4 召回阶段双闸的配合召回阶段需要同时读取冻结画像和非冻结偏好但两条路径的加权方式不一样。我把代码里的关键逻辑列出来def assemble_context(user_id, query): # 冻结区全量加载不参与打分 core_items load_frozen(user_id) # 非冻结区按画像槽位召回并对候选打分 longterm_items recall_by_slot(user_id, query, top_k10) # 分数衰减与重要性加权 for item in longterm_items: score item.importance * item.decay_score item.rerank_score score # 组装 system prompt prompt format_core(core_items) format_preferences(longterm_items) return prompt这里最容易出错的是冻结区虽然不参与打分但它也会占 token。我在一次压测中发现冻结区写得太啰嗦会把非冻结召回的 40% 预算挤掉导致个性化回复明显变差。后来强制对冻结区内容做摘要模板化比如“居住地: 苏州”而不是原句“反正我现在一直住在苏州那边园区那一块”token 开销直接降了一半。5.5 数据回流与画像更新闭环记忆系统的闭环不能缺“回流”环节。每晚我处理三类数据对当天新增的confirmed状态记忆做批量确认观察是否满足升级条件对长期未被访问的pending记忆做清理避免工作记忆层囤积垃圾对画像槽位和真实用户行为做离线比对计算画像准确率和漂移率。有一次比对发现“内容偏好-视频时长”槽位的准确率只有 51%排查下来是因为某几场对话里系统把用户分享给别人的视频链接误解为用户的个人偏好。后来我给画像更新加了“行为主体识别”要求区分“用户自己观看”和“用户转发给他人”准确率拉回了 80% 以上。6. 常见问题与避坑实录6.1 记忆污染如何避免旧信息干扰新对话现象用户上个月说喜欢某品牌家居这个月已经明确改口但系统还在拿旧偏好做推荐。根因是画像更新太慢或者旧结论权重太高。我的解决方案是双轨并进既给每条长期记忆设周期衰减又在用户明确表达冲突时执行“冲突优先覆盖”规则让新结论在 48 小时内生效而不是等衰减慢慢磨。特别注意千万不要因为用户一次情绪化发言就推翻长期冻结偏好。比如用户吐槽“做饭好烦”并不代表他不喜欢烹饪这种临时状态只进入工作记忆层不触碰画像槽位。6.2 并发写入冲突多轮对话同时改写同一记忆实际运营中一定会遇到用户在 App 和 Web 两个端同时聊两条线程同时对同一条记忆做确认。我处理方式是在关系库里加了乐观锁用version字段做控制UPDATE memory_items SET state confirmed, version version 1 WHERE memory_id ? AND version ?;如果更新影响行数为 0说明版本冲突重新读最新数据再做合并判断。实测下来这个策略能解决 99.7% 的丢失更新问题。6.3 跨会话记忆的“超长尾”遗忘有人问有界闸把长期记忆上限设在 1000 条但用户用了半年之后一些很早期的偏好会不会被误杀答案是会但我刻意留了一个历史归档区。被淘汰的记忆不是物理删除而是打成压缩包存入冷存储。当用户新会话中重新提到相关主题时系统会先查活跃区再查归档区如果归档区有高相关且时效内一年以内的记录会重新激活回长期记忆层。这个“冷热分层”的代价是多了一次冷存储查询但对于长期用户的价值覆盖完全值得。6.4 大模型伪造记忆如何防止幻觉写入这是我最想强调的问题。模型在对话中可能凭空捏造用户偏好比如用户根本没说过的话被抽取进记忆。Hermes 的防线是所有抽取结果必须挂载到原始对话 ID 上并且内容必须来自用户发言。系统侧生成的内容不参与记忆抽取。同时对于pending状态的高价值断言我会引入“延迟确认”机制等下一次自然对话中出现用户真实表达的佐证才升格。这一条防线极大降低了幻觉写入率从测试数据看未加这层验证时错误记忆占总量约 4.7%加上之后压到 1.1% 左右。7. 这套方案的边界和可扩展方向7.1 什么场景不适合用双闸设计说实话双闸设计不是银弹。如果你的 Agent 只做一次性问答不需要跨会话记忆这套东西就是重负如果你的系统用户量极小且画像极其简单直接用一张配置表就够了没必要引入状态机和冷热分层。另外如果模型接的是高度敏感的内容场景医疗、金融记忆冻结的权限控制还需要更严格的合规审计这套方案里的“人工降级”通道就要升级成“必须人工确认”的强约束不能靠自动流程。7.2 可扩展方向群体画像与多用户记忆Hermes 目前是单用户的记忆系统。后续可以扩展的方向有两个一是群体画像聚合在用户授权范围内把同类型用户的行为偏好聚合成群体特征用来冷启动新用户或改进推荐策略而不需要每个新用户从头积累记忆。二是记忆共享与权限管理家庭成员共用同一套 Agent 时不同成员的记忆需要隔离和共享并存。冻结闸在这个场景下可以演变为“谁有权限修改哪条记忆”的权限模型有界闸则演变为“每个成员的记忆配额”。我实测的感受是把双闸做成权限模型后多用户场景的适应性会大幅提升值得做成独立的组件服务而不是内嵌在单机代码里。8. 从零到一落地 Hermes Memory 的一些实在建议如果看完上面这些内容你决定在项目里也做一套类似的东西我给几条顺序上的建议第一先定你的记忆边界。写代码之前先把用户画像里最核心的 20 个槽位列出来再倒推工作记忆和长期记忆各需要多少条。参数先保守再放开不要一上来就追求“存得多”。第二冻结闸的优先级高于有界闸。很多团队做反了先把淘汰逻辑写好再想哪些该锁结果锁的规则处处受制于淘汰逻辑。先定义不可变资产再考虑可变资产怎么流动。第三量化反馈很重要。我没有给你一套现成的指标但我强烈建议你在上线前就制定好三个指标召回相关率、画像准确率、用户重复表达率用户不需要重复描述同一件事的比例。这三个指标可以完整评价一套记忆系统是否真的在干活。第四不要害怕做减法。记忆系统设计不是加法题边界、淘汰、锁定每一个机制都是为了在某些时刻主动“忘记”。记住该记住的锁住该锁住的忘掉该忘掉的这才是跨会话记忆的核心能力。我在这套方案的多次迭代里最深的一个体会是用户并不会因为系统“记得多”而觉得智能而是会因为系统“记得准、不啰嗦、不会翻旧账”而觉得贴心。Hermes 的双闸设计本质上就是在帮系统划清“什么事值得记忆”和“什么记忆值得坚持”这两条界线。希望这个方案的思路能给你正在折腾的 Agent 项目带来一点启发。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑