December 24, 2025
Fixed inline type narrowing for tool.execute() return type when using outputSchema. (#11420)
Problem: When calling tool.execute(), TypeScript couldn't narrow the ValidationError | OutputType union after checking 'error' in result && result.error, causing type errors when accessing output properties.
Solution:
{ error?: never } to the success type, enabling proper discriminated union narrowingcreateTool generics so inputData is correctly typed based on inputSchemaNote: Tool output schemas should not use error as a field name since it's reserved for ValidationError discrimination. Use errorMessage or similar instead.
Usage:
const result = await myTool.execute({ firstName: 'Hans' });
if ('error' in result && result.error) {
console.error('Validation failed:', result.message);
return;
}
// ✅ TypeScript now correctly narrows result
return { fullName: result.fullName };
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
cancel() method as an alias for cancelRun() in the Run class. The new method provides a more concise API while maintaining backward compatibility. Includes comprehensive documentation about abort signals and how steps can respond to cancellation. (#11417)Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add onError hook to server configuration for custom error handling. (#11403)
You can now provide a custom error handler through the Mastra server config to catch errors, format responses, or send them to external services like Sentry:
import { Mastra } from '@mastra/core/mastra';
const mastra = new Mastra({
server: {
onError: (err, c) => {
// Send to Sentry
Sentry.captureException(err);
// Return custom formatted response
return c.json(
{
error: err.message,
timestamp: new Date().toISOString(),
},
500,
);
},
},
});
If no onError is provided, the default error handler is used.
Fixes #9610
fix(observability): start MODEL_STEP span at beginning of LLM execution (#11409)
The MODEL_STEP span was being created when the step-start chunk arrived (after the model API call completed), causing the span's startTime to be close to its endTime instead of accurately reflecting when the step began.
This fix ensures MODEL_STEP spans capture the full duration of each LLM execution step, including the API call latency, by starting the span at the beginning of the step execution rather than when the response starts streaming.
Fixes #11271
Fixed inline type narrowing for tool.execute() return type when using outputSchema. (#11420)
Problem: When calling tool.execute(), TypeScript couldn't narrow the ValidationError | OutputType union after checking 'error' in result && result.error, causing type errors when accessing output properties.
Solution:
{ error?: never } to the success type, enabling proper discriminated union narrowingcreateTool generics so inputData is correctly typed based on inputSchemaNote: Tool output schemas should not use error as a field name since it's reserved for ValidationError discrimination. Use errorMessage or similar instead.
Usage:
const result = await myTool.execute({ firstName: 'Hans' });
if ('error' in result && result.error) {
console.error('Validation failed:', result.message);
return;
}
// ✅ TypeScript now correctly narrows result
return { fullName: result.fullName };
Add support for instructions field in MCPServer (#11421)
Implements the official MCP specification's instructions field, which allows MCP servers to provide system-wide prompts that are automatically sent to clients during initialization. This eliminates the need for per-project configuration files (like AGENTS.md) by centralizing the system prompt in the server definition.
What's New:
instructions optional field to MCPServerConfig typeInitializeResult responseExample Usage:
const server = new MCPServer({
name: 'GitHub MCP Server',
version: '1.0.0',
instructions:
'Use the available tools to help users manage GitHub repositories, issues, and pull requests. Always search before creating to avoid duplicates.',
tools: { searchIssues, createIssue, listPRs },
});
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Fix various places in core package where we were logging with console.error instead of the mastra logger. (#11425)
fix(workflows): ensure writer.custom() bubbles up from nested workflows and loops (#11422)
Previously, when using writer.custom() in steps within nested sub-workflows or loops (like dountil), the custom data events would not properly bubble up to the top-level workflow stream. This fix ensures that custom events are now correctly propagated through the nested workflow hierarchy without modification, allowing them to be consumed at the top level.
This brings workflows in line with the existing behavior for agents, where custom data chunks properly bubble up through sub-agent execution.
What changed:
nestedWatchCb function in workflow event handling to detect and preserve data-* custom eventsExample:
const subStep = createStep({
id: 'subStep',
execute: async ({ writer }) => {
await writer.custom({
type: 'custom-progress',
data: { status: 'processing' },
});
return { result: 'done' };
},
});
const subWorkflow = createWorkflow({ id: 'sub' }).then(subStep).commit();
const topWorkflow = createWorkflow({ id: 'top' }).then(subWorkflow).commit();
const run = await topWorkflow.createRun();
const stream = run.stream({ inputData: {} });
// Custom events from subStep now properly appear in the top-level stream
for await (const event of stream) {
if (event.type === 'custom-progress') {
console.log(event.data); // { status: 'processing' }
}
}
Add onError hook to server configuration for custom error handling. (#11403)
You can now provide a custom error handler through the Mastra server config to catch errors, format responses, or send them to external services like Sentry:
import { Mastra } from '@mastra/core/mastra';
const mastra = new Mastra({
server: {
onError: (err, c) => {
// Send to Sentry
Sentry.captureException(err);
// Return custom formatted response
return c.json(
{
error: err.message,
timestamp: new Date().toISOString(),
},
500,
);
},
},
});
If no onError is provided, the default error handler is used.
Fixes #9610
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add support for instructions field in MCPServer (#11421)
Implements the official MCP specification's instructions field, which allows MCP servers to provide system-wide prompts that are automatically sent to clients during initialization. This eliminates the need for per-project configuration files (like AGENTS.md) by centralizing the system prompt in the server definition.
What's New:
instructions optional field to MCPServerConfig typeInitializeResult responseExample Usage:
const server = new MCPServer({
name: 'GitHub MCP Server',
version: '1.0.0',
instructions:
'Use the available tools to help users manage GitHub repositories, issues, and pull requests. Always search before creating to avoid duplicates.',
tools: { searchIssues, createIssue, listPRs },
});
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
fix(observability): start MODEL_STEP span at beginning of LLM execution (#11409)
The MODEL_STEP span was being created when the step-start chunk arrived (after the model API call completed), causing the span's startTime to be close to its endTime instead of accurately reflecting when the step began.
This fix ensures MODEL_STEP spans capture the full duration of each LLM execution step, including the API call latency, by starting the span at the beginning of the step execution rather than when the response starts streaming.
Fixes #11271
Fix missing timezone columns during PostgreSQL spans table migration (#11419)
Fixes issue #11410 where users upgrading to observability beta.7 encountered errors about missing startedAtZ, endedAtZ, createdAtZ, and updatedAtZ columns. The migration now properly adds timezone-aware columns for all timestamp fields when upgrading existing databases, ensuring compatibility with the new observability implementation that requires these columns for batch operations.
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Fix workflow observability view broken by invalid entityType parameter (#11427)
The UI workflow observability view was failing with a Zod validation error when trying to filter traces by workflow. The UI was sending entityType=workflow, but the backend's EntityType enum only accepts workflow_run.
Root Cause: The legacy value transformation was happening in the handler (after validation), but Zod validation occurred earlier in the request pipeline, rejecting the request before it could be transformed.
Solution:
z.preprocess() to the query schema to transform workflow → workflow_run before validationEntityType.WORKFLOW_RUN enum value for type safetyThis maintains backward compatibility with legacy clients while fixing the validation error.
Fixes #11412
Add container queries, adjust the agent chat and use container queries to better display information on the agent sidebar (#11408)
Fix workflow observability view broken by invalid entityType parameter (#11427)
The UI workflow observability view was failing with a Zod validation error when trying to filter traces by workflow. The UI was sending entityType=workflow, but the backend's EntityType enum only accepts workflow_run.
Root Cause: The legacy value transformation was happening in the handler (after validation), but Zod validation occurred earlier in the request pipeline, rejecting the request before it could be transformed.
Solution:
z.preprocess() to the query schema to transform workflow → workflow_run before validationEntityType.WORKFLOW_RUN enum value for type safetyThis maintains backward compatibility with legacy clients while fixing the validation error.
Fixes #11412
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer existsStorageSupports type is no longer exported from @mastra/core/storageAll stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
Full Changelog: a82c275
Fetched April 7, 2026