LLM 会忘。把最近 N 条 messages 塞进每次 request,十轮还能撑。遇到 tool dumps、新 thread,或任何停在第一条 message 的 goal,就会崩。
这是 Mastra 的四级阶梯,按他们推出的顺序。Alex Becker 在 Four levels of agent memory 里走的是同一套 stack。这不是 CoALA taxonomy(working / episodic / semantic / procedural)。那些名字描述的是知识种类。这四个层级描述的是tokens 住在哪里,以及你为留下它们付了什么代价。
所有层级都需要 storage。不带 options 的 new Memory() 已经是第一级。其余都是同一个 object 上的 flags。
1. Conversation history
Thread 是一次 conversation。Resource 是 owner:一个 user、一个 org、一个 project。Studio 会两边都生成。自己调用 generate 或 stream 时,你要传进去。
Mastra 从 storage 加载最近 N 条 messages,放进 context window。默认 lastMessages 是 10。
import { Agent } from "@mastra/core/agent"
import { Memory } from "@mastra/memory"
export const agent = new Agent({
id: "chat-agent",
name: "Chat agent",
instructions: "You are a helpful assistant.",
model: "openai/gpt-5-mini",
memory: new Memory({
options: {
lastMessages: 10,
},
}),
})- 从 client 只送新 message。Mastra 已经有 thread。把完整 history 再寄一遍是多余的,而且 client timestamps 会跟 store 里的 messages 抢顺序。
- Window 是滑动切割。若用户的 goal 在第一条 message,接着烧掉 30 轮 tool turns,goal 就没了。
- 新 thread 没有 history。同一个 agent、同一个人、空的 context。那是 session cookie,不是 intelligence。
- Tool results 会让切割来得比你预期更早。一次
llms.txtfetch 就可以是 10k tokens。
Failure: 把 history 当成一套 memory 系统。它对短聊天是正确的默认。
2. Working memory
Working memory 是一块 scratchpad,model 每一轮都能看见——即使几百条 messages 之后,即使换了新 thread(只要 scope 是 resource)。
你给它空字段。Agent 用 updateWorkingMemory 填进去。填好的 block 注入 system instruction,用户看不见。
memory: new Memory({
options: {
workingMemory: {
enabled: true,
scope: "resource",
template: `# User Profile
- **Name**:
- **Preferences**:
- **Current Goal**:
`,
},
},
})- 它补充 history。它不取代 history。用来放不变的 facts:名字、preferences、当前 goal。Coding agents 和任何 long-horizon task 都活在这里。
scope: "resource"是默认:每个 user 跨 threads 共用一块 scratchpad。scope: "thread"把它隔离到这次 conversation。切换 scopes 不会迁移数据。- 可以用 Zod
schema,而不是 Markdowntemplate。不能两个都用。Templates 每次 update 会替换整块。Schemas 会 merge:只送改过的 fields;把 field 设成null来删除。 - Working memory 故意很小。你必须预先定义 fields。当 observational memory 打开时,
observationalMemory.observation.manageWorkingMemory让 Observer 写 scratchpad,主 agent 就不必记得那个 tool。
Failure: 把 conversation summaries 塞进 template,或把它养成 event log。那是第四级。
3. Semantic recall
Semantic recall 是针对 message history 的 RAG,不是针对你的 product corpus。
每条新 message 都会被 embed,之后的 messages 会查询那个 vector store 找相似 turns。「I like dogs, mine is called Nas Barkley。」后来,在另一个 thread:「what animals do I like?」Lookup 按 meaning。Mastra 把 hits 注入成额外的 system block。
memory: new Memory({
storage: new LibSQLStore({ id: "agent-storage", url: "file:./local.db" }),
vector: new LibSQLVector({ id: "agent-vector", url: "file:./local.db" }),
embedder: new ModelRouterEmbeddingModel("openai/text-embedding-3-small"),
options: {
semanticRecall: { topK: 3, messageRange: 2, scope: "resource" },
},
})topK是命中数量。messageRange是每个 hit 要一并拉回的周围 turns。太多,model 会淹死。太少,你会错过那个 fact。scope: "resource"搜索该 user 的所有 threads;LibSQL、Postgres、MongoDB、OracleDB 与 Upstash 支持它。- 默认关闭。它需要 vector store 与 embedder。Write 与 query 用同一个 embedding model,规则跟 document RAG 一样。
- Recall 不精确——你得按产品调
topK与messageRange。你现在要跑 embedder 与 vector store。每一轮都有 latency。 - 如何搭建一套 RAG 系统 里的 org-scoped document index 是 agent 用 tool 搜索的 library。Message RAG 是「我们说过什么」。不同 indexes。不同 tenancy。不同 tools。
Failure: 注入的 system block 会随 query 改变。Prompt prefix 永远不够稳定,打不中 cache。在 production,cached input tokens 通常是最大的节省。Semantic recall 把它们花掉。
4. Observational memory
Observational memory 建模的是人如何记得,以及如何忘记。两个 ambient agents:Observer 与 Reflector。它们一直在。它们不总是在跑。
当 message tokens 越过阈值(默认 30,000;demos 常用 2k 好让你看着它发生),Observer 把 raw history 压成一份 dense observation log:priority markers、timestamps,以及仍然重要的那一丝。一次 10k-token 的 tool result 可以变成约 160 tokens。
当 observation log 本身越过它的阈值(默认 40,000),Reflector 重写整份 log。它丢掉低优先级行、合并相关 facts,并让 window 保持有界。Reflections 不会叠成第三层无限层。每次 reflection 就是 新的 log。新的 observations 接在它后面。
Recent messages → Agent context
Recent messages
-(crosses messageTokens)→ Observer → Observation log → Agent context
Observation log
-(crosses observationTokens)→ Reflector → Observation log- Observations 是 stable 的。它们 append。它们不会每一轮都重排 system prefix——这是对 semantic recall 那个 cache 论点的反转。
- Observer 与 Reflector 在背景跑。在 Studio 里你可以点它们做 demo。在真正的 agent 里它们是 async、non-blocking。Coding harness 里的 compaction 常常让用户停一分钟,还丢掉错误的细节。这个 loop 两边都不该做。
memory: new Memory({
storage: new LibSQLStore({ id: "memory-storage", url: "file:./memory.db" }),
options: {
observationalMemory: { model: "google/gemini-2.5-flash" },
},
})observationalMemory: true把 Observer/Reflector model 默认成google/gemini-2.5-flash。Storage 是必需的。目前支持的 adapters:@mastra/pg、@mastra/libsql、@mastra/mysql、@mastra/mongodb、@mastra/convex、@mastra/oracledb。- 影片里 Mastra 的 LongMemEval 数字,前三行用同一个 model:working memory 约 55%(不是为这个 benchmark 设计的)、semantic recall 约 80%、observational memory 约 84%。Gemini 2.5 Flash 在 OM 上被报成约 95%。把这些当成 Mastra 公布的分数,不是独立 bake-off。
- 这是能撑过 noisy tool calls 的层级:page snapshots、
llms.txt、MCP dumps。它也会累积——一个连续几周写活动文案的 workshop-helper,会把「second person, short hook, no hype」留成 observations,而不是你记得放进 template 的一个 field。 retrieval: true给 agent 一个recalltool,可以回到产生 observation 的原始 messages。{ vector: true }再给那个 store 加上 semantic search。Compression 不必等于原文消失。observation.manageWorkingMemory让 OM 接管 scratchpad。Working memory 保持小而 structured;OM 让主 agent 不必为此花一次 tool call。
5. Which level
| Level | Primitive | 适用场景 | 何时失效 |
|---|---|---|---|
| Conversation history | lastMessages | 短 threads、UI transcript | Goal 滑出 window;新 thread |
| Working memory | workingMemory | 稳定 facts 与当前 goal | 你需要 event log 或未声明的 fields |
| Semantic recall | semanticRecall | 跨很长、多 thread history 的稀疏 facts | 你需要 prompt cache,或 recall 太模糊 |
| Observational memory | observationalMemory | Long horizon、noisy tools、cache-stable context | 你拒绝跑 storage adapter |
- Mastra 目前对 long-context agents 的建议是 observational memory。前面几级仍然存在,仍然可以组合。
- History 是 model 此刻看见的。Working memory 是表单。Semantic recall 是搜索。Observational memory 是让 window 保持小、又不至于空白的方式。
- 不带 options 的
new Memory()就是 conversation history。这个 repo 里的 weather agent 就是这样。当 thread 不再只是聊天时,再升级。
6. Invariants
- 通过 storage adapter 持久化。Memory 不是 context window。
- Client 送新 message。Server 加载 thread。绝不要两边都做。
thread是 conversation。resource是 owner。跨 thread recall 是一次 resource query,不是缺了threadId。- Working memory 是一块小的、始终在线的 block。不要把它养成日记。
- 针对 messages 的 semantic recall 不是 document RAG。不同 index,不同 tenant 故事。两边 write 与 query 都用同一个 embedding model。
- Observational memory 靠 append observations 保持可 cache 的 prefix。Semantic recall 是故意打爆那个 prefix。
- Isolation 是 server 的事。
resource不是 model 可以自己挑的。
四个层级。同一个 Memory object。精妙之处在于你留下哪些 tokens,以及你愿意忘掉哪些。