@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)