跳至正文
Agents — Mastra 项目原理:Agent Loop、Workflow 与状态恢复

Mastra 项目原理:Agent Loop、Workflow 与状态恢复

Mastra 官方仓库

AI 参与说明(Agent:Codex):本文由 Codex 根据 Mastra 官方文档、Context7 检索结果与固定提交源码辅助分析,并使用项目 Anatomy 制作核心机制交互、使用 humanizer-zh 编辑中文。资料整理于 2026-10-16,源码基线为 a1fbf275e587d56a86c52295eceff6c0756169e8;该提交中 @mastra/core 的版本字段为 1.78.1-alpha.0,不代表 npm latest。本文区分源码行为、实际验证与教学模型,采用建议属于工程判断。运行记录:模型 gpt-6.1-sol,reasoning effort ultra,执行入口 Codex Desktop,提供方 openai;CLI 版本 0.162.0-alpha.17.2,不代表桌面 App 版本。

Mastra 把模型调用、工具执行、工作流、上下文管理和存储接到同一套 TypeScript 运行时中。理解它的关键,是分清两种控制权:Agent 让模型决定下一次操作,Workflow 让程序显式规定步骤关系。两者可以嵌套,但它们对“接下来执行什么”的决定方式不同。

本文先给出整个项目的功能与代码地图,再沿一次 Agent 请求和一次 Workflow 暂停恢复解释数据怎样变成结果。若只想理解它为什么能“暂停后继续”,可以先看正文内的 Workflow 实验。

先认识几个术语

英文术语中文名称简要解释
Agent Loop智能体循环请求模型、执行其选择的工具,再把结果交回模型
Tool call / Tool result工具调用 / 工具结果模型提出的操作请求,以及程序实际执行后的返回值
Workflow工作流用代码规定步骤、分支、并发与恢复关系的过程
Step步骤有输入、输出约束和执行函数的工作单元
Run单次运行Agent 或 Workflow 的一次具体执行,由 runId 标识
Snapshot快照可序列化的执行状态,用于保存结果与恢复位置
Thread对话线程将一组对话消息关联到同一段历史的对象
Processor处理器在模型请求、流式输出等边界检查或修改数据的扩展点
Storage / Vector store存储 / 向量存储前者保存结构化状态,后者支持向量相似度检索

功能地图:一项能力对应哪段代码

Mastra 是一个 monorepo。@mastra/core 提供运行时契约与主要执行机制,Memory、RAG、数据库、模型与部署适配等能力由其他包补齐;CLI 与 Studio 则负责开发、检查和交互。下面的源码链接全部固定到本文分析提交,避免 main 更新后行号失去参照。

能力对外入口与作用核心代码入口
组件装配new Mastra({ agents, workflows, storage, ... }) 统一注册并注入共享服务core/src/mastra/index.ts
Agent 与模型接入Agent.generate() / stream() 管理多轮请求、工具与输出;模型适配器处理 Provider 协议agent/agent.ts、model.loop.ts
Tools 与 MCPcreateTool() 定义参数和执行函数;MCP Client 把远端 Tools 包装为本地可调用对象,MCP Server 可反向暴露 Agent / Workflowtools/tool.ts、mcp/src/client/client.ts、mcp/src/server/server.ts
WorkflowcreateStep()、createWorkflow() 定义顺序、分支、并发及暂停恢复workflows/workflow.ts、handlers/step.ts
Memory读取历史、Working Memory、Semantic Recall 或 Observational Memory,把相关上下文加入本次请求memory/src/index.ts、core/src/memory/memory.ts
RAGMDocument 解析与分块;createVectorQueryTool() 把检索接到 Agent 工具链rag/src/document/document.ts、rag/src/tools/vector-query.ts
Storage 与 Vector store按领域保存消息、Workflow、Trace 等;向量检索使用独立接口storage/base.ts、vector/vector.ts
Processor在输入、每轮请求、模型响应、工具结果和流式输出边界处理数据processors/index.ts
Observability 与 Evals前者记录调用和状态,后者按规则或 LLM judge 评价输出observability/mastra/src/default.ts、evals/base.ts
Workspace组合文件系统、Sandbox、检索和 Skills 等 Agent 工作能力workspace/workspace.ts
HTTP、SDK 与开发界面Server adapter 绑定 API,Client SDK 远程调用,CLI 运行开发与构建,Studio 查看和调试资源server-adapter、client-js、CLI、Studio

