February 13, 2026
Mastra now has first-class evaluation primitives: versioned Datasets (with JSON Schema validation and SCD-2 item versioning) and Experiments that run agents against datasets with configurable scorers and result tracking. This ships end-to-end across @mastra/core APIs, new /datasets REST endpoints in @mastra/server, and a full Studio UI for managing datasets, triggering experiments, and comparing results.
Workspace lifecycle interfaces were split into FilesystemLifecycle and SandboxLifecycle, and MastraFilesystem now supports onInit/onDestroy callbacks. Filesystem path resolution and metadata were improved (generic FilesystemInfo<TMetadata>, provider-specific metadata, safer instructions for uncontained filesystems) and filesystem info is now exposed via the workspaces API response.
Workflows now emit a workflow-step-progress stream event for foreach steps (completed/total, current index, per-iteration status/output), supported by both execution engines. Studio renders real-time progress bars, and @mastra/react watch hooks now accumulate foreachProgress into step state.
@mastra/memory: observe() now takes a single object parameter (e.g., observe({ threadId, resourceId })) instead of positional arguments.Added Datasets and Experiments to core. Datasets let you store and version collections of test inputs with JSON Schema validation. Experiments let you run AI outputs against dataset items with configurable scorers to track quality over time. (#12747)
New exports from @mastra/core/datasets:
DatasetsManager — orchestrates dataset CRUD, item versioning (SCD-2), and experiment executionDataset — single-dataset handle for adding items and running experimentsNew storage domains:
DatasetsStorage — abstract base class for dataset persistence (datasets, items, versions)ExperimentsStorage — abstract base class for experiment lifecycle and result trackingExample:
import { Mastra } from "@mastra/core";
const mastra = new Mastra({
/* ... */
});
const dataset = await mastra.datasets.create({ name: "my-eval-set" });
await dataset.addItems([{ input: { query: "What is 2+2?" }, groundTruth: { answer: "4" } }]);
const result = await dataset.runExperiment({
targetType: "agent",
targetId: "my-agent",
scorerIds: ["accuracy"]
});
Fix LocalFilesystem.resolvePath handling of absolute paths and improve filesystem info. (#12971)
FilesystemInfo generic (FilesystemInfo<TMetadata>) so providers can type their metadata.basePath, contained) to metadata in LocalFilesystem.getInfo().Add workflow-step-progress stream event for foreach workflow steps. Each iteration emits a progress event with completedCount, totalCount, currentIndex, iterationStatus (success | failed | suspended), and optional iterationOutput. Both the default and evented execution engines emit these events. (#12838)
The Mastra Studio UI now renders a progress bar with an N/total counter on foreach nodes, updating in real time as iterations complete:
// Consuming progress events from the workflow stream
const run = workflow.createRun();
const result = await run.start({ inputData });
const stream = result.stream;
for await (const chunk of stream) {
if (chunk.type === "workflow-step-progress") {
console.log(`${chunk.payload.completedCount}/${chunk.payload.totalCount} - ${chunk.payload.iterationStatus}`);
}
}
MCP Client Storage
New storage domain for persisting MCP client configurations with CRUD operations. Each MCP client can contain multiple servers with independent tool selection:
// Store an MCP client with multiple servers
await storage.mcpClients.create({
id: "my-mcp",
name: "My MCP Client",
servers: {
"github-server": { url: "https://mcp.github.com/sse" },
"slack-server": { url: "https://mcp.slack.com/sse" }
}
});
LibSQL, PostgreSQL, and MongoDB storage adapters all implement the new MCP client domain.
ToolProvider Interface
New ToolProvider interface at @mastra/core/tool-provider enables third-party tool catalog integration (e.g., Composio, Arcade AI):
import type { ToolProvider } from '@mastra/core/tool-provider';
# Providers implement: listToolkits(), listTools(), getToolSchema(), resolveTools()
resolveTools() receives requestContext from the current request, enabling per-user API keys and credentials in multi-tenant setups:
const tools = await provider.resolveTools(slugs, configs, {
requestContext: { apiKey: "user-specific-key", userId: "tenant-123" }
});
Tool Selection Semantics
Both mcpClients and integrationTools on stored agents follow consistent three-state selection:
{ tools: undefined } — provider registered, no tools selected{ tools: {} } — all tools from provider included{ tools: { 'TOOL_SLUG': { description: '...' } } } — specific tools with optional overridesAdded (#12764)
Added a suppressFeedback option to hide internal completion‑check messages from the stream. This keeps the conversation history clean while leaving existing behavior unchanged by default.
Example Before:
const agent = await mastra.createAgent({
completion: { validate: true }
});
After:
const agent = await mastra.createAgent({
completion: { validate: true, suppressFeedback: true }
});
Split workspace lifecycle interfaces (#12978)
The shared Lifecycle interface has been split into provider-specific types that match actual usage:
FilesystemLifecycle — two-phase: init() → destroy()SandboxLifecycle — three-phase: start() → stop() → destroy()The base Lifecycle type is still exported for backward compatibility.
Added onInit / onDestroy callbacks to MastraFilesystem
The MastraFilesystem base class now accepts optional lifecycle callbacks via MastraFilesystemOptions, matching the existing onStart / onStop / onDestroy callbacks on MastraSandbox.
const fs = new LocalFilesystem({
basePath: "./data",
onInit: ({ filesystem }) => {
console.log("Filesystem ready:", filesystem.status);
},
onDestroy: ({ filesystem }) => {
console.log("Cleaning up...");
}
});
onInit fires after the filesystem reaches ready status (non-fatal on failure). onDestroy fires before the filesystem is torn down.
Update provider registry and model documentation with latest models and providers (7ef618f)
Fixed Anthropic API rejection errors caused by empty text content blocks in assistant messages. During streaming with web search citations, empty text parts could be persisted to the database and then rejected by Anthropic's API with 'text content blocks must be non-empty' errors. The fix filters out these empty text blocks before persistence, ensuring stored conversation history remains valid for Anthropic models. Fixes #12553. (#12711)
Improve error messages when processor workflows or model fallback retries fail. (#12970)
Fixed tool-not-found errors crashing the agentic loop. When a model hallucinates a tool name (e.g., Gemini 3 Flash adding prefixes like creating:view instead of view), the error is now returned to the model as a tool result instead of throwing. This allows the model to self-correct and retry with the correct tool name on the next turn. The error message includes available tool names to help the model recover. Fixes #12895. (#12961)
Fixed structured output failing with Anthropic models when memory is enabled. The error "assistant message in the final position" occurred because the prompt sent to Anthropic ended with an assistant-role message, which is not supported when using output format. Resolves https://github.com/mastra-ai/mastra/issues/12800 (#12835)
commander@^14.0.3 ↗︎ (from ^14.0.2, in dependencies)7ef618f, b373564, 927c2af, 927c2af, 5fbb1a8, b896b41, 6415277, 0831bbb, 6297864, 63f7eda, a5b67a3, 877b02c, 877b02c, d87e96b, 7567222, af71458, eb36bd8, 3cbf121, 6415277]:
7ef618f, b373564, 927c2af, b896b41, 6415277, 0831bbb, 63f7eda, a5b67a3, 877b02c, 7567222, af71458, eb36bd8, 3cbf121]:
@mastra/opencode: Add opencode plugin for Observational Memory integration (#12925)
Added standalone observe() API that accepts external messages directly, so integrations can trigger observation without duplicating messages into Mastra's storage.
New exports:
ObserveHooks — lifecycle callbacks (onObservationStart, onObservationEnd, onReflectionStart, onReflectionEnd) for hooking into observation/reflection cyclesOBSERVATION_CONTEXT_PROMPT — preamble that introduces the observations blockOBSERVATION_CONTEXT_INSTRUCTIONS — rules for interpreting observations (placed after the <observations> block)OBSERVATION_CONTINUATION_HINT — behavioral guidance that prevents models from awkwardly acknowledging the memory systemgetOrCreateRecord() — now public, allows eager record initialization before the first observation cycleimport { ObservationalMemory } from "@mastra/memory/processors";
const om = new ObservationalMemory({ storage, model: "google/gemini-2.5-flash" });
// Eagerly initialize a record
await om.getOrCreateRecord(threadId);
// Pass messages directly with lifecycle hooks
await om.observe({
threadId,
messages: myMessages,
hooks: {
onObservationStart: () => console.log("Observing..."),
onObservationEnd: () => console.log("Done!"),
onReflectionStart: () => console.log("Reflecting..."),
onReflectionEnd: () => console.log("Reflected!")
}
});
Breaking: observe() now takes an object param instead of positional args. Update calls from observe(threadId, resourceId) to observe({ threadId, resourceId }).
Fixed observational memory writing non-integer token counts to PostgreSQL, which caused invalid input syntax for type integer errors. Token counts are now correctly rounded to integers before all database writes. (#12976)
Fixed cloneThread not copying working memory to the cloned thread. Thread-scoped working memory is now properly carried over when cloning, and resource-scoped working memory is copied when the clone uses a different resourceId. (#12833)
Updated dependencies [7ef618f, b373564, 927c2af, b896b41, 6415277, 0831bbb, 63f7eda, a5b67a3, 877b02c, 7567222, af71458, eb36bd8, 3cbf121]:
Added Datasets and Experiments UI. Includes dataset management (create, edit, delete, duplicate), item CRUD with CSV/JSON import and export, SCD-2 version browsing and comparison, experiment triggering with scorer selection, experiment results with trace visualization, and cross-experiment comparison with score deltas. Uses coreFeatures runtime flag for feature gating instead of build-time env var. (#12747)
Add workflow-step-progress stream event for foreach workflow steps. Each iteration emits a progress event with completedCount, totalCount, currentIndex, iterationStatus (success | failed | suspended), and optional iterationOutput. Both the default and evented execution engines emit these events. (#12838)
The Mastra Studio UI now renders a progress bar with an N/total counter on foreach nodes, updating in real time as iterations complete:
// Consuming progress events from the workflow stream
const run = workflow.createRun();
const result = await run.start({ inputData });
const stream = result.stream;
for await (const chunk of stream) {
if (chunk.type === "workflow-step-progress") {
console.log(`${chunk.payload.completedCount}/${chunk.payload.totalCount} - ${chunk.payload.iterationStatus}`);
}
}
Revamped agent CMS experience with dedicated route pages for each section (Identity, Instruction Blocks, Tools, Agents, Scorers, Workflows, Memory, Variables) and sidebar navigation (#13016)
Added ability to create sub-agents on-the-fly via a SideDialog in the Sub-Agents section of the agent editor (#12952)
dependencies updates: (#12949)
@codemirror/view@^6.39.13 ↗︎ (from ^6.39.12, in dependencies)Removed experiment mode from the agent prompt sidebar. The system prompt is now displayed as readonly. (#12994)
Aligned frontend rule engine types with backend, added support for greater_than_or_equal, less_than_or_equal, exists, and not_exists operators, and switched instruction blocks to use RuleGroup (#12864)
Skip awaitBufferStatus calls when observational memory is disabled. Previously the Studio sidebar would unconditionally hit /memory/observational-memory/buffer-status after every agent message, which returns a 400 when OM is not configured and halts agent execution. (#13025)
Fix prompt experiment localStorage persisting stale prompts: only save to localStorage when the user edits the prompt away from the code-defined value, and clear it when they match. Previously, the code-defined prompt was eagerly saved on first load, causing code changes to agent instructions to be ignored. (#12929)
Fixed chat briefly showing an empty conversation after sending the first message on a new thread. (#13018)
Fixed missing validation for the instructions field in the scorer creation form and replaced manual submission state tracking with the mutation hook's built-in pending state (#12993)
Default Studio file browser to basePath when filesystem containment is disabled, preventing the browser from showing the host root directory. (#12971)
Updated dependencies [7ef618f, b373564, 927c2af, 927c2af, 3da8a73, 927c2af, b896b41, 6415277, 4ba40dc, 0831bbb, 63f7eda, a5b67a3, 877b02c, 877b02c, 7567222, 40f224e, af71458, eb36bd8, 3cbf121]:
/datasets for full CRUD on datasets, items, versions, experiments, and experiment results. Includes batch operations and experiment comparison. (#12747)Fixed the /api/tools endpoint returning an empty list even when tools are registered on the Mastra instance. Closes #12983 (#13008)
Fixed custom API routes registered via registerApiRoute() being silently ignored by Koa, Express, Fastify, and Hono server adapters. Routes previously appeared in the OpenAPI spec but returned 404 at runtime. Custom routes now work correctly across all server adapters. (#12960)
Example:
import Koa from "koa";
import { Mastra } from "@mastra/core";
import { registerApiRoute } from "@mastra/core/server";
import { MastraServer } from "@mastra/koa";
const mastra = new Mastra({
server: {
apiRoutes: [
registerApiRoute("/hello", {
method: "GET",
handler: async (c) => c.json({ message: "Hello!" })
})
]
}
});
const app = new Koa();
const server = new MastraServer({ app, mastra });
await server.init();
// GET /hello now returns 200 instead of 404
Added API routes for stored MCP clients and tool provider discovery. (#12974)
Stored MCP Client Routes
New REST endpoints for managing stored MCP client configurations:
GET /api/stored-mcp-clients — List all stored MCP clientsGET /api/stored-mcp-clients/:id — Get a specific MCP clientPOST /api/stored-mcp-clients — Create a new MCP clientPATCH /api/stored-mcp-clients/:id — Update an existing MCP clientDELETE /api/stored-mcp-clients/:id — Delete an MCP client// Create a stored MCP client
const response = await fetch("/api/stored-mcp-clients", {
method: "POST",
body: JSON.stringify({
id: "my-mcp-client",
name: "My MCP Client",
servers: {
"github-server": { url: "https://mcp.github.com/sse" }
}
})
});
Tool Provider Routes
New REST endpoints for browsing registered tool providers and their tools:
GET /api/tool-providers — List all registered tool providers with metadataGET /api/tool-providers/:providerId/toolkits — List toolkits for a providerGET /api/tool-providers/:providerId/tools — List tools (with optional toolkit/search filtering)GET /api/tool-providers/:providerId/tools/:toolSlug/schema — Get input schema for a tool// List all registered tool providers
const providers = await fetch("/api/tool-providers");
// Browse tools in a specific toolkit
const tools = await fetch("/api/tool-providers/composio/tools?toolkit=github");
// Get schema for a specific tool
const schema = await fetch("/api/tool-providers/composio/tools/GITHUB_LIST_ISSUES/schema");
Updated stored agent schemas to include mcpClients and integrationTools conditional fields, and updated agent version tracking accordingly.
Fixed requestContextSchema missing from the agent list API response. Agents with a requestContextSchema now correctly include it when listed via GET /agents. (#12954)
Expose filesystem info from getInfo() in the GET /api/workspaces/:id API response, including provider type, status, readOnly, and provider-specific metadata. (#12971)
Updated dependencies [7ef618f, b373564, 927c2af, b896b41, 6415277, 0831bbb, 63f7eda, a5b67a3, 877b02c, 7567222, af71458, eb36bd8, 3cbf121]:
Fetched April 7, 2026