@mastra/core@1.28.0
mastra-ai/mastra@mastra/core@1.28.0Apr 28, 2026by PaulieScanlon
AI Summary
This release introduces a Modal Cloud Sandbox provider, per-request filesystem resolvers for multi-tenant routing, and custom language server registration. It also fixes vector search reliability by auto-creating indices and splitting large files, and improves streaming and observational memory reliability.
Key Highlights
- Modal Cloud Sandbox Provider (@mastra/modal)
- Per-request Workspace Filesystem Resolver (Multi-tenant Routing)
- Custom Language Server Registration in LSPConfig
- Vector Search “Works Out of the Box” (Indexing + Large File Chunking)
- Streaming & Observational Memory Reliability + Better Usage Introspection
New Features
- ModalSandbox for isolated cloud execution
- Filesystem resolver function for per-request routing
- Custom LSP server registration via LSPConfig
- Auto-indexing for vector stores and large file chunking
- filterIncompleteToolCalls option for memory config
- checkSkillFileMtime option for hot reload detection
Full Release Notes
## Highlights
### Modal Cloud Sandbox Provider (`@mastra/modal`)
New `@mastra/modal` adds a Modal-backed `ModalSandbox` for running workspace commands in an isolated cloud environment with pause/resume support—expanding Mastra’s deployment/execution options beyond local sandboxes.
### Per-request Workspace Filesystem Resolver (Multi-tenant Routing)
`Workspace`’s `filesystem` option now supports a resolver function, enabling per-request filesystem selection/routing from a single Workspace instance—useful for multi-tenant setups and scoped permissions without spinning up multiple Workspaces.
### Custom Language Server Registration in `LSPConfig`
You can now register additional language servers via `lsp.servers` (and override built-ins by ID), unlocking LSP-based inspection for languages beyond the default set (e.g., PHP, Ruby, Java, Kotlin, Swift, Elixir).
### Vector Search “Works Out of the Box” (Indexing + Large File Chunking)
Workspace file indexing now auto-creates vector indices where required (e.g., LibSQL) and splits large files into overlapping chunks instead of skipping them—preventing empty vector stores and making search reliable with `autoIndexPaths`.
### Streaming & Observational Memory Reliability + Better Usage Introspection
Multiple fixes prevent duplicated/replayed messages and tool outputs when observational memory is enabled (including disabling `savePerStep` in Harness in this mode), and `agent.stream()` callbacks now preserve provider-specific `usage.raw` so you can access cache metrics without wrapping streams.
### Breaking Changes
- None noted in this changelog.
## Changelog
### [@mastra/core@1.28.0](https://github.com/mastra-ai/mastra/blob/@mastra/core@1.28.0//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/core/CHANGELOG.md)
#### Minor Changes
- The Workspace `filesystem` option now accepts a resolver function in addition to a static instance. ([#13150](https://github.com/mastra-ai/mastra/pull/13150))
**Before:** `filesystem: WorkspaceFilesystem` (static, same filesystem for every request)
**After:** `filesystem: WorkspaceFilesystem | (({ requestContext }) => WorkspaceFilesystem)` (static or per-request)
This enables per-request filesystem routing from a single Workspace — useful for multi-tenant setups, role-based access (e.g. admin vs user directories), and scoped filesystem permissions without creating separate Workspace instances.
- Added support for custom language server registration with the `servers` field in `LSPConfig`. Previously, LSP inspection only worked with built-in server definitions for TypeScript, JavaScript, Python, Go, and Rust. You can now register additional language servers, such as PHP, Ruby, Java, Kotlin, Swift, or Elixir, by providing a `CustomLSPServer` definition. ([#14969](https://github.com/mastra-ai/mastra/pull/14969))
**Example:**
```typescript
const workspace = new Workspace({
lsp: {
servers: {
phpactor: {
id: 'phpactor',
name: 'Phpactor Language Server',
languageIds: ['php'],
extensions: ['.php'],
markers: ['composer.json'],
command: 'phpactor language-server',
},
},
},
});
```
Custom servers are merged with built-in servers and can also override them by using the same ID. Closes #14828.
#### Patch Changes
- Update provider registry and model documentation with latest models and providers ([`733bf53`](https://github.com/mastra-ai/mastra/commit/733bf53d9352aedd3ef38c3d501edb275b65b43c))
- Fixed output processors returning `undefined` from `processOutputStream` causing an `undefined` chunk to be enqueued into the consumer stream. A processor that forgets to `return part` (or explicitly returns `undefined`) now drops that chunk, matching existing `null` behavior, instead of emitting a bogus value to downstream readers. ([#15674](https://github.com/mastra-ai/mastra/pull/15674))
```ts
// Before: returning undefined emitted { value: undefined, done: false } to consumers
// After: returning undefined drops the chunk, same as returning null
const processor = {
id: 'my-processor',
processOutputStream: async ({ part }) => {
if (shouldDrop(part)) return; // implicit undefined — now safely dropped
return part;
},
};
```
- Fixed streamed tool results being replayed when observational memory runs mid-stream. ([#15701](https://github.com/mastra-ai/mastra/pull/15701))
Fixed observational memory markers being saved as separate empty assistant messages.
- Fixed false positive provider change detection in observational memory. Message metadata now uses the configured model ID instead of the API response model ID, ensuring consistency with step-start parts and preventing incorrect 'Model changed' activations when the provider returns versioned model names (e.g., gpt-5.4-2026-03-05 vs gpt-5.4). ([#15681](https://github.com/mastra-ai/mastra/pull/15681))
- Fixed interaction between savePerStep and observational memory that caused message duplication. The saveStepMessages method redundantly re-added response messages to the message list on every step, duplicating them. Additionally, savePerStep is now force-disabled when observational memory is enabled, since OM handles its own per-step persistence and the two features conflict. ([#15684](https://github.com/mastra-ai/mastra/pull/15684))
- Fixed rotated response message ids not propagating to the active output stream after error processor retries, which could split a single response across two ids on the API-error retry path. ([#15702](https://github.com/mastra-ai/mastra/pull/15702))
Fixed processor-supplied options to `writer.custom` being dropped in the agentic execution step, so future options like `transient` now reach the underlying output writer.
- Fixed `agent.stream()` callbacks so that `onStepFinish` and `onFinish` now preserve the provider-level `usage.raw` object on `LanguageModelUsage`. This lets consumers inspect provider-specific cache metrics (e.g., Anthropic and Bedrock prompt caching) directly from the callback payload without having to wrap the stream. ([#15546](https://github.com/mastra-ai/mastra/pull/15546))
Closes #15510.
- Add opt-in `checkSkillFileMtime` option to detect in-place SKILL.md edits during hot reload. ([#15676](https://github.com/mastra-ai/mastra/pull/15676))
Previously, only directory mtime was checked for skill staleness, so editing a skill's name (to fix a validation error) or updating its description wouldn't trigger re-discovery until server restart.
The option is off by default since it doubles `stat()` calls per skill during staleness checks. Recommended for local development only, not for cloud storage backends where `stat()` has higher latency.
```ts
const myAgent = new Agent({
workspace: {
filesystem: new LocalFilesystem({ basePath: process.cwd() }),
skills: ['./**/skills'],
checkSkillFileMtime: true, // Enable for local dev
},
});
```
- Added `filterIncompleteToolCalls` option to memory config. When set to `false`, suspended tool calls remain visible in the agent's prompt context, allowing the agent to see its own pending interactions in thread history. Defaults to `true` (current behavior). Useful for suspend/resume patterns with providers that support incomplete tool calls (e.g. Anthropic). ([#14721](https://github.com/mastra-ai/mastra/pull/14721))
- Fixed workspace file indexing so vector search works out of the box. ([#15011](https://github.com/mastra-ai/mastra/pull/15011))
- Large files that exceeded the embedding model token limit were previously silently skipped, leaving the vector store empty and causing search failures. Large files are now split into overlapping line-based chunks, each indexed separately with correct line-range tracking back to the original file.
- The vector index is now created automatically before the first upsert. Previously, backends that require an explicit `createIndex` call (e.g. LibSQL) would leave the table uncreated, causing `no such table` errors on search. Workspaces with `vectorStore` + `embedder` + `autoIndexPaths` configured now work without any manual setup.
- Disable `savePerStep` in Harness to prevent duplicate messages when observational memory is enabled ([#15684](https://github.com/mastra-ai/mastra/pull/15684))
The `savePerStep` option in Harness caused message duplication when used alongside observational memory. This change temporarily disables `savePerStep` in the Harness runtime while we work on a permanent fix.
### [@mastra/agent-browser@0.2.1](https://github.com/mastra-ai/mastra/blob/@mastra/agent-browser@0.2.1//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/browser/agent-browser/CHANGELOG.md)
#### Patch Changes
- Standardize `headless` default to `true` across all browser providers. Each provider now resolves `headless` once in its constructor and passes it to the thread manager via the base class getter, removing duplicate fallback logic. ([#15696](https://github.com/mastra-ai/mastra/pull/15696))
- Fixed `browser_evaluate` so expression scripts now return their computed value instead of `undefined` (for example, `document.querySelectorAll('a').length`). ([#15689](https://github.com/mastra-ai/mastra/pull/15689))
### [@mastra/browser-viewer@0.1.1](https://github.com/mastra-ai/mastra/blob/@mastra/browser-viewer@0.1.1//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/browser/browser-viewer/CHANGELOG.md)
#### Patch Changes
- Remove unused `userDataDir` config option from `BrowserViewerConfig`. ([#15696](https://github.com/mastra-ai/mastra/pull/15696))
- Standardize `headless` default to `true` across all browser providers. Each provider now resolves `headless` once in its constructor and passes it to the thread manager via the base class getter, removing duplicate fallback logic. ([#15696](https://github.com/mastra-ai/mastra/pull/15696))
### [@mastra/gcs@0.2.1](https://github.com/mastra-ai/mastra/blob/@mastra/gcs@0.2.1//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/workspaces/gcs/CHANGELOG.md)
#### Patch Changes
- Fix `toKey()` to resolve `"."` and `"./"` as the root path ([#14824](https://github.com/mastra-ai/mastra/pull/14824))
Both `GCSFilesystem` and `S3Filesystem` produced invalid object keys when called with `path: "."` (e.g. `prefix/.` instead of `prefix/`). Since the built-in `mastra_workspace_list_files` tool and Mastra Studio both default to `path: "."`, workspace directory listings returned empty results when backed by GCS or S3.
`toKey()` now normalises `"."` and `"./"` to empty string before prepending the prefix, matching the existing behaviour of `"/"`. Dotfiles like `.env` or `.gitignore` are unaffected.
### [@mastra/mcp@1.5.2](https://github.com/mastra-ai/mastra/blob/@mastra/mcp@1.5.2//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/mcp/CHANGELOG.md)
#### Patch Changes
- Replace `uuid` with `@lukeed/uuid` and `node:crypto` ([#15691](https://github.com/mastra-ai/mastra/pull/15691))
### [@mastra/mcp-registry-registry@1.0.1](https://github.com/mastra-ai/mastra/blob/@mastra/mcp-registry-registry@1.0.1//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/mcp-registry-registry/CHANGELOG.md)
#### Patch Changes
- Replace `uuid` with `@lukeed/uuid` and `node:crypto` ([#15691](https://github.com/mastra-ai/mastra/pull/15691))
### [@mastra/memory@1.17.1](https://github.com/mastra-ai/mastra/blob/@mastra/memory@1.17.1//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/memory/CHANGELOG.md)
#### Patch Changes
- Fixed streamed tool results being replayed when observational memory runs mid-stream. ([#15701](https://github.com/mastra-ai/mastra/pull/15701))
Fixed observational memory markers being saved as separate empty assistant messages.
### [@mastra/modal@0.2.0](https://github.com/mastra-ai/mastra/blob/@mastra/modal@0.2.0//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/workspaces/modal/CHANGELOG.md)
#### Minor Changes
- Added `@mastra/modal` — Modal cloud sandbox provider for Mastra workspaces. ([#14486](https://github.com/mastra-ai/mastra/pull/14486))
Use `ModalSandbox` to run commands in an isolated Modal environment with pause/resume support:
```ts
import { Workspace } from '@mastra/core/workspace';
import { ModalSandbox } from '@mastra/modal';
const workspace = new Workspace({
sandbox: new ModalSandbox({
tokenId: process.env.MODAL_TOKEN_ID!,
tokenSecret: process.env.MODAL_TOKEN_SECRET!,
}),
});
```
#### Patch Changes
### [@mastra/mongodb@1.7.3](https://github.com/mastra-ai/mastra/blob/@mastra/mongodb@1.7.3//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/stores/mongodb/CHANGELOG.md)
#### Patch Changes
- Replace `uuid` with `@lukeed/uuid` and `node:crypto` ([#15691](https://github.com/mastra-ai/mastra/pull/15691))
### [@mastra/s3@0.4.1](https://github.com/mastra-ai/mastra/blob/@mastra/s3@0.4.1//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/workspaces/s3/CHANGELOG.md)
#### Patch Changes
- Fix `toKey()` to resolve `"."` and `"./"` as the root path ([#14824](https://github.com/mastra-ai/mastra/pull/14824))
Both `GCSFilesystem` and `S3Filesystem` produced invalid object keys when called with `path: "."` (e.g. `prefix/.` instead of `prefix/`). Since the built-in `mastra_workspace_list_files` tool and Mastra Studio both default to `path: "."`, workspace directory listings returned empty results when backed by GCS or S3.
`toKey()` now normalises `"."` and `"./"` to empty string before prepending the prefix, matching the existing behaviour of `"/"`. Dotfiles like `.env` or `.gitignore` are unaffected.
### [@mastra/s3vectors@1.0.4](https://github.com/mastra-ai/mastra/blob/@mastra/s3vectors@1.0.4//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/stores/s3vectors/CHANGELOG.md)
#### Patch Changes
- Replace `uuid` with `@lukeed/uuid` and `node:crypto` ([#15691](https://github.com/mastra-ai/mastra/pull/15691))
### [@mastra/server@1.28.0](https://github.com/mastra-ai/mastra/blob/@mastra/server@1.28.0//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/server/CHANGELOG.md)
#### Patch Changes
- Fixed non-Zod Standard Schema types (e.g. ArkType) being incorrectly called as lazy getters in resolveLazySchema, which caused Studio UI tool forms to receive validation errors instead of the actual JSON Schema ([#15670](https://github.com/mastra-ai/mastra/pull/15670))
- Fix: Public origin resolution for AWS ALB deployments ([#15666](https://github.com/mastra-ai/mastra/pull/15666))
Implement cascading header resolution in getPublicOrigin() to properly handle:
- X-Forwarded-Host (traditional reverse proxies) → always HTTPS
- Host header (AWS ALB with Preserve Host Header) → respect X-Forwarded-Proto or default HTTPS
- request.url (local development) → fallback
Fixes OAuth callback URLs being resolved to http:// instead of https:// when deployed behind AWS ALB with Preserve Host Header enabled.
### [@mastra/stagehand@0.2.1](https://github.com/mastra-ai/mastra/blob/@mastra/stagehand@0.2.1//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/browser/stagehand/CHANGELOG.md)
#### Patch Changes
- Standardize `headless` default to `true` across all browser providers. Each provider now resolves `headless` once in its constructor and passes it to the thread manager via the base class getter, removing duplicate fallback logic. ([#15696](https://github.com/mastra-ai/mastra/pull/15696))
### Other updated packages
The following packages were updated with dependency changes only:
- [@mastra/agent-builder@1.0.29](https://github.com/mastra-ai/mastra/blob/@mastra/agent-builder@1.0.29//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/agent-builder/CHANGELOG.md)
- [@mastra/client-js@1.14.2](https://github.com/mastra-ai/mastra/blob/@mastra/client-js@1.14.2//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/client-sdks/client-js/CHANGELOG.md)
- [@mastra/deployer@1.28.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer@1.28.0//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/deployer/CHANGELOG.md)
- [@mastra/deployer-cloud@1.28.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-cloud@1.28.0//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/deployers/cloud/CHANGELOG.md)
- [@mastra/deployer-cloudflare@1.1.26](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-cloudflare@1.1.26//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/deployers/cloudflare/CHANGELOG.md)
- [@mastra/deployer-netlify@1.1.2](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-netlify@1.1.2//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/deployers/netlify/CHANGELOG.md)
- [@mastra/deployer-vercel@1.1.20](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-vercel@1.1.20//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/deployers/vercel/CHANGELOG.md)
- [@mastra/editor@0.7.19](https://github.com/mastra-ai/mastra/blob/@mastra/editor@0.7.19//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/editor/CHANGELOG.md)
- [@mastra/express@1.3.12](https://github.com/mastra-ai/mastra/blob/@mastra/express@1.3.12//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/server-adapters/express/CHANGELOG.md)
- [@mastra/fastify@1.3.12](https://github.com/mastra-ai/mastra/blob/@mastra/fastify@1.3.12//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/server-adapters/fastify/CHANGELOG.md)
- [@mastra/hono@1.4.7](https://github.com/mastra-ai/mastra/blob/@mastra/hono@1.4.7//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/server-adapters/hono/CHANGELOG.md)
- [@mastra/koa@1.4.12](https://github.com/mastra-ai/mastra/blob/@mastra/koa@1.4.12//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/server-adapters/koa/CHANGELOG.md)
- [@mastra/longmemeval@1.0.31](https://github.com/mastra-ai/mastra/blob/@mastra/longmemeval@1.0.31//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/explorations/longmemeval/CHANGELOG.md)
- [@mastra/mcp-docs-server@1.1.28](https://github.com/mastra-ai/mastra/blob/@mastra/mcp-docs-server@1.1.28//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/mcp-docs-server/CHANGELOG.md)
- [@mastra/opencode@0.0.28](https://github.com/mastra-ai/mastra/blob/@mastra/opencode@0.0.28//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/integrations/opencode/CHANGELOG.md)
- [@mastra/playground-ui@23.0.2](https://github.com/mastra-ai/mastra/blob/@mastra/playground-ui@23.0.2//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/packages/playground-ui/CHANGELOG.md)
- [@mastra/react@0.2.29](https://github.com/mastra-ai/mastra/blob/@mastra/react@0.2.29//private/var/folders/d4/mn8gvlx91cz80s_9c4gjr12r0000gn/T/mastra-mastra-ai-mastra-_mastra_core_1.28.0/client-sdks/react/CHANGELOG.md)