这张表列的是与执行机制直接相关的主要能力。Voice、Channels、后台任务等扩展不在本文展开;不能因为某项能力存在入口,就认定任意 Provider 或部署环境都支持相同保证。

运行架构:把定义、执行、状态和界面分开

应用可以把 core 嵌进已有后端直接调用,也可以运行 Mastra Server,让前端通过 Client SDK 访问。Studio 使用同一类服务接口查看 Agent、Workflow、Memory 和执行记录;它不是每次模型推理的必经节点。Server routes

flowchart TB
  App[应用代码] --> Registry[Mastra<br/>注册与共享服务]
  UI[前端 / Studio] --> SDK[Client SDK]
  SDK --> Server[Server adapter<br/>HTTP 路由与处理]
  Server --> Registry
  Registry --> Agent[Agent / Agent Loop]
  Registry --> Workflow[Workflow / Execution engine]
  Agent --> Model[Model adapter / Provider]
  Agent --> Tools[Tool / MCP / Workspace]
  Agent --> Context[Processor / Memory]
  Context --> Storage[Storage / Vector store]
  Workflow --> Storage
  Agent -.执行记录.-> Observe[Observability]
  Workflow -.执行记录.-> Observe

图中实线表示运行时调用或服务使用关系,不是 npm 依赖箭头。代码依赖方向则以 core 的抽象为中心:@mastra/memory、@mastra/rag、@mastra/mcp、@mastra/server 等围绕这些契约实现能力;@mastra/deployer 使用 Server 构建运行入口,CLI 再调用开发和构建逻辑。Studio 的前端源码位于 packages/playground 与 packages/playground-ui,远程 SDK 位于 client-sdks/,数据库实现位于 stores/,Observability 实现位于 observability/。不要只看 packages/ 就认为已经看到全仓。工作区定义、Deployer

Mastra 的作用更接近应用装配入口。注册 Agent 时,它注入 Storage、logger、其他 Agents、Vector store 等共享能力;注册 Workflow 时,也把执行引擎所需服务连接起来。注册表让组件能够协作,真正的执行规则仍在 Agent Loop 和 Workflow engine 内。Agent 注册

核心数据模型:配置对象不等于运行状态

对象关键字段或结构在执行中的作用
Agent definitioninstructions、model、tools、memory、inputProcessors、outputProcessors、errorProcessors定义模型能收到什么指令、使用什么能力
MessageList规范化消息、system messages、来源标记统一处理输入、历史、响应与工具结果,再转换为 Provider 的消息格式
MastraDBMessageid、role、content.parts、createdAt、threadId、resourceId保存结构化消息;工具调用及结果不只是拼成文本
Tool invocationtoolCallId、toolName、args、状态与结果把模型的调用请求关联到同一次工具执行及其返回值
Workflow / Step输入输出 Schema、Step ID、execute、步骤图定义一次执行可以沿哪些路径运行,以及数据契约
Run / SnapshotrunId、context、Step 状态、suspendedPaths、结果保存已发生的执行,并定位恢复入口
StorageThreadTypeid、resourceId、时间、可选标题和元数据关联跨多次请求的会话历史

消息的内部格式保留 content.parts,模型协议转换时才生成对应 Provider 接受的 assistant Tool call 与 tool result。MessageList 还记录消息来自 input、memory、response、system 或 context,方便不同阶段读取与处理。消息类型、MessageList

