← 返回 Tony's Lab
发布于 2026-08-24 · 调研笔记
Observational Memory · @mastra/memory ≥ 1.1.0

观察式记忆 让 Agent 像人一样:不记每句话,只留值得记的

Mastra 的长期上下文记忆系统。两个后台 Agent——Observer(观察者)和 Reflector(反思者)——盯着对话,把不断膨胀的原始消息压缩成一条条精炼的观测日志。

压缩比 5–40× 观察阈值 30k tokens 反思阈值 40k tokens 默认模型 gemini-2.5-flash
context-window oscillation 缓冲观察中…
消息历史 ↯ 观察日志 ▔ —— 每轮「观察激活」把 ~24k tokens 压成 ~3k 观测;达到 40k 时反思整卷重写

一句话看懂the pitch

你不记得每场对话的每一个字——你下意识地观察发生了什么,然后大脑反思,重组、合并、压缩成长期记忆。OM 就是把这套机制装进了 Agent。

开启后,Agent 的上下文窗口不再无限膨胀:原始消息在后台被持续压缩成观测日志(observation log),日志太长时再被压缩成反思(reflections)。Agent 始终只看到"值得看的那部分",而不是一坨越拖越长的聊天记录。

一句话:OM 同时接管了 message history 和 working memory 的职责,官方称其准确率更高、成本比 Semantic Recall 更低。

它解决什么问题two enemies

上下文窗口是有限的。即使模型支持超大 token 上限,窗口塞满时表现也会变差。这会带来两个问题:

context rot · 上下文腐烂

历史越长,表现越差

Agent 携带的原始消息越多,越容易被无关内容干扰,偏离当前任务。工具调用、页面快照、中间思考……全是噪声。

context waste · 上下文浪费

大部分 token 已无用

历史里的大多数 token 对"保持 Agent 在线"早已没有贡献,却仍然每轮付费、每次拖慢响应。

OM 的解法:把旧上下文压缩成密集观测,同时解决问题。典型例子——一个用 Playwright MCP 的 Agent,每次页面快照就是 50,000+ tokens;有了 OM,Observer 把交互过程凝成几百 tokens 的观测,Agent 不再背着每一张原始快照。

三层记忆系统three tiers

无论对话跑多久,OM 的记忆始终围绕三个层级组织,内存有界,不无限增长:

T1
近期消息

当前任务的精确对话历史,原样保留,负责"现在在做什么"。

≈ 6k tokens 常驻
T2
观测日志

Observer 写下的浓缩笔记:发生了什么、当前任务、建议的下一步。

≈ 40k tokens 有界
T3
反思

Reflector 把观测合并、提炼模式后的更浓缩层,重写整个日志。

由 Reflector 持续压缩

上下文如何随时间变化observe-and-shrink cycle

默认设置下,上下文窗口不会无界增长——它在一个"观察-收缩"的循环里振荡(右侧动画即模拟这一过程):

  1. 0 → 30k tokens:消息历史正常增长。后台 Observer 每 ~6k tokens 缓冲一批观测(bufferTokens: 0.2)。

  2. 30k 触发:缓冲的观测立即激活。被观察的消息移出上下文窗口,只留 ~6k tokens 近期历史(bufferActivation: 0.8 保留阈值的 20%)。

    被移除的 ~24k tokens 按典型 5–40× 压缩比变成约 1–5k tokens 的观测。

  3. 重复:历史从 ~6k 再涨回 30k、再次收缩。每一轮向观测日志追加内容,而日志增长速度远慢于原始历史。

  4. 观测达 40k:Reflector 基于当前观测和更早的反思生成一份更小的日志。循环继续,但内存始终有界。

这些是"激活阈值"而非硬上限:当后台缓冲跟不上时历史可以越过阈值。高于 blockAfter(默认 1.2,≈36k)时允许激活越过保留目标;开启 shareTokenBudget 后两个预算池共享,观测日志还小时消息历史最多可借到 ~70k tokens。

Observer 观察者how observations are made

