Agent 记忆这个话题,圈子里已经讨论了大半年了。从 MemGPT 到 GraphRAG,市面上不缺方案,但大部分要么太简单(只存原始对话),要么太重(要搭图数据库)。
上周我看到 Elastic 开源了一个新项目------Atlas Agent Memory。它走了一条中间路线:基于 Elasticsearch 做三类记忆分离,通过 MCP 直接对接任何 Agent。我花了一个周末把它跑起来接入了 Claude Desktop,今天分享一下上手体验。
Agent 不能只靠上下文窗口
先说一个共识:用 LLM 的上下文窗口存记忆是不靠谱的。
拆成三点来讲:
- 上下文窗口有限:4k / 16k / 100k,终究是个固定数。Agent 跑了几天,对话记录可以轻松填满。
- 未结构化的记忆不好用:所有历史消息堆在一起,你想让 Agent 回忆"用户上周四提到的技术栈偏好",它得从头扫一遍。
- 没有写入可见性:这一轮对话存进去的信息,下一轮才能看到。Agent 自己写进去的内容,在同一轮对话里看不到------这会导致它重复问相同的问题。
Atlas 解决这些问题的思路来自认知科学:把记忆分成三类,分别存储、分别检索。
三类记忆架构:情景、语义、程序性
Atlas 用了三个独立的 Elasticsearch 索引,分别对应三类记忆:
bash
atlas_memory_episodic -- 情景记忆:具体事件的时间线
atlas_memory_semantic -- 语义记忆:提炼出来的事实
atlas_memory_procedural -- 程序性记忆:怎么做某件事的步骤
情景记忆是最原始的输入。每次用户发来一条消息,Atlas 就写一条情景记录,包含 timestamp、原始消息内容、还有谁发的。这个索引写入频率很高,所以它带了时间衰减逻辑------半年前的情景,权重会逐年降低。
语义记忆是 LLM 在 Consolidation 阶段从情景里提炼出来的事实。比如用户几次提到"我在用 Next.js 14",LLM 会把它提炼成一条语义事实:"用户的技术栈是 Next.js 14",并附上证据情景的 ID。下次 Agent 再问"用什么框架",直接去语义索引检索,不用翻历史消息。
程序性记忆存的是操作指南。比如 Agent 学会了"用户公司的部署流程是:先跑 linter,然后构建,然后发 Slack 通知",这个流程会被存成程序性记忆,之后类似场景可以直接复用。
混合检索管线:BM25 → Dense → RRF → Reranker
单靠语义检索是不够的。实际场景里,用户可能问"我上周说过的那个框架",这是一个精确短语匹配("上周"+"框架")------BM25 擅长;也可能问"你觉得 Go 和 Rust 哪个更适合写 CLI?"------密集向量检索擅长。
Atlas 的检索管线是四段式:
bash
输入查询
↓
BM25 精确检索(lexical)
↓
Jina v5 密集向量检索(dense)
↓
RRF 融合(rank_constant=30)
↓
Jina v2 Cross-encoder 重排序
↓
Top-10 结果输出
BM25 做精确匹配,Jina v5 做语义匹配,RRF 把两个排序列表融合起来(rank_constant=30 是默认值,控制融合的平滑度),最后用 Cross-encoder 做一次精排。
官方的评测数据:R@10(检索前 10 条的回召率)达到了 0.89 。更重要的是 零跨租户泄漏------A 用户搜不到 B 用户的记忆。
5 步上手:从克隆到 MCP 接入 Claude Desktop
我们直接上手。需要先准备好:
- Elasticsearch Cluster(8.15+)
- Node.js 18+
- uv(Python 包管理器)
- Elastic Inference Service(EIS),用于 Jina embedding + Claude chat-completion
第一步,把项目克隆下来:
bash
git clone https://github.com/noamschwartz/atlas-memory-demo.git
cd atlas-memory-demo
第二步,配置环境变量。项目提供了一个 .env.example 文件,需要填 EIS 的 endpoint 和 API key,以及 ES 集群的连接信息。关键配置项:
bash
ELASTICSEARCH_URL=https://your-cluster.es.us-east-1.aws.cloud.es.io:9243
ELASTICSEARCH_API_KEY=your-api-key
EIS_ENDPOINT=https://your-eis-endpoint
EIS_API_KEY=your-eis-key
第三步,一行命令启动:
bash
./setup.sh
这个脚本做了三件事:
- 用
uv创建 Python 虚拟环境并安装依赖(FastAPI + elasticsearch-py) - 用
npm install安装前端依赖 - 自动创建三个 ES 索引并写入 mapping
我建议先手动确认索引已经创建成功:
bash
# 查看三个记忆索引的 mapping
curl -X GET "https://your-cluster.es.us-east-1.aws.cloud.es.io:9243/atlas_memory_episodic/_mapping" \
-H "Authorization: ApiKey your-api-key"
第四步,写入一条记忆:
python
import requests
# 写入情景记忆
response = requests.post(
"http://localhost:8000/api/atlas/write_memory",
json={
"memory_type": "episodic",
"text": "用户说:我在用Next.js 14做全栈开发,觉得App Router比Pages Router好用",
"user_id": "user_001",
"refresh": True # 同轮立即可见
},
params={"user_id": "user_001"}
)
print(response.json())
注意这个 refresh=True。默认情况下,ES 写入后需要 1 秒的刷新间隔才能被搜索到。如果 Agent 在写入后立即搜索,可能搜不到自己刚写的内容。Atlas 在写 API 里暴露了这个参数,确保同轮可见。
第五步,检索记忆。Atlas 的搜索默认返回 top-10 结果,使用完整的混合检索管线:
python
response = requests.post(
"http://localhost:8000/api/atlas/recall_memory",
json={
"query": "用户用什么框架开发",
"user_id": "user_001"
},
params={"user_id": "user_001"}
)
results = response.json()
for r in results["memories"]:
print(f"[{r['memory_type']}] {r['text']} (score: {r['score']:.3f})")
输出的结果应该会包含刚才写入的情景记录,甚至可能在 Consolidation 后返回提炼出的语义事实。
MCP 接入 Claude Desktop------这才是杀手锏
Atlas 的价值不只是"一个 Agent 记忆的 API",它提供了 MCP 端点,可以让任何支持 MCP 的 Agent 即插即用。Claude Desktop 从今年年初就开始支持 MCP 协议了。
在 Claude Desktop 的配置文件中添加 Atlas 的 MCP 端点:
json
{
"mcpServers": [
{
"name": "atlas-memory",
"url": "http://localhost:8000/api/atlas/mcp/user_001",
"tools": ["recall_memory", "write_memory", "forget_memory"]
}
]
}
重启 Claude Desktop,Agent 就可以:
- 用户在对话里说"帮我把之前那个项目信息调出来",Agent 调用
recall_memory检索语义记忆 - Agent 发现自己不知道"项目部署流程",可以调用
write_memory写入一条程序性记忆 - 用户纠正"我上次说的不对,应该用 pnpm 不是 yarn",Agent 调用
write_memory,同时触发 Supersession 机制标记旧记录为已替代
Atlas 提供三个 MCP 工具:
| 工具名 | 参数 | 作用 |
|---|---|---|
recall_memory |
query, user_id, include_superseded, include_catalog | 检索记忆,默认 top-10 |
write_memory |
memory_type, text, fact_type, confidence | 写入任意类型记忆 |
forget_memory |
memory_id | 删除指定记忆 |
接入的实际效果:我的 Agent 从一个"每轮对话都忘了自己是谁"的水平,变成了可以连续聊 50 轮还能准确引用用户两天前说过的话。不是说它变成了 AGI------但它再也不会问出"你刚才说的那个部署流程能再说一遍吗"这种让用户崩溃的问题了。
关键特性:Supersession / 时间衰减 / DLS
Supersession------记忆版 Git
用户说"等等,上次我说的话不对"------这是 Agent 记忆里最常见的场景。
如果只是简单覆盖,你会丢掉原始记录信息。Atlas 的做法是:当新的记忆写入时,如果 LLM 判断它是对旧记录的纠正,就设置旧记录的 superseded_by 字段指向新记录的 ID。检索时默认不返回已被 supersede 的记录,但如果你打开 include_superseded=true,可以看到完整的审计链。
这比"覆盖"更合理:至少你知道 Agent 的"认知"是怎么演变的,而不是突然就变了。
时间衰减------Gauss 曲线 Painless 脚本
场景记忆里的记录,半年后还有多大意义?Atlas 在检索时用了 Painless 脚本计算时间衰减:
bash
script_score = gauss(timestamp, offset=180d, scale=1825d)
- 前 180 天的记录权重不变(offset=180d)
- 之后的权重按 Gauss 曲线衰减,半衰期约 5 年(scale=1825d)
同时,被频繁检索过的记忆会有 use-count 加成:1 + log10(1 + use_count) * 0.2。
DLS 多租户隔离
SaaS 形态的 Agent 记忆系统,最怕的是 A 用户的记忆泄漏到 B 用户那里。Atlas 用的是 Elasticsearch 原生的 Document-Level Security(DLS):
- 每个用户一个 API Key,Key 的 role descriptor 内嵌了
user_id == X的 term filter - App 层再套一层 term filter 兜底
- 官方评测:零泄漏
如果你的 Agent 要做多租户(比如公司内部多个团队用同一个 Agent),DLS 是 Atlas 最值得关注的能力之一。
评测与性能
Atlas 的官方门控标准:
| 指标 | 门控值 | 实测值 |
|---|---|---|
| R@10 | ≥ 0.85 | 0.89 |
| R@5 | ≥ 0.75 | 已有数据待补充 |
| 泄漏数 | = 0 | 0 |
评测采用 QA-style passage retrieval:LLM 为每篇文档写 2 个问题,然后看检索系统能不能找到对应的文档。这是一个比较标准的召回评测范式。
不过要注意,Atlas 目前只是一个 demo 项目(MIT 协议),不是正式产品。官方的 GitHub repo 也只提供了快速启动的 demo 代码,不是生产就绪的部署方案。如果你要在生产环境用,至少需要自己处理:
- 高可用部署(目前 demo 是单实例 FastAPI)
- 监控与告警(无内置 metrics)
- 数据备份策略(需要自己配置 ES 快照)
什么时候值得用 Atlas
写到最后,给一个实用的决策参考:
适合用 Atlas 的场景:
- 你的 Agent 需要做多轮跨会话的长期记忆(不只是当前对话窗口)
- 你已经有 Elasticsearch 基础设施,或者愿意部署 ES 集群
- 你需要多租户隔离(SaaS 产品形态)
- 你想通过 MCP 快速接入 Claude Desktop / Cursor / Claude Code
不太适合的场景:
- 只是简单的对话历史存储(用 Redis 或 SQLite 就够了)
- 对延迟极度敏感(混合检索管线有 4 步,latency 比单一向量检索高不少)
- 需要极低维护成本(你不想管 ES 集群)
Atlas 不是万能的记忆方案,它解决了一个很具体的问题------Agent 需要结构化的、可检索的、多租户隔离的长期记忆。如果你正在被"Agent 记不住东西"这个问题困扰,它至少值得跑起来看一眼。
相比之下,与其花时间调 Prompt 让 Agent "记住用户说过的话",不如把存储层做对。Atlas 给出的思路是:记忆不是一个 Chat History,它应该是一个认知系统的三层结构。这个抽象层,才是 Agent 能长期工作的关键。
不是所有 Agent 都需要记忆,但需要长期交互的 Agent,没有一个好的记忆层是活不下去的。真正的 Agent 记忆不是一个数据库,而是一个认知系统------而 Atlas 证明了至少在架构上,ES 能把这件事做对。