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() 已經是 level 1。其餘都是同一個 object 上的 flags。
1. Conversation history
Thread 是一次 conversation。Resource 是擁有者:一個 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 系統。它對短 chats 是正確的預設。
2. Working memory
Working memory 是一塊 scratchpad,模型每個 turn 都看得到——即使幾百則 messages 之後,即使在新 thread 上(若 scope 是 resource)。
你給它空欄位。Agent 用 updateWorkingMemory 填進去。填好的區塊會注入 system instruction,用戶永遠看不到。
memory: new Memory({
options: {
workingMemory: {
enabled: true,
scope: "resource",
template: `# User Profile
- **Name**:
- **Preferences**:
- **Current Goal**:
`,
},
},
})- 它補充 history。它不取代 history。用在不會變的事實:名字、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。太多,模型會淹死。太少,你會錯過那個 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 是模型 此刻看見的。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是擁有者。跨 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不是模型可以自己挑的。
四個層級。同一個 Memory object。精妙之處在於你留下哪些 tokens,以及你願意忘掉哪些。