当消息历史 token 超过阈值(默认 30,000),Observer 创建观测——关于"发生了什么"的简明笔记。它用本地快速 token 估算做阈值判断:文本用 tokenx,图片部分用 provider 感知的启发式,多模态对话也能在正确时机触发观察。

观测长这样

一条条带优先级标记和时间戳的浓缩事实——Observer 还会顺带追踪当前任务和建议的响应,让 Agent 在窗口收缩后无缝接续。

2026-01-15
🔴 12:10 User is building a Next.js app with Supabase auth, due in 1 week
  - 🔴 12:10 App uses server components with client-side hydration
  - 🟡 12:12 User asked about middleware configuration for protected routes
🔴 12:15 User stated the app name is "Acme Dashboard"

缓冲与提前激活

Open observation.threadTitle 后,话题明显转变时 Observer 还能建议一个短标题(Mastra Code 就是用它给线程列表更新标题)。

OM 支持在达到 token 阈值之前激活缓冲的观测——适合提示词缓存即将过期、或 Agent 切换模型供应商的场景:

activateAfterIdle空闲即激活

线程空闲超过设定时间就激活缓冲观测。接受毫秒数、"5m" 等时长字符串,或 "auto" ——按当前模型的提示词缓存 TTL 自动选择(OpenRouter/xAI 5 分钟、Gemini 24 小时、DeepSeek 1 小时等)。

activateOnProviderChange换模型即激活

下一步要使用不同 provider/model 时强制激活——换模型必然作废提示词缓存,与其把大段原始窗口发给无法复用缓存的新 provider,不如先压缩。反思的提前激活是 opt-in,不继承顶层设置。

空闲缓冲 & 附件

observation.bufferOnIdle 设为 true:Agent 回合结束进入空闲时立即做后台观测缓冲,适合"短回合很多、不想等下一次对话或阈值"的应用(默认关,与 bufferTokens 的步内异步缓冲相互独立)。