runId、threadId 和 resourceId 回答三个不同问题:这是哪次执行、属于哪段对话、归属于哪个资源。一个 Thread 可以包含多次 Agent 请求;resourceId 可以支持跨 Thread 的上下文作用域,但它本身不是身份验证或访问授权。应用仍须从可信身份中确定归属并执行权限检查。Thread 类型

Agent:一次输入怎样变成多轮模型请求

入口共享,Provider 调用方式仍有区别

Agent.generate() 和 Agent.stream() 都进入私有 #execute()。它解析模型、指令、Thread、Resource、Tools 和处理器,再创建内部的准备 Workflow。generate() 最后消费内部输出管线并返回完整结果;stream() 返回可逐步读取的 MastraModelOutput。generate、stream

text
Agent.generate() / Agent.stream()
  → #execute()
  → createPrepareStreamWorkflow()
      parallel(prepareToolsStep, prepareMemoryStep)
      → mapResultsStep → streamStep
  → MastraLLMVNext.stream() → loop()
  → AgenticLoopBuilder
      LLM execution → Tool execution → result mapping → continuation check

准备阶段把工具解析和上下文准备并行执行;prepareMemoryStep 建立 MessageList 并运行输入处理器。随后 streamStep 把解析好的模型、工具和消息交给 LLM 层。这里的内部流式管线并不意味着两种 API 都向 Provider 请求流式响应:底层仍按 methodType 选择 model.doGenerate() 或 model.doStream()。准备阶段、上下文准备、Provider 边界

模型提出请求,框架执行操作

假设应用注册一个 weather Tool,模型第一轮返回调用:toolCallId=call-1、toolName=weather、args={city: "Seattle"}。模型只是产生结构化请求;框架查找本轮可用 Tool,校验和执行应用函数,随后把结果按同一个 toolCallId 合入消息。

sequenceDiagram
  participant App as 应用
  participant Agent as Agent Loop
  participant Model as Model adapter
  participant Tool as weather Tool
  App->>Agent: generate(messages, options)
  Agent->>Model: 指令、历史、输入、Tool schemas
  Model-->>Agent: Tool call(call-1, weather, city)
  Agent->>Tool: execute(args, context)
  Tool-->>Agent: Tool result
  Note over Agent: 按 toolCallId 合入 MessageList
  alt 仍满足继续条件
    Agent->>Model: 包含 Tool result 的下一轮消息
    Model-->>Agent: 最终文本 / 新 Tool calls
  else 预算耗尽或暂停
    Note over Agent: 本次请求不再发起下一轮模型调用
  end
  Agent-->>App: MastraModelOutput / 完整结果

AgenticLoopBuilder 本身使用 Workflow 的 .dowhile() 和 .foreach() 组织循环:一轮先执行模型,再调度 Tool calls,映射结果并检查是否继续。因此 Mastra 的 Agent 不只是薄薄一层 generateText() 包装,它复用了自己的编排机制来管理状态与分支。循环构造、循环外层

普通本地 Tool 最终进入 executeToolCall(),调用 tool.execute(args, toolOptions)。工具不存在或执行出错时,许多普通错误会成为模型可见的 Tool error,为下一轮纠正提供依据;授权拒绝、取消与审批挂起有各自的处理路径,不能一律当作成功返回。由 Provider 自己执行的 Tool,也不会再由本地重复执行。工具执行、Provider-executed 分支、结果与错误映射

一个实用设计是 toModelOutput:Tool 可以给业务层返回完整对象,只把精简结果交给模型。例如工具返回温度、湿度和原始数据,下一轮模型只收到天气摘要。这减少了上下文中无用的数据,但也意味着“工具原始返回值”和“模型实际看到的内容”可能不同。上游固定模型测试专门检查了这次转换。映射实现、上游测试

怎样结束,预算在数什么

maxSteps bounds counted LLM steps, not individual Tool calls. 一个模型 Step 可以提出多个 Tool calls,不能拿工具数量直接换算模型轮次。该版本在既未设置 maxSteps、也未设置 stopWhen 时,默认使用 5 个计数 Step;显式设置 maxSteps 时,它与自定义 stopWhen 构成 OR 条件。只传自定义 stopWhen 时,源码不会额外补上默认 5 轮。停止条件构造

