前几天在 GitHub 上看到 MemOS 团队开源了 Memmy,定位是“统一多 Agent 记忆”。这个方向其实在 Agent 开发社区里讨论了很久:单 Agent 的记忆管理还没完全统一标准,多 Agent 之间的记忆共享、权限隔离、长期记忆检索就更是各写各的。Memmy 的出现,算是把“记忆层”单独抽出来做成基础设施,思路很值得学习。
这篇文章我会从 Agent 记忆的基础概念讲起,分析多 Agent 场景下记忆管理为什么难,再结合一个可运行的简化示例,讲讲“统一记忆服务”应该怎么设计、怎么落地。如果你是做 AI Agent 开发、想了解 Agent 记忆框架,或者正在折腾多 Agent 项目,这篇文章应该能给你一套比较完整的参考。
1. 背景与核心概念:Agent 为什么需要记忆
1.1 从“无状态对话”到“有状态 Agent”
早期的大模型应用大多是无状态的:用户问一句,模型答一句,前后对话之间没有什么关联。这种方式实现简单,但一旦遇到需要多轮交互、需要根据用户历史偏好做决策的场景,就很难满足需求。
Agent 和普通对话机器人最大的区别,在于它需要在一个任务周期里持续决策、持续行动。比如一个客服 Agent,它可能需要先了解用户之前的工单记录,再结合当前问题判断处理方案;一个编程 Agent,它需要记住项目里已经改过哪些文件、哪些测试已经通过。这些信息如果不能跨轮次保留,Agent 的表现就会非常“健忘”。
记忆能力(Memory)就是解决这个问题的核心机制。它让 Agent 可以把对话历史、工具调用结果、用户偏好、任务状态等信息保存下来,在后续推理和行动时重新读取,从而形成连续的行为逻辑。
1.2 单 Agent 记忆的常见类型
在讨论多 Agent 记忆之前,我们先看单 Agent 场景下的记忆分类。通常可以分成这几种:
| 记忆类型 | 存储内容 | 典型实现 |
|---|---|---|
| 对话记忆 | 最近几轮用户消息、模型回复 | 滑动窗口、消息列表 |
| 工作记忆 | 当前任务的中间状态、变量、上下文 | 内存对象、临时缓存 |
| 长期记忆 | 跨会话保存的用户偏好、事实知识 | 数据库、向量库 |
| 语义记忆 | 概念、常识、领域知识的结构化表达 | 知识图谱、向量索引 |
| 情景记忆 | 特定事件、历史交互记录的抽象 | 事件日志、摘要存储 |
这里容易混淆的是一点:对话历史并不等于记忆。对话历史是原始数据,记忆是从数据里提炼出来的、可供检索和复用的信息。真正设计记忆系统时,通常要做一层“信息提炼”,比如把冗长对话压缩成摘要再入库。
1.3 多 Agent 记忆:从“各自记忆”到“共享记忆”
多 Agent 系统里,不同 Agent 往往承担不同职责:有的负责信息收集,有的负责任务拆解,有的负责最终回复合成。如果每个 Agent 只维护自己的私有记忆,就会出现几个典型问题:
- 信息孤岛。A Agent 获取到的用户偏好,B Agent 完全不知道,导致任务需要重复询问。
- 上下文重复传递。为了让下游 Agent 了解上游结果,只能把大量信息塞进提示词里,既浪费 token,又容易超长。
- 状态不一致。多个 Agent 并行处理同一个任务时,各自保存的“任务状态”可能相互冲突。
所以“统一多 Agent 记忆”并不是一个伪需求,而是 Agent 走向复杂协作时的必然趋势。MemOS 团队开源 Memmy,正是在这个背景下出现的:他们想把不同 Agent 的记忆收口到统一的能力层,让记忆的写入、读取、检索、共享都有一致规范。
2. Memmy 到底解决什么问题
2.1 Memmy 是什么
根据公开信息,Memmy 是 MemOS 团队开源的一个面向多 Agent 场景的记忆管理项目,核心方向是“统一多 Agent 记忆”。它希望为不同 Agent 提供统一的记忆接口,让 Agent 之间的记忆可以按需共享,同时保留对记忆数据的控制能力。
由于项目仍在快速迭代中,我建议你以官方 GitHub 仓库的最新文档为准。本文不会编造具体 API 名称,而是重点讲解它背后的设计思路,并给出一套可落地的简化实现。
2.2 多 Agent 记忆统一要解决的四件事
我们从工程角度拆解,一个“统一多 Agent 记忆”系统至少要解决四件事:
- 记忆的标准化写入。不同 Agent 产生的记忆,需要统一成相同的结构,而不是各自记各自的。
- 记忆的高效检索。Agent 需要根据当前任务快速找到相关记忆,不能每次全量扫描。
- 记忆的隔离与共享。有些记忆属于单 Agent,有些记忆需要全局共享,两者要能区分。
- 记忆的生命周期管理。记忆会过期、会冲突、会失效,需要更新和淘汰机制。
Memmy 这类项目的价值在于,它把这些能力从各个 Agent 里抽出来,做成一个独立的记忆基础设施。这样开发者在新增 Agent 时,不需要再重复实现“记住用户偏好”“查找历史任务”这类通用能力。
2.3 与 Agent 框架、MCP、Harness 的关系
最近社区里 Agent 周边概念很多,容易混淆,我在这里做一个简单区分:
- Agent 框架负责“怎么搭建一个 Agent”,包括模型调用、工具注册、推理循环。
- MCP 解决的是“怎么让 Agent 调用外部工具和数据源”,是一种标准化连接协议。
- Harness 通常指 Agent 的运行容器或执行环境,负责资源管理、权限控制、执行编排。
- Memmy 这类记忆项目解决的是“Agent 怎么记住东西”,属于 Agent 的能力组件,可以和框架、MCP 配合使用。
所以它们不是替代关系,而是分层配合关系。你完全可以在某个 Agent 框架里开发业务逻辑,同时接入记忆服务来统一管理记忆。
3. 记忆层的核心设计思路
3.1 记忆条目的数据结构设计
无论用什么存储,记忆条目本身应该有相对稳定的核心结构。我建议至少包含以下字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| memory_id | 记忆唯一标识 | mem_20250612_001 |
| agent_id | 记忆所属 Agent | recruiter_agent |
| session_id | 所属会话 | session_1001 |
| memory_type | 记忆类型 | preference / fact / task_state |
| content | 记忆内容(文本) | 用户偏好 Python 技术栈 |
| tags | 标签,便于检索 | ["user", "tech_stack"] |
| visibility | 可见范围 | private / public / shared |
| created_at | 创建时间 | 2025-06-12T10:00:00Z |
| updated_at | 更新时间 | 2025-06-12T10:30:00Z |
这个结构是我个人在 Agent 项目中归纳的通用结构,不一定和 Memmy 完全一致,但它能覆盖多数需求。visibility 字段很关键,它是“跨 Agent 共享”的开关。
3.2 统一读写接口
统一记忆服务的核心,是提供一组对上层 Agent 友好的接口。大致可以抽象为:
- write_memory:写入一条记忆。
- read_memory:根据条件读取记忆。
- search_memory:根据语义或关键词检索记忆。
- update_memory:更新已有记忆。
- delete_memory:删除记忆。
- list_memory:列出某个范围下的全部记忆。
每个 Agent 在运行时只需要调用这些接口,不需要关心底层到底用了 Redis、MySQL 还是向量数据库。这就是“统一”的价值。
3.3 底层存储选型
底层存储可以分层设计:
- 热记忆:近几轮的对话和短期任务状态,放 Redis,读写快,过期自动清理。
- 冷记忆:长期偏好、事实知识,放数据库或向量库,便于检索和保存。
- 语义检索:把记忆内容做 embedding,存入向量库,支持相似度搜索。
在实际落地时,冷热分离会让系统更稳定:Hot path 不要碰慢存储,批量检索走索引或向量。Memmy 这类项目内部大概率也参考了类似思路。
4. 从零实现一个简化版多 Agent 记忆服务
为了把上面的设计思路讲透,我这里给出一个教学用示例。它模拟了“Memmy 风格”的统一记忆服务:多个 Agent 通过 HTTP 接口写入记忆、读取记忆、检索记忆。这个示例适合本地运行,用来理解记忆层的核心逻辑。
需要说明:这不是 Memmy 的真实 API,而是我根据自己的工程经验设计的参考实现。真实项目请以官方仓库为准。
4.1 项目结构
agent-memory-demo/ ├── requirements.txt ├── app.py # FastAPI 入口 ├── memory_store.py # 内存存储与检索核心 └── examples/ └── agents_demo.py # 模拟多 Agent 调用4.2 环境准备
建议使用 Python 3.10 或更高版本。
mkdir agent-memory-demo cd agent-memory-demo python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activaterequirements.txt:
fastapi==0.111.0 uvicorn==0.30.1 pydantic==2.7.4安装依赖:
pip install -r requirements.txt4.3 实现核心记忆存储
文件:memory_store.py
这里实现一个内存版记忆存储,并用简单的关键词重叠算法模拟语义检索。生产环境建议把这一层换成 Redis + 向量数据库。
import time import uuid from typing import List, Optional class MemoryItem: """记忆条目""" def __init__( self, agent_id: str, content: str, memory_type: str = "fact", session_id: str = "", tags: Optional[List[str]] = None, visibility: str = "public", ): self.memory_id = uuid.uuid4().hex[:12] self.agent_id = agent_id self.content = content self.memory_type = memory_type self.session_id = session_id self.tags = tags or [] self.visibility = visibility self.created_at = time.time() self.updated_at = time.time() def to_dict(self): return { "memory_id": self.memory_id, "agent_id": self.agent_id, "content": self.content, "memory_type": self.memory_type, "session_id": self.session_id, "tags": self.tags, "visibility": self.visibility, "created_at": self.created_at, "updated_at": self.updated_at, } class MemoryStore: """简化版记忆存储,生产环境可替换为 Redis + 向量库""" def __init__(self): self._items = {} # memory_id -> MemoryItem def write(self, agent_id: str, content: str, **kwargs) -> MemoryItem: item = MemoryItem(agent_id=agent_id, content=content, **kwargs) self._items[item.memory_id] = item return item def read(self, memory_id: str) -> Optional[MemoryItem]: return self._items.get(memory_id) def list_memory(self, agent_id: Optional[str] = None) -> List[MemoryItem]: if agent_id is None: return list(self._items.values()) return [item for item in self._items.values() if item.agent_id == agent_id] def search(self, query: str, limit: int = 5) -> List[MemoryItem]: """基于标签和关键词重合度的简单检索。 生产环境建议替换为 embedding + 向量检索。 """ query_tags = set(query.lower().replace(",", " ").split()) scored = [] for item in self._items.values(): score = 0.0 if query.lower() in item.content.lower(): score += 1.0 content_words = set(item.content.lower().split()) overlap = query_tags & content_words score += len(overlap) * 0.5 tag_overlap = query_tags & set(item.tags) score += len(tag_overlap) * 1.0 if score > 0: scored.append((score, item)) scored.sort(key=lambda x: x[0], reverse=True) return [item for _, item in scored[:limit]]这个类结构很直白。write负责写入记忆,read根据 ID 读取,list_memory可以按 Agent 过滤,search做了一个简单的相关性打分。理解这个逻辑后,你把它换成真实向量检索也只是替换search的问题。
4.4 用 FastAPI 封装统一接口
文件:app.py
from typing import List, Optional import uvicorn from fastapi import FastAPI, HTTPException from pydantic import BaseModel from memory_store import MemoryStore app = FastAPI(title="Agent Memory Service", version="0.1.0") store = MemoryStore() class MemoryWriteRequest(BaseModel): agent_id: str content: str memory_type: str = "fact" session_id: str = "" tags: List[str] = [] visibility: str = "public" class MemoryUpdateRequest(BaseModel): content: Optional[str] = None tags: Optional[List[str]] = None @app.post("/memories") def write_memory(req: MemoryWriteRequest): item = store.write( agent_id=req.agent_id, content=req.content, memory_type=req.memory_type, session_id=req.session_id, tags=req.tags, visibility=req.visibility, ) return item.to_dict() @app.get("/memories/{memory_id}") def read_memory(memory_id: str): item = store.read(memory_id) if not item: raise HTTPException(status_code=404, detail="memory not found") return item.to_dict() @app.get("/memories") def list_memories(agent_id: Optional[str] = None): items = store.list_memory(agent_id=agent_id) return [item.to_dict() for item in items] @app.post("/memories/search") def search_memories(query: str, agent_id: Optional[str] = None, limit: int = 5): items = store.search(query, limit=limit) if agent_id: items = [item for item in items if item.agent_id == agent_id] return [item.to_dict() for item in items] @app.put("/memories/{memory_id}") def update_memory(memory_id: str, req: MemoryUpdateRequest): item = store.read(memory_id) if not item: raise HTTPException(status_code=404, detail="memory not found") if req.content is not None: item.content = req.content if req.tags is not None: item.tags = req.tags item.updated_at = time.time() return item.to_dict() @app.delete("/memories/{memory_id}") def delete_memory(memory_id: str): item = store.read(memory_id) if not item: raise HTTPException(status_code=404, detail="memory not found") del store._items[memory_id] return {"status": "deleted"} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)这里有一个细节需要解释:store._items是私有变量,示例里直接访问了,这是为了缩短代码长度。真实工程里应该在MemoryStore里增加一个delete方法封装删除逻辑,避免外部直接操作内部数据结构。
4.5 模拟多 Agent 写入与共享
文件:examples/agents_demo.py
这个脚本模拟三个角色:用户画像 Agent、技术筛选 Agent、沟通 Agent。它们通过同一个记忆服务写入和读取记忆。
import requests BASE_URL = "http://127.0.0.1:8000" def post_memory(agent_id: str, content: str, tags: list, visibility: str = "public"): resp = requests.post(f"{BASE_URL}/memories", json={ "agent_id": agent_id, "content": content, "tags": tags, "visibility": visibility, }) print(f"[{agent_id}] 写入记忆:", resp.json()) return resp.json() def search_memory(query: str, agent_id: str = None): resp = requests.post(f"{BASE_URL}/memories/search", json={ "query": query, "agent_id": agent_id, "limit": 5, }) print(f"检索 '{query}' 结果:") for item in resp.json(): print(" -", item["content"], "| 来源 Agent:", item["agent_id"]) return resp.json() if __name__ == "__main__": # 用户画像 Agent 写入用户偏好 post_memory( agent_id="user_profile_agent", content="用户是 Python 后端开发者,熟悉 FastAPI 和 PostgreSQL", tags=["user", "tech_stack"], ) # 技术筛选 Agent 记录岗位要求 post_memory( agent_id="tech_screen_agent", content="当前岗位要求熟悉 Python、FastAPI、Redis", tags=["job", "requirement"], ) # 沟通 Agent 检索共享记忆 search_memory("Python FastAPI 岗位要求")运行这个脚本前,需要先启动记忆服务:
python app.py再开一个终端运行示例:
python examples/agents_demo.py预期输出大致如下:
[user_profile_agent] 写入记忆: {'memory_id': '...', 'content': '用户是 Python 后端开发者,熟悉 FastAPI 和 PostgreSQL', ...} [tech_screen_agent] 写入记忆: {'memory_id': '...', 'content': '当前岗位要求熟悉 Python、FastAPI、Redis', ...} 检索 'Python FastAPI 岗位要求' 结果: - 当前岗位要求熟悉 Python、FastAPI、Redis | 来源 Agent: tech_screen_agent - 用户是 Python 后端开发者,熟悉 FastAPI 和 PostgreSQL | 来源 Agent: user_profile_agent这个例子虽然简单,但已经体现了“统一多 Agent 记忆”的核心流程:不同 Agent 写入各自的记忆,沟通 Agent 在需要时统一检索,不需要提前约定字段格式,也不需要把全部信息塞进提示词。
5. 用记忆存储还是直接拼提示词
很多同学可能会问:多 Agent 系统里,与其搞一套记忆服务,不如直接把所有历史信息都拼到 prompt 里,不是更简单吗?
在早期原型阶段确实可以,但一旦 Agent 数量增多、任务变长,这种方案会快速失控。
| 对比维度 | 全量拼入 Prompt | 统一记忆服务 |
|---|---|---|
| Token 成本 | 随历史线性增长,成本高 | 只取相关记忆,成本可控 |
| 检索速度 | 无检索,但上下文过长影响模型效果 | 向量检索,毫秒级返回 |
| 信息隔离 | 所有 Agent 看到全部内容 | 可控制可见范围和权限 |
| 长期保存 | 会话结束即丢失 | 跨会话持久化 |
| 多 Agent 一致性 | 各持一份副本,容易不一致 | 统一写入和读取,状态一致 |
所以更合理的架构是:短期内的小任务状态放内存或 Redis,关键事实放记忆服务,只有当前决策真正需要的相关内容才注入提示词。这也是记忆层存在的工程价值。
6. 常见问题与排查思路
在实现和使用记忆服务时,最常遇到的问题如下。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Agent 总是“忘事” | 记忆写入后没有在决策前检索 | 检查 Agent 推理流程是否在调用模型前读取了记忆 |
| 检索结果不相关 | 只用关键词匹配,没有语义能力 | 升级为向量检索,或用 embedding 计算相似度 |
| 多 Agent 写到同一份记忆导致冲突 | 缺少 agent_id 隔离维度 | 写入时强制带 agent_id,检索时按需过滤 |
| 记忆越来越多,成本升高 | 缺少生命周期管理 | 增加过期时间、定期摘要压缩、淘汰低价值记忆 |
| 隐私或越权读取 | visibility 没有校验 | 在读取接口层校验可见范围和权限 |
| 并发写入覆盖数据 | 读取-修改-写入没有做原子操作 | 使用版本号或乐观锁,必要时用 Lua 脚本保证原子性 |
如果你遇到“记忆好像没生效”的问题,我建议先按这个顺序排查:
- 确认写入接口是否被调用。打印日志检查 write_memory 是否真的执行。
- 确认读取时是否传对了条件。比如当前 Agent 只读取自己写入的记忆,但需要的其实是全局记忆。
- 确认检索是否返回了结果。如果用的是向量库,往往还需要先确认 embedding 是否生成成功。
- 确认记忆注入是否真的进入提示词。很多框架层会过滤外部数据,需要检查中间管道。
7. 最佳实践与工程建议
7.1 记忆设计要明确“写入什么、谁可见”
不要在代码里随手 write 所有内容。先定义清楚哪些信息值得成为记忆:
- 用户长期偏好:值得写入。
- 一轮无关闲聊:不值得写入。
- 任务中间状态:可以放短期缓存。
- 工具调用结果:按需摘要后入库。
visibility 字段要尽早设计。至少区分公开记忆和私有记忆,避免所有 Agent 都能读到敏感信息。
7.2 把记忆存储做成可替换的抽象层
我的建议是:不要在业务代码里直接调用 Redis 或向量库 SDK,而是先定义一层 MemoryStore 接口。这样后续换存储、加缓存、做权限控制都更容易。上面的示例就是这种思路,只是实现简单了一些。
7.3 记忆更新要谨慎,删除要克制
记忆一旦写入,可能会被多个 Agent 依赖。更新记忆时最好保留历史版本,或者至少记录 updated_at,方便回溯。批量删除记忆在生产环境要格外谨慎,建议先做标记删除,再异步清理。
7.4 关注记忆安全与红线上限
Agent 记忆里可能包含用户隐私、密钥、授权信息。在工程上要注意:
- 对记忆内容做脱敏处理,密钥和 token 不进记忆库。
- 记忆读取接口要做鉴权,不能任何 Agent 都能读取全部记忆。
- 涉及删除和变更的操作,要遵循最小权限原则。
- 生产环境变更前先备份,并在测试环境验证。
7.5 监控指标
如果你把记忆服务真正跑到生产,建议至少监控五个指标:
- 写入 QPS 和写入耗时。
- 检索召回率和响应耗时。
- 单 Agent 记忆量增长速度。
- 检索失败率和空结果率。
- 记忆容量和存储成本。
这些指标能帮你判断当前记忆策略是否健康。比如空结果率太高,说明很多查询没有命中记忆,需要检查写入策略和检索策略。
8. 从 Memmy 到自建记忆层,下一步怎么走
MemOS 团队开源 Memmy 这件事,给整个 Agent 生态带来的信号很明确:记忆正在从“Agent 内部实现细节”变成一种独立的平台能力。以后做多 Agent 应用的团队,可能不再需要从零开发记忆模块,而是直接接入一个开源的记忆层。
如果你准备在自己项目里落地多 Agent 记忆,我建议按照下面的路径推进:
- 先盘业务场景。明确需要记忆的信息有哪些类型、哪些需要跨 Agent 共享。
- 跑通一个最小闭环。用本文的简版示例,先解决“写入-检索-注入”通路。
- 引入真实存储。把内存存储替换成 Redis、MySQL 或向量数据库。
- 加上权限和可视化。控制 Agent 可见范围,方便调试。
- 持续评估效果。通过实际任务的成功率来判断记忆策略是否有效。
在动手实现时,优先级应该放在检索质量和更新策略上。检索不准,记忆存得再多也没有意义;更新不严,记忆就会变成垃圾场。
如果你刚开始接触 Agent 记忆,我建议先从最简单的“显式写入 + 关键词检索”开始,跑通后再引入向量检索。直接上完整生产方案,很容易在调试阶段被复杂链路卡住。
这个领域迭代很快,Memmy 只是其中一个方向,后续大概率还会出现更多专注于记忆管理、记忆可视化、记忆共享协议的开源项目。保持关注官方仓库,自己动手多写几个 Agent 调用记忆服务的例子,会比只看概念收获大得多。