Observer 能看到历史中的附件:文本转录里保留 [Image #1: reference-board.png] 这类可读占位符,真实附件则随文本一并转发;图片类 file 部件会尽量升级为图片输入。若 Observer 模型是纯文本或拒绝多模态输入,设 observeAttachments: false 丢弃附件(占位符仍在),或用 'auto' 让 Mastra 按模型能力自动决定。

Reflector 反思者when observations get long

当观测超过阈值(默认 40,000),Reflector 登场:压缩观测、合并相关条目、并反思其中的模式。

关键设计:反思不会积累成一个独立且无限增长的层。每次反思都会重写整份观测日志——Reflector 的输出成为新日志,新观测在其后追加;下次达到阈值时,Reflector 重新处理一切(包括更早的反思),对旧信息压缩得更激进,同时保留近期细节。

因此无论对话跑多久,内存都围绕反思阈值保持有界。⚠️ 注意:gemini-2.5-flash 极擅长在长输出中保留细节,可能导致反思在最大压缩重试后仍高于阈值——此时它会返回重试中最小的非退化候选来终止循环。想要更激进的压缩,可给 Reflector 换 grok-4-1-fast、deepseek-v4-pro 等,Observer 继续用 gemini。

提取器 Extractorpersist structured values

想要 OM 在观测之外持久化特定值?用提取器。内置的当前任务、建议响应、线程标题和自定义值走同一条提取管线。

带 schema 的提取器会作为一次结构化的 follow-up 输出请求运行;不带 schema 的是内联字符串提取,直接写进 Observer/Reflector 的响应里。默认情况下,后续运行会把上次提取的值展示给提取器;设 includePreviousExtraction: false 可让 Observer 看不到旧值。

// 从观测中提取一份紧凑的用户画像
const memory = new Memory({
  options: {
    observationalMemory: {
      model: 'openai/gpt-5-mini',
      observation: {
        extract: [
          new Extractor({
            name: 'User profile',
            instructions: 'Extract stable user profile facts.',
            schema: z.object({
              preferredName: z.string().optional(),
              timezone: z.string().optional(),
              tools: z.array(z.string()).optional(),
            }),
          }),
        ],
      },
    },
  },
})

从流中读取提取结果——OM 完成一次观察或反思时,data-om-observation-end(同步完成)与 data-om-buffering-end(后台完成)事件会带上 extractedValues 和 extractionFailures;提取器失败不影响其他成功值。

另有 onExtracted 回调可在持久化前规范化或做出反应(如发信号给前端);observation.manageWorkingMemory 让 Observer 自动管理工作记忆——主 Agent 不再需要自己记得调用工作记忆工具,且自动把工作记忆移入 state signals 以保持提示词缓存友好。

检索模式 Retrievalget the exact words back

压缩的代价是"原话没了"。检索模式解决这个:每个观测组都保留指向其来源消息的 range(如 startId:endId),并注册一个 recall 工具,让 Agent 在需要精确措辞、工具输出或时间线时翻回原始消息。

retrieval: true纯浏览

启用 recall 工具翻阅原始消息。不需要向量库。默认可浏览当前 resource 的全部线程。

retrieval: { vector: true }语义搜索

复用 Memory 上配置的向量库与 embedder。新观测组在缓冲时与同步观察时自动索引(非阻塞),语义搜索返回"观测组 + 它对应的原始消息 ID 区间",边说结论边给出来源。

recall 工具能力:按任意观测组区间翻页原始消息(可设 detail 粒度)· mode: "search" 语义搜索(需 vector)· 列出/浏览全部线程(默认 scope: 'resource')· 设 scope: 'thread' 则只限当前线程。自定义指引用 retrieval.instructions 追加,内置指令永不被覆盖。

作用域 Scopethread vs resource

thread · 默认每线程独立观测

每个线程有自己的观测记录,久经考验,适合作通用记忆系统,尤其适合长时间 agentic 任务。⚠️ 必须提供有效 threadId,否则直接抛错——防止多个线程静默共享一份观测记录导致数据库死锁。

resource · 实验性跨会话记忆

观测在某个 resource(通常是用户)的所有线程间共享,实现真正的跨对话记忆。注意:每个线程都变成"该用户所有线程的一个视角",可能需调整 system prompt 防止一个线程接着做另一个线程没做完的活。

⚠️ WARNING

resource 作用域下,所有线程的未观察消息会一起被处理——历史线程很多的用户首次启用会非常慢;且 resource 作用域不支持异步缓冲(自动禁用)。现有应用请用 thread 作用域。

Token 预算与缓冲设置token budgets

OM 用 token 阈值决定何时观察、何时反思。核心默认值:

设置项默认作用
observation.messageTokens30,000何时运行 Observer(消息历史阈值)
reflection.observationTokens40,000何时运行 Reflector(观测日志阈值)
observation.bufferTokens0.2缓冲频率:每 20% × messageTokens 跑一次后台观察(≈6k);也可用绝对数如 5000;false 关闭异步缓冲(观察+反思都变同步)
observation.bufferActivation0.8激活时清窗力度:保留 messageTokens 的 20%(≈6k);调小保留更多历史
observation.blockAfter1.2安全网:缓冲跟不上时允许激活越过保留目标(1–100 之间乘阈值,100+ 为绝对 token 数)
reflection.bufferActivation0.5观测达到阈值 50% 时开始在后台反思
reflection.blockAfter1.2反思的安全阈值,超出时无缓冲反思可用则同步运行
shareTokenBudgetfalse两个预算池共享:观测日志小时消息历史可借空间(上限约 70k);需 bufferTokens: false

缓存友好的建议

依赖提示词缓存的话:把 activateAfterIdle 设为 "auto" 或具体 TTL——线程空闲够久、缓存即将过期时,下一次请求先激活缓冲观测、发送更小的压缩窗口,而不是把大段原始消息发给注定无法命中缓存的请求。模型中途切换时用 activateOnProviderChange: true 同样避免把大窗口发给无法复用缓存的新 provider。

Token 估算另有逐 part 缓存(存在 part.providerMetadata.mastra,缓存版本/tokenizer 源匹配时才复用);文件部件可调用方直接提供 tokenEstimate 覆盖估算(仅对 image/file 部件生效)。observation.previousObserverTokens 可限制每次发给 Observer 的旧观测量(默认 2k,0 完全不发,false 不截断)。

模型建议models

Observer 和 Reflector 在后台运行,任何支持 Mastra 模型路由的模型(provider/model)都可用。未设置时默认 google/gemini-2.5-flash。建议选上下文窗口大(128K+)且足够快的模型,别拖慢用户操作。

官方实测过的模型:gemini-2.5-flashgpt-5-miniclaude-haiku-4-5deepseek-reasonerdeepseek-v4-pro / flashgrok-4-1-fastqwen3glm-4.7

@mastra/memory@1.10.0 起支持 ModelByInputTokens:按输入 token 量分级选模型——小输入用便宜快速的模型,大上下文换更强的模型,运行时按实际输入量解析层级。

快速开始quickstart

// 1) 最简单的开启方式
const agent = new Agent({
  id: 'my-agent',
  instructions: 'You are a helpful assistant.',
  model: 'openai/gpt-5-mini',
  memory: new Memory({
    options: { observationalMemory: true },
  }),
})

// 2) 完整示例:本地 LibSQL + 指定观察模型
const memory = new Memory({
  storage: new LibSQLStore({ id: 'memory-storage', url: 'file:./memory.db' }),
  options: {
    observationalMemory: {
      model: 'deepseek/deepseek-reasoner',  // 默认 gemini-2.5-flash
    },
  },
})

// 同一 resource + thread 复用 → 第二次回答记得第一次的对话
const opts = { resource: 'user-123', thread: 'conversation-123' }
await agent.generate('Remember that my favorite color is blue.', { memory: opts })
await agent.generate('What is my favorite color?', { memory: opts }) // → blue
⚠️ 客户端接入时

只发新消息,别发完整会话历史。OM 本身依赖存储的历史;客户端重发全量历史既冗余,且客户端时间戳与存储时间戳冲突时会造成消息排序 bug。

⚠️ 存储要求

OM 需要存储 provider,且目前只支持 @mastra/pg、@mastra/libsql、@mastra/mysql、@mastra/mongodb、@mastra/convex、@mastra/oracledb。时间间隙标记(temporalMarkers)默认关闭,开启后消息间隔 ≥10 分钟会插入一条"时隔提醒",Observer 也能据此把记忆锚定到时间点。

与其他记忆功能对比vs other memory features

功能定位与 OM 的关系
Message history当前对话的高保真记录OM 接管其生命周期,messageTokens 决定保留多少原始历史
Working memory小型结构化状态(JSON/MD):偏好、名字、目标OM 更适合"会长大的摘要/状态";WM 留给小而结构化的数据
Semantic Recall基于 RAG 检索相关旧消息OM 声称准确率更高、成本更低;retrieval 模式可兼得语义搜索

注意:如果用 working memory 存"越来越长的对话摘要或状态",OM 是更好的替代——它自动管理消息历史,长对话事件日志正是它的主场。旧线程无需迁移:OM 惰性读取,首次超阈值时自动处理存量消息。

调研小结takeaways

OM 的本质:把"无限增长的上下文"换成"有界的、不断自我压缩的记忆"。Observer 负责把消息变观测(5–40× 压缩),Reflector 负责把观测变反思(整卷重写),三层结构让 Agent 同时拥有精确的近期记忆和浓缩的长期记忆。

最值得抄的三个设计:

① 异步缓冲——观察在后台预计算,阈值触发瞬间激活、零停顿,绝不打断对话;② 缓存友好——稳定、追加式的上下文让提示词前缀始终可缓存,配合空闲/换模型提前激活省成本;③ 检索兜底——压缩丢了原话?recall 工具随时翻回原始消息,且带来源 range,可追溯。

适用场景:长时间 agentic 任务、Mastra Code 这类编码 Agent、跨会话个人助理(resource 作用域)。成本:每次观察/反思是一次后台 LLM 调用,模型与频率由阈值表完全可控。