模型正常结束、步数上限、Processor 拦截、取消、工具审批和暂停都可能让本次请求停止。length、error、content-filter 等终止原因不会因为还有 Tool call 就无条件重开循环;Processor 请求的重试另受其预算约束,网络重试也不是 maxSteps。继续判定

本次在 Node.js 26.0.0 与 @mastra/core@1.78.1-alpha.0 中运行了无 API Key 的固定模型测试:第一轮必定请求 weather,工具返回固定的 Seattle 天气;第二轮检查实际收到的消息确实包含 role: tool、原 toolCallId 和返回对象,再输出预设答案。只改变 maxSteps,得到以下结果:

条件模型调用次数Tool 执行次数最终文本finishReason
maxSteps: 221Seattle: 72F, sunny.stop
maxSteps: 111空字符串tool-calls

第一轮 Tool 已完成,并不意味着程序还有预算请求模型组织答案。这个测试核对的是框架编排与消息传递;天气、模型决策和最终文本都是固定数据,不能据此评价真实模型的推理质量。上游还有更完整的 Tool / toModelOutput 测试,可继续沿同一路径阅读。

Workflow:显式连接输入、输出和执行位置

createStep() 将 Schema 和 execute 绑定为 Step;.then() 等方法构造执行图;.commit() 完成构图并登记 executionGraph;createRun() 为一次执行创建或复用 Run,并按配置保存初始状态。它们没有把业务函数编译成另一个独立程序,恢复时仍需要可用的 Workflow 定义。Step 构造、commit 与 createRun

数据怎样流向下一步

本文以下细节限定默认 DefaultExecutionEngine。Run.start() 进入 _start(),验证初始输入,再调用引擎的 execute()。引擎遍历 execution entries,executeEntry() 根据类型分派;普通 Step 最终调用 step.execute()。成功返回后,输出成为后一个 entry 的输入,并更新 Step 状态及 Snapshot。start、默认引擎遍历

连接方式数据规则容易误解之处
.then(step)上一 entry 的输出进入下一 Step不会按同名属性自动替应用映射任意结构
.map(...)明确从 Step 结果、初始输入等构造下一份输入这是数据映射,不是数组逐项执行
.parallel([a, b])各分支读取同一份上游数据,结果按 Step ID 聚合下游收到的是聚合对象,不是某一分支的裸输出
.branch([...])评估条件,执行所有为真的分支,再聚合结果不是只执行第一个匹配项的 if / else
.foreach(step)对上游数组逐项执行,结果按原索引组织默认并发数为 1,不能把它直接理解成全并发

这些结构都在 Workflow API 构造图,但实际调度集中于 handlers。尤其 .branch() 会选择所有满足条件的分支;如果业务要求只能命中一个,应用应让条件互斥。构图 API、控制流执行

暂停后保存什么,恢复时读什么

Snapshot 保存的是可序列化状态:初始输入位于 context.input,每步输入位于 context[stepId].payload,成功输出位于 output,暂停数据位于 suspendPayload;value 保存 Workflow state,suspendedPaths 标识恢复路径。图结构也可序列化,但不会把函数闭包和 JavaScript 调用栈保存到数据库。Snapshot 类型

flowchart TB
  Start[Run.start / inputData] --> Execute[按执行图调用 Step.execute]
  Execute --> Decision{Step 的执行结果}
  Decision -->|success| Next[保存 output<br/>作为下个 entry 的输入]
  Next --> Execute
  Decision -->|suspended| Snapshot[保存 payload / suspendPayload<br/>与 suspendedPaths]
  Snapshot --> Wait[结束当前请求<br/>等待外部决定]
  Wait --> Resume[Run.resume / resumeData]
  Resume --> Restore[加载 Snapshot<br/>验证并定位目标 Step]
  Restore --> Reenter[使用保存的 payload<br/>重新进入目标 execute]
  Reenter --> Decision
  Next -->|执行图已完成| Result[保存并返回最终 result]
  Decision -->|failed / canceled 等| Exit[返回相应终态]

