恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
多Agent记忆孤岛怎么破?开源Memmy统一共享记忆实战指南
首页
资讯中心
/
多Agent记忆孤岛怎么破?开源Memmy统一共享记忆实战指南
多Agent记忆孤岛怎么破?开源Memmy统一共享记忆实战指南
发布时间:2026/8/30 15:01:47
刚接触多 Agent 项目的时候我踩得最多、也最难受的坑就是“每个 Agent 各记各的”。Agent A 刚和用户确认过工作日上午联系Agent B 接棒后完全不记得又追问一遍Agent C 需要做质检复盘却拿不到完整上下文只能从日志里手工拼。数据散落在不同 Agent 的内部状态里既没有统一结构也没有共享入口。这种“记忆孤岛”问题在多 Agent 场景里几乎难以避免。最近我在整理 Agent 记忆相关方案时正好看到 MemOS 团队开源了 Memmy主打统一多 Agent 记忆。这篇文章会围绕多 Agent 记忆这个主题展开先梳理核心概念和痛点再结合 Memmy 这类开源方案拆解记忆服务的整体设计最后给出一个两个 Agent 共享客服记忆的实战示例并附上常见问题和工程建议。如果你是做 Agent 开发、Agent 框架集成或者单纯想了解 Agent 记忆怎么落地都会用得上。1. 多 Agent 项目的“记忆孤岛”问题1.1 Agent 记忆到底是什么Agent 记忆可以从三个层面理解。第一是“会话内记忆”。比如用户连续问了几个问题每个问题单独看可能缺少上下文Agent 需要把本轮对话历史带上来才能理解“那个功能和这个功能有什么区别”。这类记忆通常在请求内部传递对话一结束就基本失效。第二是“长期记忆”。比如用户在历史会话中说过“我偏好 QQ 邮箱”这个信息往往值得跨会话保留。后面用户再来咨询时Agent 能直接读取偏好而不是重新问一遍。这类记忆一般需要持久化存储比如数据库、向量库或专门的记忆服务。第三是“共享记忆”。这属于多 Agent 场景下的特殊需求多个 Agent 共同读写同一份业务上下文。比如客服 Agent 负责接待质检 Agent 负责回访两个 Agent 需要知道同一个用户的历史偏好、最近工单状态和沟通禁忌。共享记忆的核心不是“能存多少”而是“谁能读到什么”。在传统单体架构中记忆问题往往被业务库顺带解决了。但在多 Agent 架构中不同 Agent 可能由不同团队维护使用不同的模型和编排框架记忆如果不做统一抽象很容易变成各自为政。1.2 多 Agent 协作场景中的记忆困境现在很多 Agent 项目已经不是“一个 Agent 包打天下”而是拆成多个专职 Agent意图识别、信息检索、方案生成、执行工具、质检反馈等。Agent 之间需要通过某种机制协作而协作又依赖上下文传递。这个环节最容易出现三类问题。第一上下文不共享。Agent A 在大模型对话中积累的信息只存在于 Agent A 的运行时上下文里。Agent B 启动时是“全新状态”无法感知用户刚刚表达过的偏好或历史操作记录只能靠外部系统硬编码传递一旦遗漏就造成体验割裂。第二记忆结构不统一。有的 Agent 用 JSON 存有的用文本块存有的直接塞在 Redis 里。检索时无法统一处理更别说做跨 Agent 的过滤、排序、过期和权限控制了。第三记忆冲突与污染。两个 Agent 同时对同一用户写入偏好如果没有版本控制或冲突处理后写入的容易覆盖先写入的甚至把噪声记忆当作正式偏好保存导致后续所有 Agent 都被污染。一句话总结多 Agent 协作的瓶颈往往不是模型能力而是记忆共享能力。1.3 MemOS 与 Memmy统一多 Agent 记忆的开源方案Memmy 是 MemOS 团队开源的一个多 Agent 记忆方案。关于 MemOS 和 Memmy 的更详细背景建议大家直接查看官方仓库的说明这里只基于社区公开信息介绍我的理解。从项目定位来看Memmy 想解决的就是“多 Agent 记忆统一”问题。它更像一个独立的记忆服务或记忆中间件负责将 Agent 运行过程中产生的关键信息抽取、存储、索引并提供统一的读取和检索接口。这样做的好处是Agent 框架可以保持轻量专业的事交给专业的记忆层来做。一个典型的架构关系如下Agent A → 写记忆 → Memmy统一记忆服务 → 检索 → Agent B ↑ ↑ └──────── 对话上下文 / 用户偏好 ─────────┘在这个模型里Agent A 不需要知道 Agent B 怎么存储Agent B 也不需要关心 Agent A 的运行时结构双方只依赖统一的记忆接口。这个思路和很多企业做“统一配置中心”“统一日志平台”的动机是一致的避免各模块自建一套减少重复建设和维护成本。需要注意的是Agent 记忆本身是 Agent 开发中的一个独立方向有很多同类方案。Memmy 的定位、底层检索能力、是否需要配合特定向量库都还在快速演进中。所以我建议把它当作一个“可参考的工程化思路”来学习同时结合自己项目的实际需求选型。2. Memmy 核心概念与整体设计要在项目里用好一个记忆服务不能只把它当“数据库”来用。需要先理解它抽象出来的核心概念才能设计出合理的接入方式。2.1 核心抽象Memory Item、Namespace、RetrievalMemmy 这类统一记忆服务通常会抽象出几个核心概念。第一个是“记忆条目Memory Item”。一条记忆往往包含唯一 ID、内容文本、元数据、创建时间等信息。内容文本可以是一段自然语言也可以是从对话中抽取的结构化描述。元数据则用来挂接业务信息比如 user_id、agent_id、scene、priority 等。第二个是“命名空间Namespace”。命名空间用来隔离记忆范围。不同业务、不同环境、不同租户的记忆可以放在不同命名空间下避免互相干扰。比如客服业务和销售业务完全独立就可以划分成两个命名空间。第三个是“检索Retrieval”。记忆服务的核心能力不只是“存进去”还要能“找回来”。检索通常支持两种方式一种是关键词/结构化过滤另一种是语义相似度召回。在多 Agent 场景里语义召回很重要因为 Agent 记下的信息和后续查询往往不是逐字匹配而是语义相近。可以这样理解Memory Item 是记忆的载体Namespace 是记忆的边界Retrieval 是记忆的入口。三者组合起来才构成一个相对完整的记忆服务模型。2.2 工作流程写入、检索、更新、淘汰记忆服务的完整生命周期可以分为四个阶段。写入阶段Agent 在处理完一段用户交互后从对话历史中抽取值得长期保存的信息组装成记忆条目写入服务。写入前可以做清洗、去重、脱敏和摘要压缩。检索阶段另一个 Agent 在处理新请求时向记忆服务发起查询。查询可以带一个自然语言 query也可以带结构化过滤条件。服务先过滤出候选记忆再按相关性排序返回 Top-K 结果。更新阶段当同一用户出现新的偏好或状态变化时不能简单追加一条新记忆否则记忆会越来越碎片化。比较好的做法是给记忆条目加版本号、更新时间或业务主键发现已有更合适的记忆条目就做更新。淘汰阶段记忆也不能无限增长。需要设计 TTL、优先级和合并压缩策略。比如高频访问的记忆保留长期未命中的记忆降级或删除降低存储成本和召回噪声。这四个阶段缺一不可。很多项目只做了“写”和“查”忽略了“更新”和“淘汰”结果记忆库越来越大召回准确率越来越低。2.3 Memmy 在多 Agent 架构中的位置Memmy 这类统一记忆服务在多 Agent 架构中通常位于“Agent 编排层”和“存储层”之间。[ 用户请求 ] │ [ Agent 编排层 Orchestrator ] │ ├─ Agent A意图识别 │ ├─ Agent B信息检索 │ └─ Agent C执行动作 │ [ 统一记忆服务如 Memmy ] │ [ 持久化存储 / 向量索引 ]Agent 编排层负责调度统一记忆服务负责为所有 Agent 提供记忆读写能力。Agent 不直接访问底层数据库而是通过记忆服务的 SDK 或 HTTP API 操作记忆。这样做的好处很明显降低耦合。Agent 不需要关心记忆存储在哪、用什么索引。统一权限。可以集中控制哪些 Agent 能读哪些命名空间。可观测。所有记忆读写都有日志方便复盘和排障。便于升级。底层存储升级不会影响 Agent 逻辑。3. 环境准备与快速启动了解概念之后建议先本地跑通一个最小实例再考虑如何接入自己的 Agent 项目。下面给出通用启动步骤具体命令以官方仓库 README 为准。3.1 环境依赖说明运行一个 Python 或 Node.js 的 Agent 项目通常需要准备操作系统Linux / macOS / Windows 均可建议 Linux 或 macOS 便于排查问题。编程语言运行时Node.js 18 或 Python 3.9取决于 Memmy 本身的使用方式。容器环境如果官方提供 Docker 镜像建议直接用 Docker 跑省去本地依赖安装。网络本地开发一般不需要外网但安装依赖时需要能访问 npm / pip / GitHub 等资源。这里没有给出具体版本号因为开源项目迭代较快版本要求请以仓库里的 package.json / requirements.txt / Dockerfile 为准。3.2 获取源码并安装依赖先从官方仓库获取 Memmy 源码。仓库地址请以项目官方页面为准不要使用来源不明的镜像。# 以官方仓库地址为准下面使用占位符示意 git clone memmy-repository-url cd memmy # 查看 README 和项目结构 ls -la cat README.md如果项目使用 Node.js通常可以通过 npm 安装依赖npm install如果项目使用 Python通常会提供 requirements.txt 或 pyproject.tomlpip install -r requirements.txt安装完成后根据 README 中的说明启动服务。这里不写死具体命令因为不同项目启动方式差异较大。3.3 快速启动本地记忆服务下面是一个常见的启动方式示例。假设项目支持 npm 脚本# 开发模式启动 npm run dev如果官方提供 Docker 镜像可以用 docker-compose 快速体验下面给一个示例模板。注意镜像名称仅作示意要以官方发布为准。# docker-compose.yml本地快速体验示例生产环境请按官方建议调整 version: 3 services: memmy: image: registry/memmy-image:tag ports: - 18080:18080 environment: - MEMMY_STORAGE_DIR./data - MEMMY_API_TOKENdev-token volumes: - ./data:/app/data启动后可以通过如下方式验证服务是否正常curl http://localhost:18080/health如果返回类似{status:ok}的 JSON说明本地记忆服务已经跑起来了。4. 核心机制拆解从写入到召回要让记忆服务真正可用重点不是会调接口而是理解写入什么、怎么召回、如何更新。4.1 记忆写入从对话历史到结构化记忆Agent 的对话历史通常是原始文本直接全部写入记忆库既不经济也容易造成噪声。更合理的做法是先由 Agent 或预处理模块进行抽取只保留值得长期保存的信息。下面我用 Python 写一个简化版客户端演示如何向记忆服务写入一条结构化记忆。接口字段只是示例实际使用时请按 Memmy 的 API 文档调整。# memory_client.py 简化示例 import requests import uuid from datetime import datetime class MemoryClient: 统一记忆服务客户端。 演示 write / search / delete 三个核心方法。 接口路径和字段需要按 Memmy 实际版本调整。 def __init__(self, endpoint: str, namespace: str, token: str ): self.endpoint endpoint.rstrip(/) self.namespace namespace self.headers {Authorization: fBearer {token}} if token else {} def write(self, content: str, metadata: dict) - dict: payload { mem_id: str(uuid.uuid4()), namespace: self.namespace, content: content, metadata: metadata, created_at: datetime.now().isoformat(), } resp requests.post( f{self.endpoint}/v1/memories, jsonpayload, headersself.headers, ) resp.raise_for_status() return resp.json()这段代码的核心逻辑是把一条记忆封装成统一的 JSON 结构然后通过 POST 接口发送给记忆服务。重点在 metadata不要把业务信息都堆在 content 里因为后续检索过滤主要依赖 metadata。4.2 记忆检索如何精准召回检索是最能体现记忆服务价值的环节。简单做法是直接把所有记忆读回来让大模型筛选但工程上必须控制召回数量。通常需要支持两个维度结构化过滤和语义召回。def search(self, query: str, top_k: int 5, filters: dict None): 按语义和过滤条件召回记忆。 payload { namespace: self.namespace, query: query, top_k: top_k, filters: filters or {}, } resp requests.post( f{self.endpoint}/v1/memories/search, jsonpayload, headersself.headers, ) resp.raise_for_status() return resp.json()[results]在实际使用中filters 通常用来限制范围例如只查某个 user_id 下的记忆或者只查某个场景类的记忆。query 用于语义召回。例如查询“用户偏好什么时间联系”底层会先向量化再计算相似度最后返回 Top-K 条。如果召回结果不准确优先检查两件事filters 是否限制得太宽或太窄top_k 是否设置过大导致噪声进入上下文。4.3 记忆更新与过期策略前面提到记忆不能只增不改。对于同一业务主键比如 user_idpref 类型如果写入一条新记忆最好能影响旧记忆。一种处理方式是在 metadata 中增加 unique_key 和 versiondef write_with_version(self, content: str, metadata: dict): metadata.setdefault(version, 1) metadata[updated_at] datetime.now().isoformat() # 先按 unique_key 删除旧记忆再写入新记忆 self.delete(filters{unique_key: metadata[unique_key]}) return self.write(content, metadata)当然这种“先删后写”的方案适合一致性要求高的业务。如果只是追加型记忆就不需要删除旧记录而是靠检索时的排序规则把最新的记忆排前面。过期策略同样重要。可以在 metadata 中增加 ttl 字段由记忆服务定期清理过期记忆。也可以在写入时就对内容做摘要压缩把多条相关记忆合并成一条控制总量增长。5. 实战让两个 Agent 共享客服记忆这一节做一个比较完整的场景示例。假设一个小型客服系统中客服 Agent 负责接待用户质检 Agent 负责回访总结。两个 Agent 需要共享同一份用户偏好记忆。5.1 场景与项目结构我们希望实现的效果是客服 Agent 在对话后写入一条记忆用户偏好上午联系价格敏感希望简洁沟通。质检 Agent 在开始回访前从记忆服务检索到这条记忆避免重新向用户确认。两个 Agent 使用同一个命名空间确保记忆互通。项目目录如下memory-demo/ ├── config.yaml ├── memory_client.py ├── agent_support.py ├── agent_quality.py └── run_demo.py5.2 定义记忆接入客户端先编写一个完整的简化客户端。我故意把它设计成一个独立的模块方便两个 Agent 复用。# memory-demo/memory_client.py import requests import uuid from datetime import datetime class MemoryClient: 统一记忆服务客户端演示用。 def __init__(self, endpoint: str, namespace: str, token: str ): self.endpoint endpoint.rstrip(/) self.namespace namespace self.headers {Authorization: fBearer {token}} if token else {} def write(self, content: str, metadata: dict) - dict: payload { mem_id: str(uuid.uuid4()), namespace: self.namespace, content: content, metadata: metadata, created_at: datetime.now().isoformat(), } resp requests.post( f{self.endpoint}/v1/memories, jsonpayload, headersself.headers, ) resp.raise_for_status() return resp.json() def search(self, query: str, top_k: int 5, filters: dict None): payload { namespace: self.namespace, query: query, top_k: top_k, filters: filters or {}, } resp requests.post( f{self.endpoint}/v1/memories/search, jsonpayload, headersself.headers, ) resp.raise_for_status() return resp.json()[results] def delete(self, filters: dict): resp requests.post( f{self.endpoint}/v1/memories/delete, json{namespace: self.namespace, filters: filters}, headersself.headers, ) resp.raise_for_status() return resp.json()这段代码里的接口路径是示意写法实际项目应以官方 SDK 或 API 文档为准。5.3 编写客服 Agent 与质检 Agent客服 Agent 的职责是从对话中识别用户偏好并写入统一记忆。# memory-demo/agent_support.py from memory_client import MemoryClient def handle_contact(user_id: str, conversation_text: str, memory: MemoryClient): # 说明实际项目可以用大模型做信息抽取这里直接模拟抽取结果 pref { user_id: user_id, preferred_contact_time: morning, price_sensitive: True, communication_style: concise, unique_key: f{user_id}:preference, version: 2, } # 写入前先删掉旧偏好避免重复积累 memory.delete(filters{unique_key: pref[unique_key]}) memory.write( content( f用户 {user_id} 的偏好工作日上午联系 价格敏感度高沟通时希望简洁直接。 ), metadatapref, ) print(f[support_agent] 已写入用户偏好记忆user_id{user_id})质检 Agent 的职责是在回访前先查询历史记忆。# memory-demo/agent_quality.py from memory_client import MemoryClient def summarize_user(service_record: str, memory: MemoryClient): results memory.search( query用户联系偏好 价格敏感 沟通风格, top_k3, filters{user_id: U-1001}, ) if not results: print([quality_agent] 未找到历史记忆需要人工确认用户偏好) return top results[0] print([quality_agent] 查找到历史记忆) print(f 内容: {top[content]}) print(f 元数据: {top[metadata]})最后是统一入口。# memory-demo/run_demo.py from memory_client import MemoryClient from agent_support import handle_contact from agent_quality import summarize_user def main(): memory MemoryClient( endpointhttp://localhost:18080, namespacecustomer-service, tokendev-token, ) # 客服 Agent 先处理一次对话 handle_contact(U-1001, 用户在上午来电希望简洁沟通, memory) # 质检 Agent 接棒查询历史记忆 summarize_user( 服务记录用户希望尽快联系对价格较敏感, memory, ) if __name__ __main__: main()5.4 运行与验证先确认本地记忆服务已经启动再运行脚本cd memory-demo python run_demo.py预期输出效果类似[support_agent] 已写入用户偏好记忆user_idU-1001 [quality_agent] 查找到历史记忆 内容: 用户 U-1001 的偏好工作日上午联系价格敏感度高沟通时希望简洁直接。 元数据: {user_id: U-1001, preferred_contact_time: morning, ...}如果质检 Agent 能读取到客服 Agent 写入的记忆说明共享记忆链路已经打通。6. 常见问题与排查思路实战中最容易碰到的不是“能不能跑通”而是“跑通后各种异常”。下面整理一份高频问题速查表。问题现象常见原因解决思路查询不到其他 Agent 写入的记忆命名空间不一致统一命名空间或按业务层级定义写入成功但召回结果为空相关性阈值过高调低阈值或改用 filters 先过滤召回结果不准确缺少元数据过滤增加 user_id、scene 等过滤条件旧记忆覆盖新记忆缺少版本号或更新时间判断在 metadata 中增加 timestamp、version记忆库无限膨胀缺少过期淘汰策略配置 TTL定期清理或合并压缩敏感信息泄漏共享记忆缺少权限与脱敏按角色授权写入前做脱敏本地服务启动失败依赖安装不完整、端口冲突查看启动日志检查依赖和端口占用典型排查流程可以按下面顺序走确认记忆服务健康检查通过能看到日志输出。检查写入接口返回的 mem_id确认内容确实写入成功。检查查询时使用的 namespace 是否和写入时一致。临时去掉 filters用最简单的 query 查询判断是过滤条件问题还是召回问题。打开服务端日志观察是否存在超时或索引未构建完成的情况。对比两条记录的 metadata确认 unique_key 或 user_id 是否一致。7. 最佳实践与工程建议如果要把 Memmy 或类似的统一记忆方案落地到生产环境建议关注以下工程细节。7.1 命名空间与元数据设计命名空间不要起得太随意。建议按“环境 业务域”分层例如crm/prod/customer-service、shop/test/sales。这样既便于隔离也便于权限控制。metadata 是记忆服务的灵魂。设计时建议至少包含以下字段user_id记忆归属方检索时的第一过滤条件。agent_id写入来源便于追溯。scene业务场景避免跨场景误用。timestamp写入或更新时间用于排序和过期判断。unique_key业务主键用于更新和去重。version版本号配合 unique_key 实现更新覆盖。ttl过期时间防止记忆无限膨胀。7.2 数据安全与脱敏共享记忆越方便越要重视数据安全。写入前做好脱敏是底线。手机号、身份证号、银行卡号、住址等信息在写入记忆库前必须脱敏或加密存储。权限方面建议按命名空间控制读写权限。例如质检 Agent 只能读取customer-service命名空间不能读取payment命名空间。如果服务支持 API Token 或 mTLS生产环境一定要开启不要让记忆服务裸奔在公网。7.3 性能与容量规划记忆服务的性能瓶颈主要在三块写入吞吐、检索延迟、存储容量。写入方面Agent 不需要每次对话都写记忆只有关键状态发生变更时才写入避免把记忆服务变成日志系统。检索方面设置合理的 top_k。建议先返回 5 条以内的候选再交给大模型判断而不是一次性召回 100 条让模型自己挑。存储方面做好过期清理。可以定期把“长期未命中的记忆”和“同主题重复记忆”做合并压缩。比如把一条用户偏好从 500 字压缩成 50 字召回质量反而更好。7.4 可观测性与审计Agent 记忆的写入和读取应该像业务日志一样可观测。对每条写入建议记录哪个 Agent 写入、写入内容摘要、metadata 全量、耗时。对每次检索建议记录query、filters、召回条数、Top-1 内容摘要。这样做的价值是当后续 Agent 行为异常时可以快速定位是不是“记忆污染”导致的而不是盲目调整 Prompt。8. 总结与下一步学习建议写到这里再回头看 Memmy 开源这件事我认为它真正值得学习的不是某一个 API而是它把“多 Agent 记忆”单独抽象出来做成服务的思路。多 Agent 协作项目里记忆不是某一个 Agent 的内部实现细节而是一个横切关注点。统一记忆服务能很好解决记忆孤岛、结构不统一、权限难控的问题。如果你正在做 Agent 开发我建议不要先急着在 Agent 框架内部堆记忆代码而是先把记忆服务独立出来。先用 Memmy 或类似思路跑通最小闭环写入、检索、更新、过期。再逐步接入业务场景。下一步可以继续研究三个方面一是记忆分层设计把短期记忆、长期记忆、会话记忆区分开二是结合 RAG 优化记忆召回让记忆库更好支撑大模型生成三是记忆的冲突解决与压缩策略控制记忆质量。如果本文对你有帮助欢迎收藏备用。接下来我还会继续更新 Agent 记忆、RAG、Agent 框架相关的实战文章有疑问也可以在评论区留言交流。