Highlights
Datasets & Experiments (core + server + Studio UI)
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 & Filesystem Lifecycle + Safer Filesystem Introspection
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.
Workflow foreach Progress Streaming
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.
Breaking Changes
@mastra/memory:observe()now takes a single object parameter (e.g.,observe({ threadId, resourceId })) instead of positional arguments.
Changelog
@mastra/core@1.4.0
Minor Changes
-
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 experiments
New storage domains:
DatasetsStorage— abstract base class for dataset persistence (datasets, items, versions)ExperimentsStorage— abstract base class for experiment lifecycle and result tracking
Example:
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)
- Fix absolute path resolution: paths were incorrectly stripped of leading slashes and resolved relative to basePath, causing PermissionError for valid paths (e.g. skills processor accessing project-local skills directories).
- Make
FilesystemInfogeneric (FilesystemInfo<TMetadata>) so providers can type their metadata. - Move provider-specific fields (
basePath,contained) to metadata in LocalFilesystem.getInfo(). - Update LocalFilesystem.getInstructions() for uncontained filesystems to warn agents against listing /.
- Use FilesystemInfo type in WorkspaceInfo instead of duplicated inline shape.
-
Add
workflow-step-progressstream event for foreach workflow steps. Each iteration emits a progress event withcompletedCount,totalCount,currentIndex,iterationStatus(success|failed|suspended), and optionaliterationOutput. 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
ToolProviderinterface at@mastra/core/tool-providerenables 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()receivesrequestContextfrom 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
mcpClientsandintegrationToolson 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 overrides
-
Added (#12764)
Added asuppressFeedbackoption 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
Lifecycleinterface has been split into provider-specific types that match actual usage:FilesystemLifecycle— two-phase:init()→destroy()SandboxLifecycle— three-phase:start()→stop()→destroy()
The base
Lifecycletype is still exported for backward compatibility.Added
onInit/onDestroycallbacks toMastraFilesystemThe
MastraFilesystembase class now accepts optional lifecycle callbacks viaMastraFilesystemOptions, matching the existingonStart/onStop/onDestroycallbacks onMastraSandbox.const fs = new LocalFilesystem({ basePath: "./data", onInit: ({ filesystem }) => { console.log("Filesystem ready:", filesystem.status); }, onDestroy: ({ filesystem }) => { console.log("Cleaning up..."); } });
onInitfires after the filesystem reachesreadystatus (non-fatal on failure).onDestroyfires before the filesystem is torn down.
Patch Changes
-
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)
- Include the last error message and cause when all fallback models are exhausted, instead of the generic "Exhausted all fallback models" message.
- Extract error details from failed workflow results and individual step failures when a processor workflow fails, instead of just reporting "failed with status: failed".
-
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:viewinstead ofview), 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 #12800 (#12835)
@mastra/codemod@1.0.2
Patch Changes
- dependencies updates: (#12989)
- Updated dependency
commander@^14.0.3↗︎ (from^14.0.2, independencies)
- Updated dependency
@mastra/deployer@1.4.0
Patch Changes
- Updated dependencies [
7ef618f,b373564,927c2af,927c2af,5fbb1a8,b896b41,6415277,0831bbb,6297864,63f7eda,a5b67a3,877b02c,877b02c,d87e96b,7567222,af71458,eb36bd8,3cbf121,6415277]:- @mastra/core@1.4.0
- @mastra/server@1.4.0
@mastra/mcp-docs-server@1.1.2
Patch Changes
- Updated dependencies [
7ef618f,b373564,927c2af,b896b41,6415277,0831bbb,63f7eda,a5b67a3,877b02c,7567222,af71458,eb36bd8,3cbf121]:- @mastra/core@1.4.0
@mastra/memory@1.3.0
Minor Changes
-
@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 cycle
import { 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 fromobserve(threadId, resourceId)toobserve({ threadId, resourceId }).
Patch Changes
-
Fixed observational memory writing non-integer token counts to PostgreSQL, which caused
invalid input syntax for type integererrors. 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]:- @mastra/core@1.4.0
@mastra/playground-ui@11.0.0
Minor Changes
-
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
coreFeaturesruntime flag for feature gating instead of build-time env var. (#12747) -
Add
workflow-step-progressstream event for foreach workflow steps. Each iteration emits a progress event withcompletedCount,totalCount,currentIndex,iterationStatus(success|failed|suspended), and optionaliterationOutput. 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)
Patch Changes
-
dependencies updates: (#12949)
- Updated dependency
@codemirror/view@^6.39.13↗︎ (from^6.39.12, independencies)
- Updated dependency
-
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
awaitBufferStatuscalls when observational memory is disabled. Previously the Studio sidebar would unconditionally hit/memory/observational-memory/buffer-statusafter 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]:- @mastra/core@1.4.0
- @mastra/client-js@1.4.0
- @mastra/react@0.2.3
- @mastra/ai-sdk@1.0.4
@mastra/server@1.4.0
Minor Changes
- Added REST API routes for Datasets and Experiments. New endpoints under
/datasetsfor full CRUD on datasets, items, versions, experiments, and experiment results. Includes batch operations and experiment comparison. (#12747)
Patch Changes
-
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
mcpClientsandintegrationToolsconditional 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]:- @mastra/core@1.4.0