resume() 加载原 Snapshot,确定目标 Step 并校验 resumeSchema,再把保存的 context、value、暂停路径和新的恢复数据交给引擎。默认引擎从该路径对应的 entry 开始;目标 Step 读取自己的旧 payload,resumeData 则来自这次调用。已经成功完成的前置 entries 不会因普通恢复而全部重跑;并行、嵌套和逐项执行还有各自路径规则。恢复入口、恢复输入选择

遇到多个暂停 Step 时,需要明确恢复目标。一次请求返回 suspended 表示引擎已将执行交回外部,并不意味着原进程中一直有一个等待人工点击的函数栈。

Anatomy:恢复时,金额究竟从哪里来

这里解剖的是默认 Workflow 引擎的 Step 恢复机制,对应 Run.resume()、DefaultExecutionEngine 与 Step handler。我们假设一个三步流程:normalize → approval → receipt。金额超过 100 时,approval 保存输入并暂停;恢复时,程序把 Snapshot 中保存的金额与新传入的 resumeData.approved 组合,交给 receipt。

金额、阈值和审批规则是本文的教学场景;保存 Step 输入、定位暂停路径、重新进入 execute 和注入恢复数据的规则来自源码。冲印台是视觉比喻,浏览器内运行的是依据这些规则重建的确定性模型,不请求 LLM、不处理真实审批,也不直接加载完整 Mastra。交互金额只取整数;下方实际运行的代码另外验证了小数正规化。

ANATOMY · FIG. 01

暂停留下输入,恢复补上决定

票据上的金额来自保存的 payload;冲印台只补入新的 approved,然后交给 receipt。

180

阈值 amount > 100。先运行 180,暂停后把待提交金额改为 60,再选择恢复。

保存的金额 ≠ 待提交的金额准备
工作流票据冲印台左侧票据金额 180,中间冲印台接收 approved=—,右侧等待生成 receipt。前方抽屉还没有 snapshot。上方滑块当前为 180,不改变已保存的金额。AMOUNT180DRAFTSNAPSHOT · PAYLOAD—AMOUNT180BOOL ?—AMOUNT180PENDING
normalize → approval → receiptexecute 次数:0 → 0 → 0
恢复时传入 resumeData

点击一个决定,继续当前保存的运行。

提交金额后,normalize 生成 approval 的输入。超过 100 才会暂停。

① 原运行输入 · 保存在 snapshotapproval.payload
null
normalize:0 次 · 等待运行
② 外部新输入 · 只送给 approvalresumeData
null
approval:0 次 · 等待运行
③ 组合字段 · 传向下游 receiptapproval.output
null
receipt:0 次 · 尚未执行
展开 snapshot 的教学摘录

仅保留本图解释所需的步骤字段;这不是 Mastra 完整的存储记录。

null

教学模型,在浏览器本地计算,不调用 Mastra、LLM 或数据库。暂停条件检查 resumeData === undefined,所以传入 {approved: false} 后也会完成;业务结果是 rejected,Workflow 状态仍为 success。没有自动重放。

先用 180 启动流程,再把“待提交金额”改成 60,最后传入批准决定并恢复。输出仍应使用 180:恢复操作读的是已有 Run 的 Snapshot,不会自动把网页上新调整的金额替换成旧 Step 的输入。要以 60 发起另一笔请求,需要启动新的 Run。

再试一次拒绝。approved: false 是一个已经给出的决定,程序应完成并返回拒绝结果;它不等于尚未收到 resumeData。若把条件误写成 if (!resumeData?.approved),拒绝也会再次触发暂停。图中的字段来源、结果和执行计数读取同一份模型:正常恢复时 normalize 不重跑,approval 从头执行第二次,receipt 首次执行。

Resuming a suspended Step re-enters its execution function with restored input and new resume data. It does not restore a JavaScript stack frame. 因此应在暂停点写 return await suspend(...),让暂停分支结束当前执行,避免提前触发后续业务;真正不可重复的外部副作用仍须使用业务幂等键保护。不要把 await suspend() 当成操作系统冻结线程的指令。Step handler

无模型案例:实际跑一次暂停和恢复

下面是本次实际执行并断言通过的最小案例。它只运行普通 JavaScript Steps,使用内存 libSQL 保存 Snapshot;不需要模型 API Key。金额先保留两位小数,再进入暂停与恢复分支。运行环境为 Node.js 26.0.0。

在一个已有的临时工作目录中安装精确版本,将代码保存为 approval.mjs,再运行 node approval.mjs。这些是配合本文源码快照的 prerelease;安装时使用 --legacy-peer-deps,因为该 libSQL 包声明的 Core peer range 尚不接纳这份 alpha 版本,不应将它当成稳定版通用安装模板。

sh
npm install --save-exact --legacy-peer-deps --ignore-scripts @mastra/core@1.78.1-alpha.0 @mastra/libsql@1.26.3-alpha.0 zod@4.3.6
node approval.mjs
js
import assert from 'node:assert/strict';
import { createWorkflow, createStep } from '@mastra/core/workflows';
import { Mastra } from '@mastra/core/mastra';
import { LibSQLStore } from '@mastra/libsql';
import { z } from 'zod';

const inputSchema = z.object({ amount: z.number().positive() });
const approvalOutput = z.object({ amount: z.number(), decision: z.enum(['approved', 'rejected', 'automatic']) });
const outputSchema = approvalOutput.extend({ receipt: z.string() });
const executions = { normalize: 0, approval: 0, receipt: 0 };
const calls = [];

const normalize = createStep({
  id: 'normalize', inputSchema, outputSchema: inputSchema,
  execute: async ({ inputData }) => {
    executions.normalize++;
    return { amount: Math.round(inputData.amount * 100) / 100 };
  },
});

const approval = createStep({
  id: 'approval', inputSchema, outputSchema: approvalOutput,
  resumeSchema: z.object({ approved: z.boolean() }),
  suspendSchema: z.object({ amount: z.number(), reason: z.string() }),
  execute: async ({ inputData, resumeData, suspendData, suspend }) => {
    executions.approval++;
    calls.push({ inputData, resumeData, suspendData });
    if (inputData.amount > 100 && resumeData === undefined) {
      return await suspend({ amount: inputData.amount, reason: 'Amount exceeds automatic approval limit' });
    }
    return {
      amount: inputData.amount,
      decision: resumeData === undefined ? 'automatic' : resumeData.approved ? 'approved' : 'rejected',
    };
  },
});

const receipt = createStep({
  id: 'receipt', inputSchema: approvalOutput, outputSchema,
  execute: async ({ inputData }) => {
    executions.receipt++;
    return { ...inputData, receipt: `${inputData.decision}:${inputData.amount.toFixed(2)}` };
  },
});

const workflow = createWorkflow({ id: 'approval-flow', inputSchema, outputSchema })
  .then(normalize).then(approval).then(receipt).commit();
const storage = new LibSQLStore({ id: 'example-storage', url: ':memory:' });
const mastra = new Mastra({ logger: false, storage, workflows: { workflow } });
const store = await storage.getStore('workflows');
const snapshot = async runId => JSON.parse(JSON.stringify(await store.loadWorkflowSnapshot({ workflowName: workflow.id, runId })));
const run = await workflow.createRun({ runId: 'approval-example' });
const started = await run.start({ inputData: { amount: 150.125 } });
assert.equal(started.status, 'suspended');
const saved = await snapshot(run.runId);
assert.deepEqual(saved.suspendedPaths, { approval: [1] });
assert.equal(saved.context.approval.payload.amount, 150.13);
console.log('start:', started.status, saved.context.approval.payload);

const resumed = await run.resume({
  step: 'approval',
  resumeData: { approved: false },
});
assert.equal(resumed.status, 'success');
assert.deepEqual(resumed.result, {
  amount: 150.13, decision: 'rejected', receipt: 'rejected:150.13',
});
assert.deepEqual(executions, { normalize: 1, approval: 2, receipt: 1 });
console.log('resume:', resumed.status, resumed.result);
console.log('executions:', executions);
await mastra.shutdown();

预期的关键输出如下;日期和内部元数据未参与断言。

text
start: suspended { amount: 150.13 }
resume: success {
  amount: 150.13,
  decision: 'rejected',
  receipt: 'rejected:150.13'
}
executions: { normalize: 1, approval: 2, receipt: 1 }

本次另测 approved: true,结果为 approved:150.13,执行次数相同;输入 50 时直接得到 automatic:50.00,三步各执行一次。示例中的 calls 记录用于核对:首次进入 approval 时没有恢复决定,第二次进入时保留原 inputData,并收到新的 resumeData 和此前的 suspendData。

这里的 url: ':memory:' 只让示例在同一进程内可复现;跨重启恢复需要耐久数据库,并以相同定义重新取得原 Run。本次验证覆盖普通顺序流程的暂停、拒绝、批准和自动通过,没有据此声称所有执行引擎、并发恢复或外部系统副作用都具有同样保证。

Memory、RAG 与 Processor:改变模型能看到什么

Memory 的作用是为本次请求组装上下文,并在输出后保存需要保留的内容。它通过 Processor 接入 Agent:输入阶段装入历史或召回资料,输出阶段保存消息并按配置建立索引。跨请求记忆来自这些状态及检索,不是模型调用后永久修改了权重。Memory 的输入处理器、输出处理器

几种能力解决的问题不同,不能都叫成“记住所有历史”:

能力选择或生成什么主要用途
Message History最近的对话消息保持眼前这段对话连续
Working Memory按模板或 Schema 维护的结构化资料保留偏好、目标等较长期的信息
Semantic Recall与当前查询语义相关的历史消息在较长历史中找回相关内容
Observational Memory对原消息提炼的 observations,并进一步压缩在上下文预算内保留长期对话的重要信息

该提交的基础 Memory 默认配置是 lastMessages: 10,semanticRecall 和 Working Memory 默认关闭。具体 Agent 与运行时配置可以覆盖它,不能把所有可选能力写成开箱即全部启用。默认配置

Semantic Recall 的链路是:把查询转换为向量,或由支持自嵌入的 Vector store 处理查询文本;按 Thread / Resource 作用域检索;拿命中的消息 ID 回 Storage 取原消息;把消息加入上下文。模型看到的是召回内容,不是直接阅读一串向量。检索与回取

Observational Memory 使用观察和反思来压缩上下文,与向量相似度召回采用不同机制。它能减少保留原始消息所需的上下文,但提炼后的资料不是原始记录的无损替代品;需要审计时应保留相应来源。Observational Memory 实现

RAG 面向外部知识:MDocument 负责解析、分块等预处理,应用建立向量索引;查询时 createVectorQueryTool() 运行检索并可选 rerank,再返回上下文与来源。把它注册为 Tool 后,模型可以选择调用它;它不必是每轮请求的固定前置步骤。文档分块、检索 Tool

Processor 的范围比 Memory 更广:processInput 处理一次请求的输入,processInputStep 处理每轮模型上下文,processLLMRequest / processLLMResponse 位于模型协议边界,processToolResult 位于工具结果写入历史之前。实现过滤、截断、计量或缓存时,先选择正确的生命周期;修改一次模型请求并不必然永久改写消息历史。Processor 契约

边界与采用判断

Snapshot 可用,不等于耐久执行已经配置好

该提交在 new Mastra() 未传 Storage 时,自动创建 InMemoryStore。如果显式 Storage 缺少 Workflow 或后台任务领域,还会补相应内存实现。这保证了内部调度可以使用存储接口,却不能保证进程重启后数据仍在。需要恢复真实业务时,应明确配置对应持久化领域,并在新的进程中通过同一个 Workflow ID 和 runId 加载原运行。默认存储与领域补齐

Workflow 的 Step 重试、恢复和默认包装也不自动提供外部系统的 exactly-once 副作用。发邮件、扣款、创建工单等操作应使用业务幂等键、唯一约束或目标系统的幂等接口;Snapshot 只能表达引擎保存了哪些状态,不能代替跨系统事务。并发恢复的协调还依赖 Storage 是否支持相应条件更新。默认包装、并发恢复协调

Trace、评分与正确性是三件事

Observability 记录模型、Tool、Agent 和 Workflow 的执行,Exporter 把记录发送到 Storage 或外部系统。Evals 中的 MastraScorer 则组织 preprocess → analyze → generateScore → generateReason 等评价步骤,可以使用确定性函数,也可以请求模型评分。Trace 帮助解释发生了什么,Scorer 按给定标准评价结果;两者都需要结合参考数据和业务断言才能支持质量判断。Exporter、Scorer

什么时候值得引入

以下是依据模块边界作出的工程判断:当应用同时需要多步 Tools、Memory、可暂停流程和统一调试界面时,Mastra 能让这些能力共享运行模型与服务。若只需要一次模型请求和一个简单 Tool,完整引入这些抽象未必划算;应先比较已有代码真正缺少哪一层。

确定性的业务主流程适合放进 Workflow,把开放式决策限定在某个 Agent Step 内。例如程序明确规定“收集材料 → Agent 分析 → 人工批准 → 执行”,模型只负责分析和建议;它不必获得改变批准步骤的权力。反过来,把所有业务逻辑交给一个自由 Agent,并不会因为接入框架就自然获得可预测的流程。

对数据库、模型、执行环境与部署适配分别做选择。Workspace 能接文件系统和 Sandbox,但是否隔离命令、网络和文件权限取决于具体实现;MCP 解决能力发现和调用,也不替业务完成授权。部署到某个平台时,同样要验证该 Adapter 的运行时限制与恢复语义,而不是把“TypeScript 可运行”当作全部支持的证据。Workspace 配置

版本和许可要看对应范围

本文源码快照的根工作区要求 Node.js ^22.19.0 || >=24.11.0、pnpm >=11.0.0,声明 packageManager: pnpm@11.21.0;@mastra/core@1.78.1-alpha.0 的包声明则要求 Node.js >=22.13.0。前者是开发整个仓库的条件,后者是这个包的运行条件,不宜互换。各包版本独立,也不能拿仓库根版本代表整个框架。根 package.json、Core package.json

仓库主体在其声明范围内采用 Apache-2.0;任意 ee/ 目录使用另列的 Enterprise License,第三方组件保留原许可。该 Enterprise License 明确区分 source-available 与 open source,并为生产使用列出书面协议和有效 License Key 条件。采用某项功能前要核对它所在的目录,不能把主体许可扩大到全仓。许可范围、Enterprise License

建议的源码阅读顺序

先读 packages/core/src/mastra/index.ts,了解对象如何注册并共享服务;接着看 agent/agent.ts 的 generate()、stream() 和内部执行入口,再顺着 loop/ 阅读模型步骤与工具步骤。之后单独阅读 workflows/workflow.ts、default.ts 和 handlers/step.ts,把普通 Agent Loop 与持久恢复的状态边界分开。

阅读时可以反复追问三个问题:这个字段是应用配置还是本次运行状态?这个结果只是发给前端,还是已经写入 Storage?重试或恢复后,哪段业务代码会再次执行?这三个问题比记住所有包名更能帮助定位问题。

关联阅读:Pi Agent 框架架构、Eve 的 Durable Agent 与部署边界、Agent 开发中的测试方法。

博客助手

正在打开博客助手…