releases.shpreview
Home/Mastra
Mastra

Mastra

Thread-scoped notifications; processor state signals for thread context

This release5 featuresNew capabilitiesAI-tallied from the release notes
@mastra/core@1.39.0

Highlights

Thread-Scoped Notification Signals + Persisted Inbox (Experimental)

New record-first notification signals add a thread-scoped inbox with agent.sendNotificationSignal(), priority-aware delivery (including due-notification dispatch and low-priority rollups), a structured metadata.notification contract, and a notification_inbox tool to list/read/dismiss/archive notifications.

Notification Inbox Storage Support Across Backends

Notification inbox persistence is now supported in multiple stores—Postgres (@mastra/pg), MongoDB (@mastra/mongodb), and LibSQL (@mastra/libsql)—so notification signals can reliably persist inbox records regardless of your storage choice.

Processor State Signals for Memory-Backed Thread Context

Processors can publish replayable, deduped state “lanes” via computeStateSignal() and external producers can update them with agent.sendStateSignal(), enabling efficient snapshot/delta state history and making browser context flow through the same thread state mechanism.

Working Memory Delivered as State Signals (Experimental Opt-in)

A new workingMemory.useStateSignals option sends working memory as state signals (instead of system-message injection), with automatic WorkingMemoryStateProcessor attachment, cache-key dedupe, and unified-diff deltas when smaller than snapshots (markdown mode).

Type-Safe Message Rendering with @mastra/react MessageFactory + Canonical Stream Types

@mastra/react adds a fully type-safe MessageFactory for per-part rendering (including dynamic tool parts) plus strongly-typed status slots (Tripwire/Warning/Error/Task), while @mastra/core/stream now exports canonical IsTaskCompletePayload and TripwirePayload types for consistent UI typing.

Breaking Changes
  • None noted in this changelog.

Changelog

@mastra/core@1.39.0
Minor Changes
  • Add a type-safe MessageFactory component to @mastra/react for rendering a MastraDBMessage with your own per-part components, and align its tripwire/task-verdict types with @mastra/core. (#17514)

    @mastra/react

    MessageFactory provides optional, fully type-safe render functions for each kind of message part. Only the renderer matching a part's type runs, and each receives correctly narrowed props; missing renderers fall back gracefully. Runtime-only dynamic-tool and AI SDK v5 tool-${string} parts are covered by a dedicated DynamicTool renderer, and optional role wrappers let you frame parts per message role.

    import { MessageFactory } from '@mastra/react';
    
    <MessageFactory
      message={message}
      Text={part => <p>{part.text}</p>}
      ToolInvocation={part => <ToolCard name={part.toolInvocation.toolName} />}
      DynamicTool={part => <ToolCard name={part.toolName} state={part.state} />}
      Data={part => <DataView type={part.type} data={part.data} />}
      roles={{ Signal: ({ children }) => <SignalFrame>{children}</SignalFrame> }}
    />;

    It also accepts an optional status prop with four strongly-typed slots that render from a message's metadata while keeping part renderers pure. Tripwire, Warning, and Error are replacement slots (rendered instead of the parts when metadata.status matches); Task is an adjacent slot (rendered alongside the parts when a task-completion verdict exists). The factory only surfaces metadata to the slots and never filters it (for example, it still invokes Task when suppressFeedback is true) — the consumer decides what to render or skip. Existing behavior is unchanged when status is omitted.

    <MessageFactory
      message={message}
      status={{
        Error: ({ text }) => <ErrorNotice>{text}</ErrorNotice>,
        Task: ({ passed, suppressFeedback }) => (suppressFeedback ? null : <TaskVerdict passed={passed} />),
      }}
      {...renderers}
    />

    The narrowed part types used by the renderers are exported so consumers can type their own components: TextPart, ReasoningPart, FilePart, StepStartPart, ToolInvocationPart, SourceDocumentPart, and SourceUrlPart, plus MessageFactoryPart (the exact union of part shapes MessageFactory can dispatch — the typed accumulator parts plus the runtime-only dynamic-tool / tool-${string} parts) for typing part arrays precisely instead of unknown[].

    MastraDBMessageMetadata.isTaskCompleteResult is now typed as the { passed?, suppressFeedback? } completion-verdict shape (matching completionResult) instead of boolean, so the Task slot resolves verdicts from either field without a cast.

    TripwireMetadata is now an alias of core's TripwirePayload, and the message accumulator persists the canonical shape. Two behavioral changes to persisted metadata.tripwire:

    • The tripwire reason is now persisted as tripwire.reason (previously it was only stored in the message text part).
    • The processor metadata field was renamed from tripwire.tripwirePayload to tripwire.metadata to match the canonical type.

    The MessageFactory Tripwire slot receives reason through props.tripwire.

    @mastra/core

    Exported the canonical IsTaskCompletePayload and TripwirePayload types from @mastra/core/stream so consumers can type their own task/completion and tripwire UI against them instead of redeclaring the shapes.

    import type { IsTaskCompletePayload, TripwirePayload } from '@mastra/core/stream';
  • Add experimental record-first notification signals with thread-scoped inbox storage, agent.sendNotificationSignal(), priority-aware notification delivery policies, due-notification dispatch, summary rollups for low-priority notifications, a structured metadata.notification signal contract, and a flexible notification inbox tool. (#17241)

    await agent.sendNotificationSignal(
      {
        source: 'github',
        kind: 'ci-status',
        priority: 'high',
        summary: 'CI failed on main',
      },
      { resourceId: 'user-1', threadId: 'thread-1' },
    );

    Agents can then use the notification_inbox tool to list, read, dismiss, or archive persisted inbox records.

  • Add processor state signals for memory-backed thread context. (#17240)

    Processors can now publish named state lanes with computeStateSignal(), and external producers can update the same lanes with agent.sendStateSignal(). The runtime tracks each lane by id, cacheKey, and mode, so unchanged state is deduped and snapshot/delta history can be replayed efficiently.

    Browser context now uses this state-signal path, so browser state is represented as thread state instead of being injected as ad hoc context.

    // External producer: push browser state outside a model run.
    await agent.sendStateSignal(
      {
        id: 'browser',
        cacheKey: 'tab-42:https://app.example.com/dashboard',
        mode: 'snapshot',
        contents: 'Browser is open on https://app.example.com/dashboard',
        value: { url: 'https://app.example.com/dashboard', title: 'Dashboard' },
      },
      { resourceId, threadId, ifIdle: { behavior: 'persist' } },
    );
    
    // Processor-owned state: compute state before the model request.
    const browserProcessor = {
      id: 'browser-state',
      stateId: 'browser',
      async computeStateSignal({ lastSnapshot }) {
        const nextBrowserState = await getBrowserState();
    
        return {
          cacheKey: nextBrowserState.url,
          mode: 'snapshot',
          contents: `Browser is open on ${nextBrowserState.url}`,
          value: nextBrowserState,
        };
      },
    };
Patch Changes
  • Fixed CostGuardProcessor thread and resource scope resolution when running without auth middleware (e.g. Studio dev mode). The processor now falls back to the MastraMemory context on RequestContext to resolve threadId and resourceId, matching the pattern used by other processor helpers. (#17522)

  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)

  • Improved notification delivery targeting and inbox search so notification records can be found and delivered to the intended thread. (#17447)

  • Fixed subscribed client tools so browser-executed tool results continue through the existing thread subscription instead of opening and canceling a second stream. This prevents closed-stream errors in apps like Agent Builder when multiple client tools run during one response. (#17532)

  • Added experimental workingMemory.useStateSignals opt-in. When set to true, working memory is delivered to the model as a state signal (via the new state-signals API) instead of being folded into the system message. Memory auto-attaches a WorkingMemoryStateProcessor that emits a signal with stateId: 'working-memory' and dedups via cacheKey. Subsequent turns emit unified-diff deltas against the prior snapshot when the diff is smaller than the snapshot (markdown mode only); schema mode and the fallback path always emit a full snapshot. The working-memory tool is registered as setWorkingMemory instead of updateWorkingMemory under this opt-in so legacy persistence/prompt strip filters naturally bypass it. The default (false) preserves the existing system-message behavior. useStateSignals is not supported with template working memory version: 'vnext'. (#17497)

    import { Memory } from '@mastra/memory';
    
    const memory = new Memory({
      options: {
        workingMemory: {
          enabled: true,
          useStateSignals: true,
        },
      },
    });
@mastra/auth-better-auth@1.0.3
Patch Changes
  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)
@mastra/auth-cloud@1.1.3
Patch Changes
  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)
@mastra/auth-okta@0.0.4
Patch Changes
  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)
@mastra/auth-studio@1.2.3
Patch Changes
  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)
@mastra/auth-workos@1.5.1
Patch Changes
  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)
@mastra/client-js@1.23.0
Minor Changes
  • Added GET /stored/agents/:storedAgentId/dependents endpoint that lists agents (#17183) referencing a stored agent as a sub-agent.

    const { dependents, hiddenCount } = await client.getStoredAgent(id).dependents();
    // { dependents: [{ id: 'parent-1', name: 'Triager' }], hiddenCount: 2 }
    • dependents — caller-readable agents (public agents and the caller's own private agents) with id + name.
    • hiddenCount — cross-workspace dependents the caller cannot read, only surfaced when the target agent is public.

    Access mirrors GET /stored/agents/:storedAgentId — 404 when the caller cannot read the target.

  • Add experimental agent.sendMessage() and agent.queueMessage() helpers for the new message-first server routes. (#17238)

Patch Changes
  • Improved agent message, stream, and observational-memory type handling across the client SDKs and playground UI. (#17208)

  • Fixed subscribed client tools so browser-executed tool results continue through the existing thread subscription instead of opening and canceling a second stream. This prevents closed-stream errors in apps like Agent Builder when multiple client tools run during one response. (#17532)

@mastra/deployer@1.39.0
Patch Changes
  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)
@mastra/libsql@1.12.1
Patch Changes
  • Added LibSQL support for the notifications storage domain so notification signals can persist thread-scoped inbox records. (#17241)

    import { LibSQLStore } from '@mastra/libsql';
    
    const storage = new LibSQLStore({ url: 'file:./mastra.db' });
  • Fixed LibSQL memory cleanup so in-memory stores initialize their tables before clearing data. This prevents reset flows from failing with missing table errors such as mastra_resources. (#17532)

@mastra/mcp@1.9.1
Patch Changes
  • Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. (#17410)
@mastra/memory@1.20.2
Patch Changes
  • Improved agent message, stream, and observational-memory type handling across the client SDKs and playground UI. (#17208)

  • Added experimental workingMemory.useStateSignals opt-in. When set to true, working memory is delivered to the model as a state signal (via the new state-signals API) instead of being folded into the system message. Memory auto-attaches a WorkingMemoryStateProcessor that emits a signal with stateId: 'working-memory' and dedups via cacheKey. Subsequent turns emit unified-diff deltas against the prior snapshot when the diff is smaller than the snapshot (markdown mode only); schema mode and the fallback path always emit a full snapshot. The working-memory tool is registered as setWorkingMemory instead of updateWorkingMemory under this opt-in so legacy persistence/prompt strip filters naturally bypass it. The default (false) preserves the existing system-message behavior. useStateSignals is not supported with template working memory version: 'vnext'. (#17497)

    import { Memory } from '@mastra/memory';
    
    const memory = new Memory({
      options: {
        workingMemory: {
          enabled: true,
          useStateSignals: true,
        },
      },
    });
@mastra/mongodb@1.9.1
Patch Changes
  • Added notification inbox storage support for MongoDB stores. (#17241)

    import { MongoDBStore } from '@mastra/mongodb';
    
    const storage = new MongoDBStore({ url: process.env.MONGODB_URI!, dbName: 'mastra' });

    Agents using this store can persist thread-scoped notification inbox records for notification signals.

@mastra/pg@1.12.1
Patch Changes
  • Added notification inbox storage support for Postgres stores. (#17241)

    import { PostgresStore } from '@mastra/pg';
    
    const storage = new PostgresStore({ connectionString: process.env.POSTGRES_URL! });

    Agents using this store can persist thread-scoped notification inbox records for notification signals.

@mastra/playground-ui@32.0.0
Patch Changes
  • Improved agent message, stream, and observational-memory type handling across the client SDKs and playground UI. (#17208)
@mastra/react@0.5.0
Minor Changes
  • Add a type-safe MessageFactory component to @mastra/react for rendering a MastraDBMessage with your own per-part components, and align its tripwire/task-verdict types with @mastra/core. (#17514)

    @mastra/react

    MessageFactory provides optional, fully type-safe render functions for each kind of message part. Only the renderer matching a part's type runs, and each receives correctly narrowed props; missing renderers fall back gracefully. Runtime-only dynamic-tool and AI SDK v5 tool-${string} parts are covered by a dedicated DynamicTool renderer, and optional role wrappers let you frame parts per message role.

    import { MessageFactory } from '@mastra/react';
    
    <MessageFactory
      message={message}
      Text={part => <p>{part.text}</p>}
      ToolInvocation={part => <ToolCard name={part.toolInvocation.toolName} />}
      DynamicTool={part => <ToolCard name={part.toolName} state={part.state} />}
      Data={part => <DataView type={part.type} data={part.data} />}
      roles={{ Signal: ({ children }) => <SignalFrame>{children}</SignalFrame> }}
    />;

    It also accepts an optional status prop with four strongly-typed slots that render from a message's metadata while keeping part renderers pure. Tripwire, Warning, and Error are replacement slots (rendered instead of the parts when metadata.status matches); Task is an adjacent slot (rendered alongside the parts when a task-completion verdict exists). The factory only surfaces metadata to the slots and never filters it (for example, it still invokes Task when suppressFeedback is true) — the consumer decides what to render or skip. Existing behavior is unchanged when status is omitted.

    <MessageFactory
      message={message}
      status={{
        Error: ({ text }) => <ErrorNotice>{text}</ErrorNotice>,
        Task: ({ passed, suppressFeedback }) => (suppressFeedback ? null : <TaskVerdict passed={passed} />),
      }}
      {...renderers}
    />

    The narrowed part types used by the renderers are exported so consumers can type their own components: TextPart, ReasoningPart, FilePart, StepStartPart, ToolInvocationPart, SourceDocumentPart, and SourceUrlPart, plus MessageFactoryPart (the exact union of part shapes MessageFactory can dispatch — the typed accumulator parts plus the runtime-only dynamic-tool / tool-${string} parts) for typing part arrays precisely instead of unknown[].

    MastraDBMessageMetadata.isTaskCompleteResult is now typed as the { passed?, suppressFeedback? } completion-verdict shape (matching completionResult) instead of boolean, so the Task slot resolves verdicts from either field without a cast.

    TripwireMetadata is now an alias of core's TripwirePayload, and the message accumulator persists the canonical shape. Two behavioral changes to persisted metadata.tripwire:

    • The tripwire reason is now persisted as tripwire.reason (previously it was only stored in the message text part).
    • The processor metadata field was renamed from tripwire.tripwirePayload to tripwire.metadata to match the canonical type.

    The MessageFactory Tripwire slot receives reason through props.tripwire.

    @mastra/core

    Exported the canonical IsTaskCompletePayload and TripwirePayload types from @mastra/core/stream so consumers can type their own task/completion and tripwire UI against them instead of redeclaring the shapes.

    import type { IsTaskCompletePayload, TripwirePayload } from '@mastra/core/stream';
  • Use the message-first client helper for user-authored thread input while preserving fallback behavior for older servers that only support the legacy signal route. (#17238)

Patch Changes
  • Improved agent message, stream, and observational-memory type handling across the client SDKs and playground UI. (#17208)
@mastra/server@1.39.0
Minor Changes
  • Added GET /stored/agents/:storedAgentId/dependents endpoint that lists agents (#17183) referencing a stored agent as a sub-agent.

    const { dependents, hiddenCount } = await client.getStoredAgent(id).dependents();
    // { dependents: [{ id: 'parent-1', name: 'Triager' }], hiddenCount: 2 }
    • dependents — caller-readable agents (public agents and the caller's own private agents) with id + name.
    • hiddenCount — cross-workspace dependents the caller cannot read, only surfaced when the target agent is public.

    Access mirrors GET /stored/agents/:storedAgentId — 404 when the caller cannot read the target.

Patch Changes
  • Fixed memory status incorrectly reporting memory as enabled for agents without memory configured. The /api/memory/status endpoint now returns false for a resolved agent that has no memory, even when the Mastra instance has storage configured. Previously, Studio would render memory UI for such agents. (#17506)

  • Fixed subscribed client tools so browser-executed tool results continue through the existing thread subscription instead of opening and canceling a second stream. This prevents closed-stream errors in apps like Agent Builder when multiple client tools run during one response. (#17532)

Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.38.0

Highlights

OAuth ToolProviders runtime for stored agents (connections + toolkit-scoped tool resolution)

Mastra now includes the v1 ToolProvider runtime plus server routes, editor wiring, and client SDK methods to manage OAuth-backed connections and let stored agents pin specific connections per toolkit at execution time (including per-author vs shared vs caller-supplied scopes).

New MySQL storage adapter

@mastra/mysql adds a first-party MySQL storage backend with broad domain coverage (memory, threads, workflows, observability, agents, etc.), including reliable fresh-DB initialization and index creation fixes.

New cloud sandbox and browser deployment options (Vercel MicroVM + Firecrawl Browser)

@mastra/vercel introduces VercelMicroVMSandbox (ephemeral Firecracker MicroVMs with persistent in-session FS, ports, and background processes), and @mastra/browser-firecrawl ships a Firecrawl-hosted Chrome sandbox while keeping the same @mastra/agent-browser tool surface.

Code Mode (experimental): TypeScript orchestration tool for agents

createCodeMode() lets agents generate and run a single TypeScript program to orchestrate multiple tool calls (batching with Promise.all, aggregations, real computation) while tools still execute host-side with validation and tracing.

Message-first + approval-first thread APIs (send/queue messages, subscription-native tool approval, fire-and-forget resumes)

New agent.sendMessage() / agent.queueMessage() and matching server routes enable higher-level “user message into thread” flows (vs low-level signals), tool approvals can now resume via the active thread subscription (with message queueing during approval), and workflows add resumeNoWait/resumeAsync() fire-and-forget semantics returning { runId } immediately.

Breaking Changes
  • None called out in this changelog.

Changelog

@mastra/core@1.38.0
Minor Changes
  • Added channels.threadContext.addSystemMessage to opt out of the built-in channel system message. By default, AgentChannels injects a short system message telling the agent which channel/platform a request came from (DM vs public, bot identity, etc.). Set addSystemMessage: false to skip it: (#17171)

    new Agent({
      channels: {
        adapters: { slack: createSlackAdapter() },
        threadContext: {
          addSystemMessage: false,
        },
      },
    });
  • Added agent.sendMessage() and agent.queueMessage() APIs for sending user-authored input into agent threads. These are intended to be used with agent.subscribeToThread() and replace lower-level agent.sendSignal() calls for regular user messages. (#17191)

    await agent.sendMessage('Continue with the latest user input', { resourceId, threadId });
    await agent.queueMessage('Follow up after the active turn finishes', { resourceId, threadId });
    
    await agent.sendMessage(
      {
        contents: [
          { type: 'text', text: 'What is in this image?' },
          { type: 'file', data: imageBase64, mediaType: 'image/png', filename: 'screenshot.png' },
        ],
      },
      { resourceId, threadId },
    );
  • Support conditional, function-based tool approvals. (#17337)

    • MCP tools that wrap a server-level requireToolApproval function are now honored end-to-end. The per-tool approval function was previously dropped when the agent converted MCP tools (it kept only the boolean flag), so conditional approval silently fell back to always-on. CoreToolBuilder now preserves a needsApprovalFn attached directly to a tool instance.
    • The global requireToolApproval option on agent.stream/agent.generate now accepts a function in addition to a boolean. It is evaluated per tool call with the tool name, arguments, and request context, enabling policies such as regex allowlists on tool names. Returning true requires approval for that call; false allows it. On error the call defaults to requiring approval. When a function policy is set, tool calls run sequentially so approval suspensions don't race. Durable agents and stored agents continue to accept only a boolean (a function degrades to requiring approval for every call, since their options must be serializable).
    // Approve only tool calls whose name is not on an allowlist.
    const allowlist = /^(get|list|search)_/;
    await agent.generate('...', {
      requireToolApproval: ({ toolName }) => !allowlist.test(toolName),
    });
    • Precedence is unchanged from before: a per-tool approval function (createTool({ requireApproval: fn }) or an MCP-derived needsApprovalFn) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returning false even when the global option is on. The only new behavior is that the global option may now be a function in addition to a boolean.
    • The previously implicit, runtime-attached per-tool approval predicate is now a typed contract: @mastra/core exports NeedsApprovalFn and declares the optional needsApprovalFn property on the Tool class. The MCP client and the agent runtime now share this typed contract instead of reaching through any. This is additive — no public API changes.
  • Added harness events for session lifecycle updates, mode changes, model changes, and cloned threads. (#17290)

    Users can now subscribe to harness events to observe harness activity.

    Example

    const unsubscribe = harness.subscribe(event => {
      console.log(event.id, event.type);
    });
  • Added agent override support to the agent and editor APIs. (#17227)

    Code-defined agents can now declare which fields Studio may edit with the editor option:

    new Agent({
      name: 'Weather Agent',
      model,
      editor: {
        instructions: true,
        tools: { description: true },
      },
    });

    The editor applies stored overrides only for fields the editor config owns, so locked fields keep their code-defined values. Per-agent editor: false locks an agent entirely.

    MastraEditor accepts a source setting that picks the editing experience:

    new MastraEditor({ source: 'code' });
    • source: 'code' — the editor auto-wires a FilesystemStore (defaulting to ./mastra/editor/, overridable with codePath) when no editor storage is supplied, and persists overrides as deterministic per-agent JSON files.
    • source: 'db' (default) — keeps the existing storage-backed flow against whatever storage the project has configured.
  • Added the tool_provider_connections storage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)

    What you can do

    • Pin a connection on a stored agent's config and have it round-trip on read/write/create.
    • Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.

    Example

    import { LibSQLStore } from '@mastra/libsql';
    
    const storage = new LibSQLStore({ url: process.env.DATABASE_URL });
    
    // Persist an OAuth connection that an agent can pin later
    await storage.toolProviders.upsertConnection({
      authorId: 'user-123',
      providerId: 'composio',
      connectionId: 'auth_abc',
      toolkit: 'gmail',
      label: 'Work inbox',
      scope: 'per-author',
    });
    
    // List a user's own connections (admin can omit authorId to list across users)
    const { items } = await storage.toolProviders.listConnectionsByAuthor({
      authorId: 'user-123',
      providerId: 'composio',
    });

    Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.

    PR 1 of 3 split from #17224.

  • Added opt-in MCP server instructions forwarding into agent system prompts. (#17155)

    When an MCP server advertises instructions during initialization, you can now forward that guidance into the system prompt of agents that use the server's tools. This is opt-in — set forwardInstructions: true per server to enable it. Forwarded instructions are injected into the agent's system prompt, so only enable this for servers you trust.

    const mcp = new MCPClient({
      servers: {
        db: {
          url: new URL('http://localhost:4111/mcp'),
          forwardInstructions: true, // opt in; defaults to false
          instructionsMaxLength: 512, // max chars forwarded per server
        },
      },
    });
    
    const agent = new Agent({
      id: 'db-agent',
      name: 'DB Agent',
      instructions: 'Help with database changes.',
      model,
      tools: await mcp.listTools(),
    });

    You can always inspect cached instructions without forwarding them:

    const instructions = mcp.getServerInstructions();
    // => { db: 'Always validate before migrating.', other: undefined }
  • Add fire-and-forget workflow resume that returns immediately with { runId } without awaiting the run output. (#17230)

    For @mastra/inngest, this skips the getRunOutput() polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.

    • Run.resumeAsync() added to core: dispatches the resume in the background and returns { runId } immediately. Engines that poll for results (Inngest) override it to skip polling entirely.
    • InngestRun.resumeAsync() sends the resume event and returns { runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.
    • New POST /workflows/:workflowId/resume-no-wait and POST /agent-builder/:actionId/resume-no-wait routes return { runId } immediately.
    • New client SDK run.resumeNoWait() resolves with { runId }.

    The existing resumeAsync() client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(v2) comments documenting this consolidation.

  • Added experimental Code Mode for agents. createCodeMode returns an execute_typescript tool plus generated instructions that let an agent write one TypeScript program to orchestrate your tools (batch with Promise.all, aggregate, and do math in a real runtime) instead of calling tools one at a time. Tools still run on the host with full validation and tracing; only the orchestration code runs in a workspace sandbox. (#17324)

    import { createCodeMode, createTool } from '@mastra/core/tools';
    
    const { tool, instructions } = createCodeMode({
      tools: { getTopProducts, getProductRatings },
    });
    
    const agent = new Agent({
      instructions: ['You are a helpful assistant.', instructions],
      tools: { execute_typescript: tool },
    });
  • Add per-entity file persistence and per-entity git history to FilesystemVersionedHelpers. (#17225)

    FilesystemVersionedHelpers now accepts three optional hooks that let a storage domain split a published entity across many per-entity JSON files (e.g. agents/<id>.json) instead of one shared map file:

    • perEntityFilesDir — directory (under the FilesystemDB root) for per-entity files.
    • shouldPersistToPerEntityFile(entity) — decide per published entity whether to write its snapshot to a per-entity file.
    • perEntitySnapshotFilter(snapshot, entity) — filter the snapshot before writing it to the per-entity file (e.g. drop fields the user does not own).

    When configured, the helper:

    • Reads per-entity files on hydrate (alongside the shared map file).
    • Writes published snapshots to per-entity files with stable alphabetical key ordering for friendly diffs.
    • Walks per-entity file git history and surfaces each commit as a read-only version in listVersions (in addition to the existing shared-file git history).
    • Skips writing an empty shared map file when every published entity is persisted to per-entity files, so a code-only project does not end up with an empty stub committed to git.

    Also adds FilesystemDB.listDomainFiles, domainFileExists, and removeDomainFile helpers, and broadens GitHistory.getFileAtCommit to be generic so callers can request a per-entity snapshot type rather than the shared-map shape.

    Example — configure a domain to persist each entity to its own file:

    import { FilesystemDB, FilesystemVersionedHelpers } from '@mastra/core/storage';
    
    const db = new FilesystemDB('./mastra/editor');
    
    // Entities that should be persisted as their own files.
    const codeModeEntityIds = new Set(['support-bot']);
    
    const agents = new FilesystemVersionedHelpers({
      db,
      entitiesFile: 'agents.json',
      parentIdField: 'agentId',
      name: 'agents',
      versionMetadataFields: ['id', 'agentId', 'versionNumber', 'createdAt'],
      // New per-entity hooks:
      perEntityFilesDir: 'agents',
      // Decide per entity whether it gets its own file (vs. the shared agents.json).
      shouldPersistToPerEntityFile: entity => codeModeEntityIds.has(entity.id),
      // Drop fields that should not live in the per-entity file.
      perEntitySnapshotFilter: snapshot => {
        const { model, ...userOwned } = snapshot;
        return userOwned;
      },
    });
    
    // Published snapshots are now written to ./mastra/editor/agents/<id>.json,
    // and each git commit to those files shows up as a read-only version.
    const versions = await agents.listVersions({ agentId: 'support-bot' }, 'agentId');
  • Added support for resolving an agent's voice per request. (#17345)

    You can now pass voice as a resolver, just like instructions, tools, and model. Mastra runs the resolver on each getVoice() call and returns a fresh, session-owned voice instance. This fixes concurrent realtime and speech-to-speech sessions on a single deployed agent, where a shared voice instance previously let one session overwrite another session's WebSocket, tools, and instructions.

    A static voice keeps its existing shared behavior, so this change is backward compatible.

    Before

    const agent = new Agent({
      name: 'support-line',
      voice: new GeminiLiveVoice({ apiKey: KEY }), // shared across every session
    });

    After

    const agent = new Agent({
      name: 'support-line',
      voice: ({ requestContext }) => new GeminiLiveVoice({ apiKey: requestContext.get('apiKey') }),
    });
    
    const voice = await agent.getVoice({ requestContext }); // owns its own ws/tools/instructions
    await voice.connect();

    The caller owns the lifecycle of a resolver instance and should call disconnect() when the session ends. The agent.voice getter throws when voice is a resolver because it has no request context; use agent.getVoice({ requestContext }) instead.

  • Workflows that suspend during dataset experiments now resume automatically. Provide resume data in your dataset items and the workflow will continue execution through multiple suspend/resume cycles. (#17378)

    For multi-step workflows, use resumeSteps keyed by step ID:

    const item = {
      input: { prompt: 'Draft a blog post' },
      resumeSteps: { 'approval-step': { approved: true } },
    };

    For single-step workflows, use flat resumeData:

    const item = {
      input: { prompt: 'Draft a blog post' },
      resumeData: { approved: true },
    };

    Storage-backed items can use metadata.resumeSteps or metadata.resumeData as fallback. When no resume data is provided, the suspend payload is returned as output with guidance on how to add it. (#15382)

  • Added request-aware filtering for ToolSearchProcessor search, load, and active tools. The filter hook receives the resolved tool ID as toolName. (#16088)

    new ToolSearchProcessor({
      tools,
      filter: ({ toolName, requestContext }) => {
        const plan = requestContext?.get('plan');
        return plan === 'pro' || !toolName.startsWith('premium_');
      },
    });
  • Added channels.resolveResourceId to control which resourceId owns a channel thread's memory, separately from who sent the message. Useful for SSO apps that want a user's memory shared across web and a Feishu/Lark DM, or group chats scoped to the conversation instead of the sender. Only affects newly-created threads; return the provided default to keep current behavior. (#17471)

    new Agent({
      // ...
      channels: {
        adapters: { slack: createSlackAdapter() },
        resolveResourceId: async ({ thread, message }) => {
          if (thread.isDM) return resolveSsoUserId(message); // shared with web
          return thread.channelId; // group owns the memory
        },
      },
    });
  • Added the disableInit option to the MastraVector base class. When set to true, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existing disableInit behavior on storage adapters and is useful for deployments where schemas and indexes are created ahead of time by a privileged database role, while the application runs with a least-privilege role. (#17272)

    Usage

    const vector = new PgVector({
      id: 'vectors',
      connectionString: process.env.DATABASE_URL,
      disableInit: true,
    });

    The MASTRA_DISABLE_STORAGE_INIT environment variable also disables vector init, so a single flag prevents both storage and vector stores from creating schemas, tables, or indexes at startup.

  • Storage adapters can now receive a narrow back-pointer to the Mastra instance (#17226) via MastraCompositeStore.__registerMastra. This mirrors the existing registration pattern on agents, workflows, memory, scorers and processors, and lets a storage domain look up agents and editor config without pulling the full Mastra type into the storage layer (which would create a circular import).

    The reference is cascaded automatically to any parent composites and owned domain stores, and is wired both during Mastra construction and via setStorage. The editor is registered after storage so editor-driven storage overlays observe the assigned storage.

    A new StorageMastraRef interface exposes only the methods storage needs today (getAgentById, getEditor).

    // Inside a domain store, read the registered reference after Mastra wires it up:
    class MyAgentsStore extends AgentsStorage {
      protected getEditorConfig(agentId: string) {
        // `this.mastra` is populated by MastraCompositeStore.__registerMastra,
        // which runs during Mastra construction and on setStorage().
        const agent = this.mastra?.getAgentById?.(agentId);
        if (agent?.source !== 'code') return undefined;
        return agent.__getEditorConfig?.();
      }
    }
  • Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)

    Stored agents can now pin OAuth connections per toolkit

    A stored agent's config accepts a new toolProviders shape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.

    {
      toolProviders: {
        composio: {
          connections: {
            gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }],
          },
          tools: {
            GMAIL_FETCH_EMAILS: { toolkit: 'gmail' },
          },
        },
      },
    }

    New client SDK surface for managing connections

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: '…' });
    const composio = client.toolProvider('composio');
    
    const { items } = await composio.listConnections({ toolkit: 'gmail' });
    await composio.disconnectConnection('auth_abc');

    New ToolProvider interface for custom providers

    Providers implement a VNext surface (listToolkitsVNext, listToolsVNext, resolveToolsVNext) plus the auth round-trip (authorize, getAuthStatus, listConnections, disconnectConnection, listConnectionFields, health). The Composio provider has been rewritten on this surface; the older catalog methods remain as @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue to work unchanged. The Studio/Builder UI ships separately.

Patch Changes
  • Improved per-message latency in channels by removing two awaited storage round-trips from the chat message dispatch path. (#17185)

  • Fixed Observational Memory and other tagged system context being lost when an agent uses Channels. Channel-specific context now adds itself alongside other processors' system messages instead of replacing them. (#17168)

  • Fixed output processors so they receive agent step lifecycle chunks during streaming. (#16687)

  • Fixed UnixSocketPubSub streaming so a slow or stuck subscriber no longer blocks active local streams or other subscribers. (#17302)

  • Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)

    await agent.sendToolApproval({
      resourceId: 'user-123',
      threadId: 'thread-123',
      toolCallId: 'tool-call-123',
      approved: true,
    });
  • Fixed filterMessagesForPersistence unconditionally trimming whitespace from text parts, which caused spaces between words to be lost when text parts were split by token boundaries in the streaming span-based persistence. The trim now only applies when working memory tags are actually stripped. (#17404)

  • Fixed resumed agent observability spans so agent.resumeStream() and agent.resumeGenerate() use the resume payload as the AGENT_RUN span input instead of an empty array. (#17134)

    Resumed spans now also link back to the suspended trace when persisted tracing context is available, so human-in-the-loop approval flows show the decision payload and remain connected in tracing backends. Fixes #17075.

  • Fixed BatchPartsProcessor dropping the final stream part when a stopWhen condition stops the agent on a non-text part (such as a tool result). The processor batches text deltas and previously deferred the next non-text part to the following stream iteration; if the loop stopped on that part, it was lost. BatchPartsProcessor now returns the flushed batch and hands the non-text part back to the output processor runner, which re-drives it through the full output processor chain. As a result the final tool result always reaches the stream, and the flushed batch still passes through any downstream output processors (e.g. a moderation or PII processor configured after BatchPartsProcessor) instead of bypassing them. Fixes #17094. (#17342)

  • Fixed Convex workflow storage to save concurrent workflow updates atomically. (#16641)

  • Fixed processor-returned systemMessages wiping tagged system messages owned by other processors (e.g. observational memory). Processor args.systemMessages now exposes only the untagged system message bucket, so tagged messages owned by other processors are no longer round-tripped through the replacement API. MessageList.replaceAllSystemMessages() replaces only the untagged bucket and leaves tagged buckets intact. Final model input still receives both via messageList.getAllSystemMessages(). (#16950)

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

  • Fixed AgentExecutionOptions<undefined> (and its public/inner variants) incorrectly requiring a structuredOutput property. When the output type is undefined or null — including a fully-nullish union such as undefined | nullstructuredOutput is now correctly optional, regardless of the strictNullChecks setting. This is the shape produced by AgentConfig.defaultOptions, so defaultOptions: { maxSteps: 50 } now type-checks without a spurious structuredOutput requirement. Object output types still require structuredOutput as before. (#17131)

  • Fixes a crash where OpenAI rejects resumed tool-approval requests with AI_APICallError: Duplicate item found with id rs_*. (#17439)

    The same message stored in PostgreSQL's jsonb column (workflow snapshot) and text column (messages) could have different JSON key orders, causing the deduplication check to treat them as different messages and send the same reasoning item twice. Both representations now compare equal regardless of key order.

  • Fixed a render crash when loading stored threads containing signal messages (such as system reminders). Non-user signal data parts are now merged onto the neighboring assistant message instead of becoming standalone system messages that break assistant-ui. (#17429)

  • Added Alibaba provider support for Qwen models. You can now use Qwen, DashScope, and other Alibaba models with automatic provider detection. (#17433)

    Example usage:

    import { Mastra } from '@mastra/core';
    
    const mastra = new Mastra();
    
    // Use any Alibaba variant - automatically detected
    const agent = mastra.getAgent('myAgent');
    const result = await agent.generate({
      model: '__GATEWAY_ALIBABA_MODEL__',
      messages: [{ role: 'user', content: 'Hello' }],
    });

    Works with all Alibaba variants (alibaba, alibaba-cn, alibaba-coding-plan, etc.) and future variants like alibaba-coding-plan-cn-v2.

  • Fixed observational memory replaying previously observed assistant responses during reprocessing, so past assistant messages no longer reappear in later turns. (#17338)

  • Use queueMessage() for Harness follow-up scheduling while preserving queued follow-up display state. (#17191)

  • Asset download errors now include the failing URL so callers can identify which media link broke and recover from it (e.g. drop the dead part on retry). The URL appears redacted (query string and fragment stripped) in the error message and in full on error.details.url. (#17069)

  • Fixed output processor state continuity for step lifecycle chunks during streaming. Lifecycle chunks (e.g. step-finish) routed through the stream outputWriter now share the same per-processor state map as the main model-output path, so state set in processOutputStream while handling content chunks is visible when the lifecycle chunk is processed (and in processOutputResult). Previously these chunks were handled with an isolated, empty state. (#17370)

    Note: as part of routing lifecycle chunks through output processors, an aborted stream now surfaces content the model produced before the abort (e.g. a text-start chunk and partial text) instead of dropping it. Consumers that assumed an aborted result always had empty text may now observe partial output.

  • Removed a stale AI SDK UI utils dependency from @mastra/core so projects using Zod 4 do not get Zod 3 peer dependency warnings from core. (#16994)

  • Fixed AGENT_RUN spans not closing when an agent stream is aborted mid-flight (e.g. browser disconnect or AbortController.abort()). Aborted runs now end with { status: 'aborted', reason: 'abort' } so traces are exported to observability backends. (#17203)

  • Improved active workflow run listing latency. (#17374)

  • Mastra.shutdown() now releases storage resources automatically. Stores that expose a close() lifecycle hook (such as LibSQLStore) are closed during shutdown, so file handles are freed and the storage directory can be removed cleanly afterward, including on Windows. (#17306)

    const mastra = new Mastra({ storage });
    
    // Storage is closed for you — no manual cleanup needed
    await mastra.shutdown();
  • Add updateThread as an abstract method on the MastraMemory base class and implement it in MockMemory. Previously the method existed only on the concrete Memory subclass, so calling updateThread on a variable typed as MastraMemory (or any other MastraMemory subclass) produced a TypeScript error. Callers can now rename or re-title threads through the base class API without casting. (#17130)

  • Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. (#17165)

  • Fixed read_file line ranges when offsets are past the end of a file (#17275)

  • Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. (#17475)

  • Fixed forEach workflow steps losing completed outputs and status during resume. (#17294)

  • Reduced overhead when streaming tool calls in agentic workflows (#17354)

  • Fixed noisy "Cannot get workflow run. Mastra storage is not initialized" debug logs that appeared on every agent.generate() and agent.stream() call when the agent's Mastra instance had storage configured. (#17344)

    The internal workflow that runs each agent call never received the parent Mastra instance, so it could not see configured storage and logged the warning before falling back to in-memory state. It now receives the Mastra instance. It still does not write any of its own snapshots to your storage, so no extra rows are created.

  • Fixed Google model routing to accept GOOGLE_GENERATIVE_AI_API_KEY when GOOGLE_API_KEY is not set. (#17343)

  • Fixed grep context output so overlapping matches are shown once. (#17274)

  • Added jsonPromptInjection to the scorer judge config so users can opt out of native response_format for models that don't support it (e.g. some Groq Llama models). Previously, every scorer invocation made a wasted 400 API call before falling back to prompt injection. (#17046)

    import { createScorer } from '@mastra/core/evals';
    
    const scorer = createScorer({
      id: 'translation-quality',
      description: 'Evaluates translation quality',
      judge: {
        model: 'groq/llama-3.3-70b-versatile',
        instructions: 'You are an expert evaluator…',
        jsonPromptInjection: true, // skip the unsupported `response_format` attempt
      },
    });

    Fixes #17040.

  • Added native multimodal tool-result support. Core now converts MCP-style tool results with image and audio content parts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads in providerMetadata.mastra.modelOutput. (#16866)

    return {
      content: [
        { type: 'text', text: 'Screenshot captured' },
        { type: 'image', data: base64Png, mimeType: 'image/png' },
      ],
    };
  • Fixed MASTRA_TELEMETRY_DISABLED opt-out detection. The values 1, true, and yes (case-insensitive, trimmed) now reliably disable telemetry in both @mastra/core enterprise events and the mastra CLI's PostHog analytics. (#16990)

    Previously, @mastra/core only treated the literal string '1' as disabled, so common opt-out values like MASTRA_TELEMETRY_DISABLED=true silently kept telemetry on.

    The mastra CLI's PosthogAnalytics constructor now also short-circuits when telemetry is disabled — no disk I/O, no tracking ID generation, no PostHog client. Previously the config file (mastra-cli.json) was written even when telemetry was disabled.

    Example:

    # .env — any of these now reliably disable telemetry
    MASTRA_TELEMETRY_DISABLED=true
    MASTRA_TELEMETRY_DISABLED=1
    MASTRA_TELEMETRY_DISABLED=yes
  • Improved PIIDetector streaming performance. (#17377)

    • Removed per-chunk LLM calls during streaming PII checks.
    • Added local regex detection for common PII types (email, phone, SSN, credit card, IP address, API keys, URLs, UUIDs, crypto wallets, and IBAN).
    • Added regex carryover buffer across chunk boundaries to catch split PII patterns.
    • Buffered context-dependent PII types (names, addresses, dates of birth) with periodic LLM calls at configurable thresholds.
    • Added bufferSize option (default: 200) to control LLM buffer flush threshold.
    • Reduced streaming API cost, latency, and rate-limit pressure.

    Closes #16466.

  • Fixed a TypeScript TS2589 "type instantiation is excessively deep" error when using Mastra alongside deeply-generic libraries such as @hono/zod-openapi. (#17339)

  • Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)

    Better visibility into connection-scope misconfiguration

    When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.

    One bad toolkit no longer disables sibling providers

    If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.

  • Workflows now support an optional metadata field for attaching custom key-value data such as displayName, author, or category. Metadata is preserved through serialization and returned in workflow info API responses. (#17355)

    // Define a workflow with metadata
    const myWorkflow = createWorkflow({
      id: 'data-processing',
      metadata: {
        displayName: 'Data Processing Pipeline',
    
        category: 'ETL',
      },
      inputSchema: z.object({ ... }),
      outputSchema: z.object({ ... }),
    });
    
    // Retrieve workflow info with metadata via the Mastra Server API
    const workflowInfo = await mastraClient.getWorkflow('data-processing');
    console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline"
@mastra/agent-browser@0.3.0
Minor Changes
  • Add waitUntil support to browser_click, browser_press, and browser_select. When provided, the tool waits for the page to reach the given load state (load, domcontentloaded, or networkidle) after the action completes, preventing the next browser_snapshot from capturing stale DOM when the interaction triggers navigation. The parameter is optional and behaviour is unchanged when omitted. (#17426)

    Usage example:

    await browser_click({ ref: '@e1', waitUntil: 'domcontentloaded', timeout: 5000 });

    Fixes #17397.

  • Added extensibility hooks for custom browser providers (e.g. Firecrawl Browser Sandbox). (#15724)

    • New createThreadManager config option to inject a custom thread manager factory
    • Exported AgentBrowserThreadManager class and related types (AgentBrowserSession, AgentBrowserThreadManagerConfig, CreateAgentBrowserThreadManager)
    • Changed several internal members from private to protected to support subclassing
Patch Changes
@mastra/agentcore@0.2.0
Minor Changes
  • Added AWS Bedrock AgentCore Runtime sandbox support. (#16642)

    You can now run Workspace commands in AWS Bedrock AgentCore Runtime through a sandbox provider.

    import { AgentCoreRuntimeSandbox } from '@mastra/agentcore';
    
    const sandbox = new AgentCoreRuntimeSandbox({
      region: 'us-west-2',
      agentRuntimeArn: process.env.AGENTCORE_RUNTIME_ARN!,
    });
    
    const result = await sandbox.executeCommand('node', ['--version']);
Patch Changes
@mastra/ai-sdk@1.4.4
Patch Changes
  • Fixed processor middleware so args.systemMessages only contains untagged system messages. Tagged processor-owned system messages stay on the message list and are still included in the final model input. (#16950)
@mastra/blaxel@0.4.0
Minor Changes
  • Added region support to Blaxel sandboxes: new BlaxelSandbox({ region: 'eu-west-1' }). When omitted, Mastra falls back to BL_REGION and then auto. (#16555)
Patch Changes
@mastra/brightdata@0.2.1
Patch Changes
  • Fix Bright Data tools under Bun by replacing the SDK runtime client with fetch-based REST calls. (#16630)

  • Harden Bright Data search input handling. Country and language codes are now validated as alphabetic two-letter codes, the getBrightDataClient().search.google() client validates and lowercase-normalizes language before the request, and structured JSON (brd_json=1) is only requested when the search format is json so callers can obtain a true raw SERP response. (#17341)

@mastra/browser-firecrawl@0.1.0
Minor Changes
  • Initial release: Firecrawl Browser Sandbox integration for Mastra. (#15724)

    FirecrawlBrowser extends AgentBrowser to run the same deterministic browser tools (snapshot+refs, 16 tools, Playwright over CDP) against Firecrawl's cloud-hosted Chrome instances instead of local or self-hosted browsers.

    Features:

    • Cloud-hosted Chrome via Firecrawl Browser Sandbox API
    • Same tool surface as @mastra/agent-browser (~16 browser automation tools)
    • Thread-scoped browser isolation (scope: 'thread')
    • Automatic session cleanup on close

    Usage:

    import { FirecrawlBrowser } from '@mastra/browser-firecrawl';
    
    const browser = new FirecrawlBrowser({
      firecrawlApiKey: process.env.FIRECRAWL_API_KEY,
      scope: 'thread',
    });
    
    const agent = mastra.getAgent('my-agent', { browser });
Patch Changes
@mastra/claude@0.1.0
Minor Changes
  • Added @mastra/claude, a package for running Claude Agent SDK agents through Mastra. (#16906)

    Create a Claude SDK agent, register it with Mastra, and call generate() or stream() with Mastra-compatible outputs. Runs keep Claude SDK usage, cost estimates, and observability data available to Mastra.

    import { ClaudeSDKAgent } from '@mastra/claude';
    
    export const claudeAgent = new ClaudeSDKAgent({
      id: 'claude-sdk-agent',
      description: 'Use Claude Agent SDK through Mastra.',
      sdkOptions: {
        model: process.env.CLAUDE_CODE_MODEL,
        cwd: process.cwd(),
      },
    });
Patch Changes
@mastra/clickhouse@1.9.1
Patch Changes
  • Added the tool_provider_connections storage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)

    What you can do

    • Pin a connection on a stored agent's config and have it round-trip on read/write/create.
    • Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.

    Example

    import { LibSQLStore } from '@mastra/libsql';
    
    const storage = new LibSQLStore({ url: process.env.DATABASE_URL });
    
    // Persist an OAuth connection that an agent can pin later
    await storage.toolProviders.upsertConnection({
      authorId: 'user-123',
      providerId: 'composio',
      connectionId: 'auth_abc',
      toolkit: 'gmail',
      label: 'Work inbox',
      scope: 'per-author',
    });
    
    // List a user's own connections (admin can omit authorId to list across users)
    const { items } = await storage.toolProviders.listConnectionsByAuthor({
      authorId: 'user-123',
      providerId: 'composio',
    });

    Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.

    PR 1 of 3 split from #17224.

@mastra/client-js@1.22.0
Minor Changes
  • Added an agent override export API and server-side ownership enforcement. (#17228)

    The server and client now expose an agent override export endpoint so Studio can download an agent's overrides as JSON for review or commit workflows. Saves are enforced server-side against each agent's editor config, so only owned fields (instructions, tools, or tool descriptions) are persisted and fields locked by the editor config are stripped.

    The system packages response also reports the active editor source so clients can render the correct editing experience.

  • Add fire-and-forget workflow resume that returns immediately with { runId } without awaiting the run output. (#17230)

    For @mastra/inngest, this skips the getRunOutput() polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.

    • Run.resumeAsync() added to core: dispatches the resume in the background and returns { runId } immediately. Engines that poll for results (Inngest) override it to skip polling entirely.
    • InngestRun.resumeAsync() sends the resume event and returns { runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.
    • New POST /workflows/:workflowId/resume-no-wait and POST /agent-builder/:actionId/resume-no-wait routes return { runId } immediately.
    • New client SDK run.resumeNoWait() resolves with { runId }.

    The existing resumeAsync() client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(v2) comments documenting this consolidation.

  • Added a PATCH /tool-providers/:providerId/connections/:connectionId endpoint and matching client SDK method so authors can rename a connection's display label after creation. (#17249)

    Rename a connection from the client SDK

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: '…' });
    
    await client.getToolProvider('composio').updateConnection('auth_abc', {
      label: 'Work inbox',
    });

    Pass label: null (or an empty string) to clear the existing label. Labels are 1–32 characters and accept letters, digits, spaces, underscores, and hyphens ([A-Za-z0-9 _-]+).

    Ownership enforced server-side

    Non-owners get a 403 unless they hold tool-providers:admin. Shared connections are reachable by every author. The label is stored on the connection row itself, so the rename flows to every agent that pins the connection — no per-agent edit needed.

  • Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)

    Stored agents can now pin OAuth connections per toolkit

    A stored agent's config accepts a new toolProviders shape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.

    {
      toolProviders: {
        composio: {
          connections: {
            gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }],
          },
          tools: {
            GMAIL_FETCH_EMAILS: { toolkit: 'gmail' },
          },
        },
      },
    }

    New client SDK surface for managing connections

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: '…' });
    const composio = client.toolProvider('composio');
    
    const { items } = await composio.listConnections({ toolkit: 'gmail' });
    await composio.disconnectConnection('auth_abc');

    New ToolProvider interface for custom providers

    Providers implement a VNext surface (listToolkitsVNext, listToolsVNext, resolveToolsVNext) plus the auth round-trip (authorize, getAuthStatus, listConnections, disconnectConnection, listConnectionFields, health). The Composio provider has been rewritten on this surface; the older catalog methods remain as @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue to work unchanged. The Studio/Builder UI ships separately.

Patch Changes
  • Separated thread subscription cleanup from active-run aborts so closing or switching a listener only unsubscribes that listener, while explicit cancel still aborts the active run. (#17310)

  • Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)

    await agent.sendToolApproval({
      resourceId: 'user-123',
      threadId: 'thread-123',
      toolCallId: 'tool-call-123',
      approved: true,
    });
  • Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. (#17165)

  • Hardened v1 ToolProvider connection routes and SDK forwarding. (#17248)

    Fail closed on unknown connectionId

    DELETE /tool-providers/:providerId/connections/:connectionId and GET …/usage now return 403 when storage is configured but no persisted row matches the supplied connectionId and the caller isn't an admin. Previously these routes fell through to the caller's own authorId, which let non-admin callers probe (and trigger provider-side revokeConnection for) IDs that didn't belong to them.

    Aligned authorize label validation with stored label rules

    POST /tool-providers/:providerId/authorize now enforces the same label rules the stored toolProviders config uses (min(1), max(32), /^[A-Za-z0-9 _-]+$/). Labels that pass authorize are now guaranteed to pass downstream stored-agent validation.

    SDK forwards toolkit on connection-scoped operations

    @mastra/client-js:

    await client.toolProviders.get('composio').disconnectConnection('ca_xxx', {
      toolkit: 'gmail',
      force: true,
    });
    
    const usage = await client.toolProviders.get('composio').getConnectionUsage('ca_xxx', { toolkit: 'gmail' });

    disconnectConnection now forwards params.toolkit (previously dropped) and getConnectionUsage accepts an optional { toolkit } parameter so toolkit-scoped connection lookups disambiguate correctly server-side.

  • Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)

    Better visibility into connection-scope misconfiguration

    When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.

    One bad toolkit no longer disables sibling providers

    If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.

  • Workflows now support an optional metadata field for attaching custom key-value data such as displayName, author, or category. Metadata is preserved through serialization and returned in workflow info API responses. (#17355)

    // Define a workflow with metadata
    const myWorkflow = createWorkflow({
      id: 'data-processing',
      metadata: {
        displayName: 'Data Processing Pipeline',
    
        category: 'ETL',
      },
      inputSchema: z.object({ ... }),
      outputSchema: z.object({ ... }),
    });
    
    // Retrieve workflow info with metadata via the Mastra Server API
    const workflowInfo = await mastraClient.getWorkflow('data-processing');
    console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline"
@mastra/cloudflare@1.4.1
Patch Changes
  • Added the tool_provider_connections storage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)

    What you can do

    • Pin a connection on a stored agent's config and have it round-trip on read/write/create.
    • Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.

    Example

    import { LibSQLStore } from '@mastra/libsql';
    
    const storage = new LibSQLStore({ url: process.env.DATABASE_URL });
    
    // Persist an OAuth connection that an agent can pin later
    await storage.toolProviders.upsertConnection({
      authorId: 'user-123',
      providerId: 'composio',
      connectionId: 'auth_abc',
      toolkit: 'gmail',
      label: 'Work inbox',
      scope: 'per-author',
    });
    
    // List a user's own connections (admin can omit authorId to list across users)
    const { items } = await storage.toolProviders.listConnectionsByAuthor({
      authorId: 'user-123',
      providerId: 'composio',
    });

    Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.

    PR 1 of 3 split from #17224.

@mastra/convex@1.2.1
Patch Changes
  • Fixed Convex message lookups so they use indexed ids instead of a capped full-table scan. (#17409)

  • Fixed Convex workflow storage to save concurrent workflow updates atomically. (#16641)

  • Fixed concurrent Convex memory updates from overwriting each other (#17299)

  • Fixed forEach workflow steps losing completed outputs and status during resume. (#17294)

  • Fixed Convex JS vector scans to include all paginated vectors. (#17270)

@mastra/cursor@0.1.0
Minor Changes
  • Added @mastra/cursor, a package for running Cursor SDK agents through Mastra. (#16906)

    Create a Cursor SDK agent, register it with Mastra, and call generate() or stream() with Mastra-compatible outputs. Runs keep Cursor SDK usage and observability data available to Mastra.

    import { CursorSDKAgent } from '@mastra/cursor';
    
    export const cursorAgent = new CursorSDKAgent({
      id: 'cursor-sdk-agent',
      description: 'Use Cursor Agent SDK through Mastra.',
      sdkOptions: {
        apiKey: process.env.CURSOR_API_KEY,
        model: { id: process.env.CURSOR_MODEL_ID! },
        local: {
          cwd: process.cwd(),
        },
      },
    });
Patch Changes
@mastra/daytona@0.4.1
Patch Changes
  • Fixed Daytona command execution to reject invalid environment variable names (#17279)

  • Fixed sandbox execution results to report killed and timed out commands. (#17281)

@mastra/deployer@1.38.0
Patch Changes
  • The server now installs SIGINT/SIGTERM handlers and runs mastra.shutdown() before exiting, allowing storage backends to release resources cleanly instead of being terminated mid-flight. (#17413)

  • Fixed Studio playground browser telemetry not respecting MASTRA_TELEMETRY_DISABLED. The dev server was hardcoding an empty value into the served index.html, so window.MASTRA_TELEMETRY_DISABLED was always falsy in the browser and the playground React app initialized PostHog regardless of the user's .env. The dev server now propagates process.env.MASTRA_TELEMETRY_DISABLED to the browser, where the playground applies the same canonical opt-out parsing as the rest of the framework. (#16990)

    Before: Setting MASTRA_TELEMETRY_DISABLED=true in .env had no effect on playground network requests to PostHog.

    After:

    # .env
    MASTRA_TELEMETRY_DISABLED=true

    Playground analytics are now disabled.

  • Fixed false-positive LOCAL_STORAGE_PATH preflight errors caused by library code (e.g. Agent Builder prompt templates). Added a Rollup plugin (mastra-local-storage-detector) to the deployer that detects host-local storage URLs during bundling — only user modules are inspected (node_modules excluded), and tree-shaken code is ignored. The CLI preflight check now reads this bundler-generated metadata instead of scanning raw bundle source. (#17286)

  • Enabled Studio via the CLI and deployers to use agent signal subscriptions by default while preserving MASTRA_AGENT_SIGNALS=false, enableThreadSignals: false, and explicit legacy Stream as opt-outs. The React useChat() hook remains opt-in for SDK consumers via enableThreadSignals: true. (#17313)

@mastra/deployer-vercel@1.1.33
Patch Changes
  • Enabled Studio via the CLI and deployers to use agent signal subscriptions by default while preserving MASTRA_AGENT_SIGNALS=false, enableThreadSignals: false, and explicit legacy Stream as opt-outs. The React useChat() hook remains opt-in for SDK consumers via enableThreadSignals: true. (#17313)
@mastra/docker@0.3.0
Minor Changes
  • Added name option to DockerSandbox for setting the container's display name. (#17266)

    Previously, DockerSandbox did not forward a container name to Docker, so containers were created with random names like gracious_tu even though the docs implied id was used for naming. The new name option (defaults to id) is now passed to docker run --name and is sanitized to fit Docker's container-name rules ([a-zA-Z0-9_.-]).

    import { DockerSandbox } from '@mastra/docker';
    
    // Before: container ended up with a random Docker-assigned name
    new DockerSandbox({ id: 'user-1001' });
    
    // After: the id is used as the container name by default
    new DockerSandbox({ id: 'user-1001' });
    // → docker ps shows 'user-1001'
    
    // Or override explicitly
    new DockerSandbox({ id: 'user-1001', name: 'tenant-acme-dev' });

    Closes #17263.

Patch Changes
  • Fixed sandbox execution results to report killed and timed out commands. (#17281)
@mastra/duckdb@1.4.1
Patch Changes
  • Fixed DuckDB "Conflicting lock is held" error on mastra dev hot reload. DuckDBStore now releases its native file lock on shutdown so the restarted dev process can reopen the same database file. (#17413)
@mastra/e2b@0.3.2
Patch Changes
  • Fix E2B sandbox creation failing with "Sandbox.betaCreate is not a function" on e2b SDK 2.24.0+. The adapter now uses the stable Sandbox.create() API with lifecycle: { onTimeout: 'pause' } (replacing the removed betaCreate/autoPause), and requires e2b >= 2.24.0. (#17261)
@mastra/editor@0.11.0
Minor Changes
  • Added agent override support to the agent and editor APIs. (#17227)

    Code-defined agents can now declare which fields Studio may edit with the editor option:

    new Agent({
      name: 'Weather Agent',
      model,
      editor: {
        instructions: true,
        tools: { description: true },
      },
    });

    The editor applies stored overrides only for fields the editor config owns, so locked fields keep their code-defined values. Per-agent editor: false locks an agent entirely.

    MastraEditor accepts a source setting that picks the editing experience:

    new MastraEditor({ source: 'code' });
    • source: 'code' — the editor auto-wires a FilesystemStore (defaulting to ./mastra/editor/, overridable with codePath) when no editor storage is supplied, and persists overrides as deterministic per-agent JSON files.
    • source: 'db' (default) — keeps the existing storage-backed flow against whatever storage the project has configured.
  • Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)

    Stored agents can now pin OAuth connections per toolkit

    A stored agent's config accepts a new toolProviders shape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.

    {
      toolProviders: {
        composio: {
          connections: {
            gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }],
          },
          tools: {
            GMAIL_FETCH_EMAILS: { toolkit: 'gmail' },
          },
        },
      },
    }

    New client SDK surface for managing connections

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: '…' });
    const composio = client.toolProvider('composio');
    
    const { items } = await composio.listConnections({ toolkit: 'gmail' });
    await composio.disconnectConnection('auth_abc');

    New ToolProvider interface for custom providers

    Providers implement a VNext surface (listToolkitsVNext, listToolsVNext, resolveToolsVNext) plus the auth round-trip (authorize, getAuthStatus, listConnections, disconnectConnection, listConnectionFields, health). The Composio provider has been rewritten on this surface; the older catalog methods remain as @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue to work unchanged. The Studio/Builder UI ships separately.

Patch Changes
  • Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)

    Better visibility into connection-scope misconfiguration

    When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.

    One bad toolkit no longer disables sibling providers

    If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.

  • Improved the Agent Builder system prompt so it produces more reliable agents from starter cards and freeform prompts. (#17424)

  • Agent Builder is now more resilient to transient and provider-specific stream errors out of the box. The built-in builder agent ships with three error processors enabled by default — automatic retry of transient OpenAI errors (such as server_error, rate_limit, and overloaded), recovery from Anthropic 400 prefill rejections, and per-provider history-shape fixes — so flaky LLM calls no longer end the conversation. You can still pass your own errorProcessors to createBuilderAgent to extend or replace these defaults. (#17481)

@mastra/evals@1.2.4
Patch Changes
  • Fixed the hallucination and tool-usage scorers returning incorrect scores when observable memory is enabled. These scorers now detect tool calls in every message format, so responses are no longer wrongly scored as fully hallucinated or as using zero tools. (#17321)
@mastra/express@1.3.26
Patch Changes
  • Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)

  • Fixed validation error responses on routes with bodySchema, queryParamSchema, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[0].message. Fixes #17167. (#17172)

  • Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)

@mastra/fastembed@1.1.2
Patch Changes
  • Fixed FastEmbed so repeated embedding calls reuse loaded models instead of loading a new model each time. (#17303)
@mastra/fastify@1.3.26
Patch Changes
  • Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)

  • Fixed custom API route responses dropping headers set by Fastify plugins. Headers applied via Fastify hooks (e.g. Access-Control-Allow-Origin from @fastify/cors) were overwritten when the adapter hijacked the reply to stream the custom route response. The adapter now merges hook-set headers into the response before hijack — matching the behavior already implemented for streaming routes. (#15719)

  • Fixed validation error responses on routes with bodySchema, queryParamSchema, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[0].message. Fixes #17167. (#17172)

  • Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)

@mastra/hono@1.4.21
Patch Changes
  • Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)

  • Fixed validation error responses on routes with bodySchema, queryParamSchema, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[0].message. Fixes #17167. (#17172)

  • Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)

@mastra/inngest@1.5.0
Minor Changes
  • Add fire-and-forget workflow resume that returns immediately with { runId } without awaiting the run output. (#17230)

    For @mastra/inngest, this skips the getRunOutput() polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.

    • Run.resumeAsync() added to core: dispatches the resume in the background and returns { runId } immediately. Engines that poll for results (Inngest) override it to skip polling entirely.
    • InngestRun.resumeAsync() sends the resume event and returns { runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.
    • New POST /workflows/:workflowId/resume-no-wait and POST /agent-builder/:actionId/resume-no-wait routes return { runId } immediately.
    • New client SDK run.resumeNoWait() resolves with { runId }.

    The existing resumeAsync() client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(v2) comments documenting this consolidation.

  • Added connect() to support Inngest Connect for Mastra workflows. Use this when running workflow execution in a dedicated long-running worker process that should not expose an inbound HTTP endpoint: (#17064)

    import { connect } from '@mastra/inngest/connect';
    
    await connect({
      mastra,
      inngest,
      instanceId: 'worker-1',
      maxWorkerConcurrency: 10,
    });

    connect() uses the same Mastra workflow functions as serve(), including nested and cron workflows. serve() is unchanged.

Patch Changes
  • Fixed processor workflow steps so args.systemMessages only contains untagged system messages. Tagged processor-owned system messages stay on the message list and are still included in the final model input. (#16950)
@mastra/koa@1.5.9
Patch Changes
  • Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)

  • Fixed validation error responses on routes with bodySchema, queryParamSchema, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[0].message. Fixes #17167. (#17172)

  • Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)

@mastra/libsql@1.12.0
Minor Changes
  • Added the tool_provider_connections storage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)

    What you can do

    • Pin a connection on a stored agent's config and have it round-trip on read/write/create.
    • Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.

    Example

    import { LibSQLStore } from '@mastra/libsql';
    
    const storage = new LibSQLStore({ url: process.env.DATABASE_URL });
    
    // Persist an OAuth connection that an agent can pin later
    await storage.toolProviders.upsertConnection({
      authorId: 'user-123',
      providerId: 'composio',
      connectionId: 'auth_abc',
      toolkit: 'gmail',
      label: 'Work inbox',
      scope: 'per-author',
    });
    
    // List a user's own connections (admin can omit authorId to list across users)
    const { items } = await storage.toolProviders.listConnectionsByAuthor({
      authorId: 'user-123',
      providerId: 'composio',
    });

    Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.

    PR 1 of 3 split from #17224.

Patch Changes
  • Added a public close() method to LibSQLStore that releases SQLite file handles and cleans up the WAL/shm sidecar files. Previously these handles stayed open until the process exited, which on Windows caused EBUSY errors when removing the storage directory after shutdown. Mastra.shutdown() now calls close() automatically, so you no longer need to reach into private fields. (#17306)

    const storage = new LibSQLStore({ id: 'my-store', url: 'file:./dev.db' });
    
    // Release all file handles, including WAL/shm sidecar files
    await storage.close();
    
    // Now safe to remove the storage directory on all platforms, including Windows
    await fs.rm('./dev.db', { recursive: true, force: true });
@mastra/loggers@1.1.2
Patch Changes
  • Added messageKey option to PinoLogger for compatibility with structured-log aggregators. Set messageKey: 'message' to emit log messages under the message field expected by Google Cloud Logging, Datadog, ECS, and AWS CloudWatch. (#17450)
@mastra/mcp@1.9.0
Minor Changes
  • Added opt-in MCP server instructions forwarding into agent system prompts. (#17155)

    When an MCP server advertises instructions during initialization, you can now forward that guidance into the system prompt of agents that use the server's tools. This is opt-in — set forwardInstructions: true per server to enable it. Forwarded instructions are injected into the agent's system prompt, so only enable this for servers you trust.

    const mcp = new MCPClient({
      servers: {
        db: {
          url: new URL('http://localhost:4111/mcp'),
          forwardInstructions: true, // opt in; defaults to false
          instructionsMaxLength: 512, // max chars forwarded per server
        },
      },
    });
    
    const agent = new Agent({
      id: 'db-agent',
      name: 'DB Agent',
      instructions: 'Help with database changes.',
      model,
      tools: await mcp.listTools(),
    });

    You can always inspect cached instructions without forwarding them:

    const instructions = mcp.getServerInstructions();
    // => { db: 'Always validate before migrating.', other: undefined }
  • Added native multimodal tool-result support. Core now converts MCP-style tool results with image and audio content parts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads in providerMetadata.mastra.modelOutput. (#16866)

    return {
      content: [
        { type: 'text', text: 'Screenshot captured' },
        { type: 'image', data: base64Png, mimeType: 'image/png' },
      ],
    };
Patch Changes
  • Support conditional, function-based tool approvals. (#17337)

    • MCP tools that wrap a server-level requireToolApproval function are now honored end-to-end. The per-tool approval function was previously dropped when the agent converted MCP tools (it kept only the boolean flag), so conditional approval silently fell back to always-on. CoreToolBuilder now preserves a needsApprovalFn attached directly to a tool instance.
    • The global requireToolApproval option on agent.stream/agent.generate now accepts a function in addition to a boolean. It is evaluated per tool call with the tool name, arguments, and request context, enabling policies such as regex allowlists on tool names. Returning true requires approval for that call; false allows it. On error the call defaults to requiring approval. When a function policy is set, tool calls run sequentially so approval suspensions don't race. Durable agents and stored agents continue to accept only a boolean (a function degrades to requiring approval for every call, since their options must be serializable).
    // Approve only tool calls whose name is not on an allowlist.
    const allowlist = /^(get|list|search)_/;
    await agent.generate('...', {
      requireToolApproval: ({ toolName }) => !allowlist.test(toolName),
    });
    • Precedence is unchanged from before: a per-tool approval function (createTool({ requireApproval: fn }) or an MCP-derived needsApprovalFn) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returning false even when the global option is on. The only new behavior is that the global option may now be a function in addition to a boolean.
    • The previously implicit, runtime-attached per-tool approval predicate is now a typed contract: @mastra/core exports NeedsApprovalFn and declares the optional needsApprovalFn property on the Tool class. The MCP client and the agent runtime now share this typed contract instead of reaching through any. This is additive — no public API changes.
  • Close the stale MCP transport before reconnecting so SSE connections no longer leak orphaned EventSource instances and accumulate server-side sessions on implicit reconnect. (#17326)

  • Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. (#17475)

@mastra/memory@1.20.1
Patch Changes
  • Fixed observational memory replaying previously observed assistant responses during reprocessing, so past assistant messages no longer reappear in later turns. (#17338)

  • Fixed a crash in Cloudflare Workers when using a Zod schema for working memory. Working-memory input is now validated directly by the provided schema validator, which avoids runtime restrictions in Cloudflare Workers. (#17327)

  • Preserve system-reminder filtering for normalized reactive signal metadata. (#17191)

  • Added observation.bufferOnIdle to opt idle turns into background observation buffering and carry the signal sender needed for background notifications. (#17181)

  • Fixed Memory.saveMessages not populating role, content, and created_at in the vector store metadata. Calls to GET /api/memory/search now return matches with the full message shape regardless of whether messages were saved through agent.generate/agent.stream or written directly via Memory.saveMessages (for example through the POST /api/memory/save-messages HTTP route used by external agents). (#16381)

@mastra/modal@0.2.1
Patch Changes
  • Fixed sandbox execution results to report killed and timed out commands. (#17281)
@mastra/mysql@0.1.0
Minor Changes
  • Added the MySQL storage adapter for Mastra. Use it as a storage backend with the same domain coverage as the other first-party adapters (memory, threads, workflows, observability, agents, and more). (#17446)

    import { MySQLStore } from '@mastra/mysql';
    
    const store = new MySQLStore({
      connectionString: 'mysql://user:password@localhost:3306/mastra',
    });

    This release also makes table and index setup reliable on a brand-new database:

    • Fixed store initialization failing on a fresh database. Idempotency for favorites is now enforced by the table's primary key instead of a separate index that MySQL rejected, which previously aborted setup and left the connection pool unusable.
    • Fixed default performance indexes silently failing to be created. Indexes on text columns now include a key-length prefix so they are created instead of skipped.
Patch Changes
@mastra/nestjs@0.1.10
Patch Changes
  • Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)

  • Fixed validation error responses on routes with bodySchema, queryParamSchema, or pathParamSchema losing field path information when consumers pin a different zod major than the one bundled with this adapter. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[0].message. (#17172)

    ValidationError.zodError is now typed as ZodErrorLike (a structural subset of ZodError exposing issues[]) so consumers pinning a different zod major still type-check. The runtime value is unchanged; cast to your installed ZodError type if you need its instance methods.

    Fixes #17167.

  • Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)

@mastra/observability@1.14.1
Patch Changes
  • Added documentation explaining how to query and retrieve metric data from Mastra's observability store. Developers can now learn how to aggregate metrics, break them down by labels, visualize time series, and calculate percentiles using the in-process store API, HTTP endpoints, or CLI commands. (#17178)

  • Fix null estimatedCost for OpenRouter models whose id carries a vendor prefix and a dotted version (e.g. google/gemini-2.5-flash). These previously failed to match the pricing data (gemini-2-5-flash), leaving cost unreported in Studio's "Total Model Cost". Cost is now estimated correctly for these routes. (#17140)

  • Added support for costs supplied by external SDK agent integrations. (#16906)

    When an SDK agent records an estimated cost on its model generation span, observability now carries that cost onto the auto-extracted model token metric. This lets storage-backed metric queries and dashboards display costs reported by external agent SDKs, even when Mastra cannot calculate the cost from its own pricing registry.

@mastra/pg@1.12.0
Minor Changes
  • Added the disableInit option to the MastraVector base class. When set to true, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existing disableInit behavior on storage adapters and is useful for deployments where schemas and indexes are created ahead of time by a privileged database role, while the application runs with a least-privilege role. (#17272)

    Usage

    const vector = new PgVector({
      id: 'vectors',
      connectionString: process.env.DATABASE_URL,
      disableInit: true,
    });

    The MASTRA_DISABLE_STORAGE_INIT environment variable also disables vector init, so a single flag prevents both storage and vector stores from creating schemas, tables, or indexes at startup.

Patch Changes
  • Fixed PostgresStore ignoring an explicit ssl option when the connectionString also carries an sslmode=/ssl= query param. node-postgres re-parses the connection string and Object.assigns the URL-derived ssl over the explicit one, so a config like { connectionString: '...?sslmode=require', ssl: { rejectUnauthorized: false } } silently dropped rejectUnauthorized: false and failed with UNABLE_TO_GET_ISSUER_CERT_LOCALLY against self-signed CAs. The connection-string branch now parses the URL and applies the explicit ssl last, while still honoring URL-driven SSL when no ssl option is provided. Fixes #17307. (#17356)

  • Improved Postgres memory message save performance (#17351)

@mastra/playground-ui@31.0.0
Minor Changes
  • Made ButtonsGroup compose joined controls (searchbar + dropdown pills, split buttons, steppers) cleanly, and improved InputGroup so it drops straight into one. (#17259)

    • ButtonsGroup with spacing="close" fuses outline, filled and Select segments into one pill with a single clean divider, a complete focus ring (no missing side), and no consumer width classes.
    • InputGroup fills a flex row on its own, matches a same-size sibling height, and propagates size via data-size (no React context) — so an icon + input segment composes inside a ButtonsGroup pill with no layout classes.

    Use InputGroup (icon as an InputGroupAddon, optional clear button as an InputGroupButton) to build an icon input — it owns the box, focus, hover and error states on the focusable wrapper:

    import {
      ButtonsGroup,
      InputGroup,
      InputGroupAddon,
      InputGroupInput,
      Select,
      SelectTrigger,
      SelectValue,
      SelectContent,
      SelectItem,
    } from '@mastra/playground-ui';
    
    <ButtonsGroup spacing="close">
      <InputGroup variant="outline">
        <InputGroupAddon align="inline-start">
          <SearchIcon />
        </InputGroupAddon>
        <InputGroupInput placeholder="Search projects..." />
      </InputGroup>
      <Select value={sort} onValueChange={setSort}>
        <SelectTrigger className="rounded-full">
          <SelectValue />
        </SelectTrigger>
        <SelectContent align="end">{/* options */}</SelectContent>
      </Select>
    </ButtonsGroup>;
  • Refined the focus state of form inputs in @mastra/playground-ui. Applies to Input, InputGroup, Searchbar, and Textarea. (#17259)

    • Removed the green border and glow that appeared on focus.
    • On focus, the field shows a subtle background shift and brightens its border to a neutral tone, so the focused field stays clearly visible on any underlying surface.
    • Made single-line inputs fully rounded to match the design system. Multi-line surfaces (Textarea, and InputGroup with a block-style addon) keep a softer rounded-xl corner.
    • Added filled and outline variants for consumers that need to choose between the new surface treatment and a quieter border-only treatment.
    • The unstyled variant of Input and Textarea no longer leaks the browser default focus outline.

    Input, Textarea, and InputGroup default to the filled surface. Searchbar and ListSearch default to the outline (transparent) treatment. For Searchbar this matches its previous transparent look. ListSearch previously rendered a filled (bg-surface2), rounded-lg box, so its search fields across the list pages now read as transparent, fully-rounded pills — pass variant="filled" to keep them on a filled surface:

    import { Input, InputGroup, InputGroupAddon, InputGroupInput, Searchbar } from '@mastra/playground-ui';
    
    <Input placeholder="Name" />
    <Input variant="outline" placeholder="Name" />
    
    <InputGroup variant="outline">
      <InputGroupAddon>
        <SearchIcon />
      </InputGroupAddon>
      <InputGroupInput placeholder="Email" />
    </InputGroup>
    
    <Searchbar label="Search agents" placeholder="Search agents..." onSearch={handleSearch} />
    <Searchbar variant="filled" label="Search agents" placeholder="Search agents..." onSearch={handleSearch} />
Patch Changes
  • Fixed syntax highlighting in Studio code blocks. Shiki tokens now render with per-token colors (keywords, strings, identifiers) instead of flat monochrome text, and Code Mode execute_typescript programs display as a formatted, highlighted TypeScript block instead of a one-line JSON string. (#17324)

  • Improved studio load time by only bundling the CodeMirror and Shiki languages the editor actually uses, and removed a redundant TypeScript pass from the playground-ui build. (#17406)

  • Improved RadioGroup styling with neutral selected states, cleaner focus outlines, and surface-aware disabled states. (#17401)

  • Added a DataPanel.SectionHeading component for small-caps section labels (with an optional leading icon) inside a DataPanel.Content. DataCodeSection now renders through it, and DataPanel.Header hides its bottom border when the panel is collapsed (header-only) so an empty panel no longer shows a stray divider. (#17464)

    <DataPanel.SectionHeading icon={<FileInputIcon />}>Input</DataPanel.SectionHeading>
  • Pointer drags inside the SideDialog body now select text reliably instead of fighting with the close-swipe gesture. The popup chrome (header, edges) still closes the drawer on drag. (#16959)

    Drawer composition

    DrawerContent is now the shadcn-style opinionated bundle (DrawerPortal + DrawerBackdrop + DrawerViewport + DrawerPopup, with a handle bar on top/bottom-anchored drawers and a fade-out when a nested drawer covers the parent). Most drawers can now be written as:

    <Drawer>
      <DrawerTrigger>…</DrawerTrigger>
      <DrawerContent>
        <DrawerHeader>…</DrawerHeader>
        <DrawerBody>…</DrawerBody>
      </DrawerContent>
    </Drawer>

    The low-level primitives (DrawerPortal, DrawerBackdrop, DrawerViewport, DrawerPopup) remain exported for drawers that need a custom portal target, non-modal page behavior, or chrome outside the popup (see the SwipeToOpen and NonModal Storybook examples).

    Base UI's text-selectable region (the Drawer.Content part — pointer drags inside it select text instead of closing the drawer) is now exported as DrawerInteractive. Migration:

    // Before
    import { DrawerContent } from '@mastra/playground-ui';
    <DrawerContent render={<div>...</div>} />;
    
    // After
    import { DrawerInteractive } from '@mastra/playground-ui';
    <DrawerInteractive render={<div>...</div>} />;
  • Removed the unused ElementSelect export from @mastra/playground-ui. Use the Select primitives instead. (#17417)

    // Before
    import { ElementSelect } from '@mastra/playground-ui';
    
    <ElementSelect name="status" value={status} onChange={setStatus} options={['Draft', 'Published']} />;
    
    // After
    import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@mastra/playground-ui';
    
    <Select name="status" value={status} onValueChange={setStatus}>
      <SelectTrigger>
        <SelectValue placeholder="Select..." />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="draft">Draft</SelectItem>
        <SelectItem value="published">Published</SelectItem>
      </SelectContent>
    </Select>;
  • Changed Spinner to render the new compact loader by default and added variant="pulse" for longer data-loading states. Removed the color prop so the loader defaults to the neutral text token and color overrides go through className. (#17455)

    Migration note

    Before:

    <Spinner color={Colors.neutral3} />

    After:

    <Spinner className="text-neutral3" />
    <Spinner variant="pulse" className="text-neutral1" />
  • Fixed dropdowns, menus, comboboxes, and popovers being unclickable when opened inside a SideDialog (for example the dataset selector in the "Save as Dataset Item" panel on the Traces tab). These popups now render inside the dialog so they stay interactive within the modal drawer. (#17479)

  • Agent Builder starter agents now use the admin-configured default model when the model policy has one set. Previously, the starter ignored the admin default and always picked the first entry from the picker allowlist, which surfaced as "default model gets over-written by agent builder" on agents created from starter cards or the freeform prompt. (#17424)

    When no admin default is set, behavior is unchanged: the starter falls back to the first allowed model, then to the hardcoded fallback.

  • Improved @mastra/playground-ui stability by removing legacy runtime UI dependencies without changing SideDialog, MainSidebar, or accessibility behavior. Nested SideDialog levels now stack consistently, so multi-level flows behave predictably. (#16959)

  • Added an is404NotFoundError helper to detect 404 Not Found responses from the Mastra client, alongside the existing is401UnauthorizedError and is403ForbiddenError helpers. Use it to show a clear not-found state when a resource no longer exists. (#17460)

    import { is404NotFoundError } from '@mastra/playground-ui';
    
    try {
      await client.getDataset(id);
    } catch (error) {
      if (is404NotFoundError(error)) {
        // show a not-found state instead of a generic error
      }
    }
  • Improved Checkbox styling with neutral selected states, cleaner focus outlines, and smoother state transitions. (#17400)

  • Improved switch focus, disabled, and motion states. (#17416)

@mastra/react@0.4.3
Patch Changes
  • Fixed canonical user signal echoes so messages sent through the agent-signals path appear in chat history when they move from pending to active. (#17309)

  • Separated thread subscription cleanup from active-run aborts so closing or switching a listener only unsubscribes that listener, while explicit cancel still aborts the active run. (#17310)

  • Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)

    await agent.sendToolApproval({
      resourceId: 'user-123',
      threadId: 'thread-123',
      toolCallId: 'tool-call-123',
      approved: true,
    });
  • Enabled Studio via the CLI and deployers to use agent signal subscriptions by default while preserving MASTRA_AGENT_SIGNALS=false, enableThreadSignals: false, and explicit legacy Stream as opt-outs. The React useChat() hook remains opt-in for SDK consumers via enableThreadSignals: true. (#17313)

@mastra/schema-compat@1.2.11
Patch Changes
  • Fixed Gemini REST tool calls failing for z.discriminatedUnion, z.lazy, and z.tuple inputs. GoogleSchemaCompatLayer now rewrites JSON Schema 2020-12 keywords into the OpenAPI 3.0 Schema Object subset that Gemini expects: oneOfanyOf, constenum, tuple items: [array]items: { anyOf: [...] }, nullable anyOf collapse, $ref inlining with recursive schema support, and stripping of $schema/additionalProperties/propertyNames. Fixes #17057. (#17179)

  • Fixed Zod 4 schemas with .transform() producing the wrong JSON Schema for structured output and tool calling. The generated schema now describes the pre-transform input the model must produce instead of the post-transform output, so a field like z.string().transform(JSON.parse) is advertised as a string rather than string | number | boolean | null. (#17357)

@mastra/server@1.38.0
Minor Changes
  • Added an agent override export API and server-side ownership enforcement. (#17228)

    The server and client now expose an agent override export endpoint so Studio can download an agent's overrides as JSON for review or commit workflows. Saves are enforced server-side against each agent's editor config, so only owned fields (instructions, tools, or tool descriptions) are persisted and fields locked by the editor config are stripped.

    The system packages response also reports the active editor source so clients can render the correct editing experience.

  • Added isZodError helper and ZodErrorLike type, exported from @mastra/server/server-adapter (and @mastra/server/handlers/error). Use these instead of instanceof ZodError when handling validation errors in custom server adapters or middleware so the check survives consumers that pin a different zod package instance than the one bundled with @mastra/server. (#17172)

    import { isZodError } from '@mastra/server/server-adapter';
    
    try {
      await schema.parseAsync(input);
    } catch (error) {
      if (isZodError(error)) {
        // structural check — works across zod v3/v4 realms
        return formatValidationError(error);
      }
      throw error;
    }

    Underpins the fix for #17167.

  • Add fire-and-forget workflow resume that returns immediately with { runId } without awaiting the run output. (#17230)

    For @mastra/inngest, this skips the getRunOutput() polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.

    • Run.resumeAsync() added to core: dispatches the resume in the background and returns { runId } immediately. Engines that poll for results (Inngest) override it to skip polling entirely.
    • InngestRun.resumeAsync() sends the resume event and returns { runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.
    • New POST /workflows/:workflowId/resume-no-wait and POST /agent-builder/:actionId/resume-no-wait routes return { runId } immediately.
    • New client SDK run.resumeNoWait() resolves with { runId }.

    The existing resumeAsync() client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(v2) comments documenting this consolidation.

  • Add experimental HTTP message routes for agent threads. Servers now expose POST /agents/:agentId/send-message and POST /agents/:agentId/queue-message for message-first input while keeping /agents/:agentId/signals available for lower-level signals and compatibility. (#17237)

  • Added a PATCH /tool-providers/:providerId/connections/:connectionId endpoint and matching client SDK method so authors can rename a connection's display label after creation. (#17249)

    Rename a connection from the client SDK

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: '…' });
    
    await client.getToolProvider('composio').updateConnection('auth_abc', {
      label: 'Work inbox',
    });

    Pass label: null (or an empty string) to clear the existing label. Labels are 1–32 characters and accept letters, digits, spaces, underscores, and hyphens ([A-Za-z0-9 _-]+).

    Ownership enforced server-side

    Non-owners get a 403 unless they hold tool-providers:admin. Shared connections are reachable by every author. The label is stored on the connection row itself, so the rename flows to every agent that pins the connection — no per-agent edit needed.

  • Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)

    Stored agents can now pin OAuth connections per toolkit

    A stored agent's config accepts a new toolProviders shape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.

    {
      toolProviders: {
        composio: {
          connections: {
            gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }],
          },
          tools: {
            GMAIL_FETCH_EMAILS: { toolkit: 'gmail' },
          },
        },
      },
    }

    New client SDK surface for managing connections

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: '…' });
    const composio = client.toolProvider('composio');
    
    const { items } = await composio.listConnections({ toolkit: 'gmail' });
    await composio.disconnectConnection('auth_abc');

    New ToolProvider interface for custom providers

    Providers implement a VNext surface (listToolkitsVNext, listToolsVNext, resolveToolsVNext) plus the auth round-trip (authorize, getAuthStatus, listConnections, disconnectConnection, listConnectionFields, health). The Composio provider has been rewritten on this surface; the older catalog methods remain as @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue to work unchanged. The Studio/Builder UI ships separately.

Patch Changes
  • Separated thread subscription cleanup from active-run aborts so closing or switching a listener only unsubscribes that listener, while explicit cancel still aborts the active run. (#17310)

  • Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)

  • Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)

    await agent.sendToolApproval({
      resourceId: 'user-123',
      threadId: 'thread-123',
      toolCallId: 'tool-call-123',
      approved: true,
    });
  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

  • Fix custom providers appearing twice in Studio's provider selector. (#17441)

    In dev mode, GatewayRegistry registers custom gateways so PROVIDER_REGISTRY already contains them with prefixed keys (e.g. "melioffice/genai"). The /agents/providers handler then called gateway.fetchProviders() again and re-added them, but the live call returns raw unprefixed keys (e.g. "genai") which after prefixing produce the same key — however in some cases the keys differed, causing both entries to appear in the UI.

    The fix skips adding a provider from the live fetchProviders() call if it is already present in allProviders from PROVIDER_REGISTRY.

  • Fixed a startup crash that affected deployments pinning an older @mastra/core version. The server now boots successfully even when the installed @mastra/core doesn't include the Agent Builder runtime. (#17382)

    Symptom

    Deployed servers failed to start with ERR_MODULE_NOT_FOUND pointing at @mastra/core/dist/agent-builder/ee/index.js, even on apps that never used the Agent Builder.

    What changed

    The server no longer eagerly loads the Agent Builder runtime at boot. It's loaded on demand, only when a request actually needs it on an app that has configured a MastraEditor with builder support.

    No application code changes required.

  • Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. (#17165)

  • Stored agent and skill POST, PATCH, and skill publish responses now include isFavorited, matching GET behavior. Clients can read favorite status from write responses without an extra GET request. (#17246)

    Under auth-off, write responses also omit favoriteCount to match GET, so the response shape is consistent across all single-entity endpoints.

  • Fixed memory status reporting for agents that do not support Mastra memory. The memory status endpoint now preserves storage fallback for regular agents while allowing integrations to opt out of memory UI. (#16906)

  • Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)

  • Hardened v1 ToolProvider connection routes and SDK forwarding. (#17248)

    Fail closed on unknown connectionId

    DELETE /tool-providers/:providerId/connections/:connectionId and GET …/usage now return 403 when storage is configured but no persisted row matches the supplied connectionId and the caller isn't an admin. Previously these routes fell through to the caller's own authorId, which let non-admin callers probe (and trigger provider-side revokeConnection for) IDs that didn't belong to them.

    Aligned authorize label validation with stored label rules

    POST /tool-providers/:providerId/authorize now enforces the same label rules the stored toolProviders config uses (min(1), max(32), /^[A-Za-z0-9 _-]+$/). Labels that pass authorize are now guaranteed to pass downstream stored-agent validation.

    SDK forwards toolkit on connection-scoped operations

    @mastra/client-js:

    await client.toolProviders.get('composio').disconnectConnection('ca_xxx', {
      toolkit: 'gmail',
      force: true,
    });
    
    const usage = await client.toolProviders.get('composio').getConnectionUsage('ca_xxx', { toolkit: 'gmail' });

    disconnectConnection now forwards params.toolkit (previously dropped) and getConnectionUsage accepts an optional { toolkit } parameter so toolkit-scoped connection lookups disambiguate correctly server-side.

  • Lazy-load @mastra/core/tool-provider inside the tool-provider handler so (#17248) @mastra/server evaluates under any peer-compatible @mastra/core (peer floor remains >=1.34.0-0). The handler no longer imports SHARED_BUCKET_ID or UnknownToolProviderError at module load — SHARED_BUCKET_ID is mirrored as a local literal (verified in lockstep with core via a regression test), and UnknownToolProviderError is resolved via a cached await import(...) inside resolveProvider so the real class identity is preserved for instanceof.

    OSS users running Mastra without a MastraEditor are unaffected: every tool-provider route still short-circuits with HTTP 500 "Editor is not configured" via requireEditor(...) before any core/tool-provider value is touched. Users with a MastraEditor already pull a compatible core transitively through @mastra/editor. Tool-provider routes require the new core exports at request time only — older cores surface a clear runtime error instead of crashing the server at boot.

  • Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)

    Better visibility into connection-scope misconfiguration

    When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.

    One bad toolkit no longer disables sibling providers

    If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.

  • Workflows now support an optional metadata field for attaching custom key-value data such as displayName, author, or category. Metadata is preserved through serialization and returned in workflow info API responses. (#17355)

    // Define a workflow with metadata
    const myWorkflow = createWorkflow({
      id: 'data-processing',
      metadata: {
        displayName: 'Data Processing Pipeline',
    
        category: 'ETL',
      },
      inputSchema: z.object({ ... }),
      outputSchema: z.object({ ... }),
    });
    
    // Retrieve workflow info with metadata via the Mastra Server API
    const workflowInfo = await mastraClient.getWorkflow('data-processing');
    console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline"
@mastra/spanner@1.1.0
Minor Changes
  • Added five new storage domains to the Google Cloud Spanner adapter: workspaces, datasets, experiments, favorites, and channels. The Spanner store now covers the full set of editor and evaluation domains. (#17472)

    What's new

    • Datasets versioned dataset items with historical snapshots and as-of reads (time-travel reads, per-item history, batched insert/delete).
    • Experiments with per-item results, review-status aggregation, and pagination.
    • Workspaces with versioned configuration snapshots (filesystem, sandbox, mounts, search, skills, tools), mirroring the existing thin-record + versions pattern.
    • Favorites for agents and skills, maintaining a denormalized favoriteCount on the parent record atomically.
    • Channels for multi-platform installations and per-platform configuration.

    Enabling favorites also adds favorited-first ordering and favoritedOnly / entityIds filtering to agents.list() and skills.list(), and surfaces favoriteCount on skill records.

    const storage = new SpannerStore({
      id: 'spanner-storage',
      projectId: process.env.SPANNER_PROJECT_ID!,
      instanceId: process.env.SPANNER_INSTANCE_ID!,
      databaseId: process.env.SPANNER_DATABASE_ID!,
    });
    
    const datasets = await storage.getStore('datasets');
    const ds = await datasets?.createDataset({ name: 'eval-set' });
    
    const favorites = await storage.getStore('favorites');
    await favorites?.favorite({ userId: 'u1', entityType: 'agent', entityId: 'agent-1' });
Patch Changes
@mastra/vercel@0.2.0
Minor Changes
  • Added VercelMicroVMSandbox, a new workspace sandbox provider backed by the Vercel Sandbox ephemeral Firecracker MicroVM product (@vercel/sandbox). It provides a persistent in-session filesystem, sudo access, exposed ports, command execution, and background processes via the process manager. This is distinct from the existing VercelSandbox, which runs commands as stateless Vercel serverless Functions and is unchanged. Also exports VercelMicroVMProcessManager and the vercelMicroVMSandboxProvider editor descriptor (provider id vercel-microvm). Closes #16704. (#17332)

    import { Workspace } from '@mastra/core/workspace';
    import { VercelMicroVMSandbox } from '@mastra/vercel';
    
    const workspace = new Workspace({
      sandbox: new VercelMicroVMSandbox({
        runtime: 'node24',
        timeout: 600_000,
        ports: [3000],
      }),
    });
    
    await workspace.init();
    const result = await workspace.sandbox.executeCommand('node', ['--version']);
Patch Changes
@mastra/voice-aws-nova-sonic@0.1.2
Patch Changes
  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/voice-azure@0.11.1
Patch Changes
  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/voice-cloudflare@0.12.2
Patch Changes
  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/voice-deepgram@0.12.1
Patch Changes
  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/voice-elevenlabs@0.12.1
Patch Changes
  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/voice-gladia@0.12.1
Patch Changes
  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

[@mastra/voice-google@0.12.1](https://github.com/mastra-ai/mas
@mastra/core@1.36.0

Highlights

Stored Entity HTTP APIs (Agents/Skills/Workspaces) + Builder/Registry Introspection

@mastra/server adds a full HTTP surface for stored entities (CRUD, versioning, activation/restore) including favorites metadata, plus builder introspection endpoints and an external skill-registry proxy that Studio and @mastra/client-js can consume.

Browser Automation & Screencast Session Probing (incl. Stored Agents)

New browser-provider/editor primitives wire browser automation into the editor, while deployers/adapters add a probeable GET /agents/:agentId/browser/session endpoint and client SDK methods (agent.browserSession() / agent.closeBrowser()) so UIs can safely open screencast WebSockets only when supported—now also working for stored agents via editor fallback lookup.

Delta Polling for Observability List APIs (Server/Client/Stores/UI)

Observability list endpoints now support mode: 'delta' with cursors across core + @mastra/server, with store support in DuckDB/ClickHouse (and in-memory alignment) and typed client support; Playground UI’s traces list updates live via delta polling instead of full-page refetches.

Harder Authorization Defaults: FGA Coverage + ANY-of Permissions + Safer Context Keys

FGA execution checks are now enforced consistently across agents/tools/memory/workflows (requiring authenticated requestContext when invoking protected APIs), and both FGA checks and route permissions accept permission arrays (ANY-of semantics); server auth middleware also writes namespaced request-context keys to avoid collisions.

Convex Production Upgrades: Native Vector Search + Durable Server Cache

@mastra/convex adds ConvexNativeVector backed by schema vector indexes/ctx.vectorSearch for production-grade retrieval, plus ConvexServerCache to persist durable stream replay and response-cache state in Convex.

Breaking Changes
  • AgentSignalContents narrowed to string | (TextPart | FilePart)[] (no longer accepts wrapped BaseMessageListInput shapes); update agent.sendSignal callers accordingly.
  • @mastra/playground-ui Button variants changed: cta/contrast/link removed in favor of default/primary/outline/ghost; ButtonWithTooltip removed (use Button with tooltip prop).
  • PopoverContent no longer forwards onOpenAutoFocus/onCloseAutoFocus (use initialFocus/finalFocus instead).

Changelog

@mastra/core@1.36.0
Minor Changes
  • Added activateAfterIdle: "auto" for Observational Memory early activation. (#16663)

    Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects providerOptions.openai.promptCacheRetention when available.

    const memory = new Memory({
      options: {
        observationalMemory: {
          model: 'google/gemini-2.5-flash',
          activateAfterIdle: 'auto',
          activateOnProviderChange: true,
        },
      },
    });
  • Added support for permission arrays in FGA checks and route configuration. When an array is provided, the user needs any one of the listed permissions (logical OR). (#16605)

    Affected types

    • FGACheckParams.permission
    • FGARouteConfig.permission
    • FGARouteInfo.requiresPermission
    • FGADeniedError.permission
    • CheckFGAOptions.permission

    Single-permission usage continues to work unchanged.

    // Before — single permission only
    await fga.check({
      resource: { type: 'agent', id: 'abc' },
      permission: 'agents:read',
    });
    
    // After — single permission or array (ANY-of)
    await fga.check({
      resource: { type: 'agent', id: 'abc' },
      permission: ['agents:read', 'agents:execute'],
    });
  • Added consistent FGA execution checks across agents, tools, memory, and workflows to prevent unauthenticated executions when FGA is configured. Pass an authenticated user through requestContext when invoking protected APIs directly: (#16651)

    const requestContext = new RequestContext();
    requestContext.set('user', user);
    
    await agent.generate('Summarize this thread', {
      requestContext,
    });
  • Added the EditorFavorite* types and an optional favorites namespace on IMastraEditor so editor implementations can expose favoriting of stored agents and skills. (#16749)

    import type {
      IMastraEditor,
      IEditorFavoritesNamespace,
      EditorFavoriteTargetInput,
      EditorFavoriteToggleResult,
    } from '@mastra/core/editor';
    
    interface IMastraEditor {
      // ...existing members...
      favorites?: IEditorFavoritesNamespace;
    }

    The favorites field is optional — existing implementations of IMastraEditor continue to work unchanged. @mastra/editor ships a default EditorFavoritesNamespace that wires this up against the storage favorites domain.

    Also renamed AgentFeatures.stars to AgentFeatures.favorites in @mastra/core/agent-builder/ee so the feature flag aligns with the storage column (favoriteCount), HTTP routes (/favorite), and the editor favorites namespace. The field had no functional consumers, so this is a name-only change.

  • Enterprise edition now automatically captures PostHog telemetry for EE license checks and feature usage, including license validation status, RBAC access resolution, FGA authorization calls, and EE feature invocation metadata. Telemetry is enabled by default for EE customers and can be disabled with MASTRA_TELEMETRY_DISABLED=1; community users are unaffected. (#16660)

  • Added new editor configuration primitives for browser providers, agent builder integration, and stored-agent visibility. (#16778)

    New: BrowserProvider interface

    Implement a browser provider to expose browser automation tools to agents via the editor. Each provider declares an id, name, and config schema, then returns a MastraBrowser instance from createBrowser.

    import type { BrowserProvider } from '@mastra/core/editor';
    
    const myProvider: BrowserProvider = {
      id: 'my-browser',
      name: 'My Browser',
      description: 'Custom browser automation',
      configSchema: z.object({ apiKey: z.string() }),
      createBrowser: async config => {
        return createMyBrowser(config.apiKey);
      },
    };

    New: MastraEditorConfig.browsers and .builder

    Wire browser providers and agent-builder options into the editor:

    new MastraEditor({
      browsers: { 'my-browser': myProvider },
      builder: { features: { agent: { favorites: true } } },
    });

    New: visibility on updateAgentMeta

    Set an agent's visibility (private or public) through the editor namespace:

    await editor.agent.updateAgentMeta('agent-id', { visibility: 'public' });
  • publishSkill now returns the full skill file tree so consumers can persist the UI-facing tree alongside storage blobs without re-walking the source. (#16666)

    import { publishSkill } from '@mastra/core/workspace';
    
    const result = await publishSkill({ workspace, skillId, source });
    
    // New: nested tree of folders + files; binary content base64-encoded.
    for (const node of result.files) {
      console.log(node.type, node.path);
    }

    Also added two optional capability methods to IMastraEditor for server-side gating of builder-aware behavior:

    interface IMastraEditor {
      // ...existing members...
      hasEnabledBuilderConfig?(): boolean;
      resolveBuilder?(): Promise<IAgentBuilder | undefined>;
    }

    Both methods are optional — existing implementations of IMastraEditor continue to work unchanged. Servers that consume them treat undefined / missing implementation as "no builder configured."

  • Added route-specific CORS configuration so credentialed cross-origin access can be limited to selected custom routes and channel webhooks. (#16689)

    registerApiRoute('/customer-webhook', {
      method: 'POST',
      cors: {
        origin: ['https://customer-saas.example'],
        credentials: true,
      },
      handler: async c => c.json({ ok: true }),
    });
    new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: '...',
      model,
      channels: {
        adapters: {
          web: {
            adapter: createWebAdapter(),
            cors: {
              origin: ['https://customer-saas.example'],
              credentials: true,
            },
          },
        },
      },
    });

    Use server.cors for one global CORS policy across the server:

    new Mastra({
      server: {
        cors: {
          origin: '*',
        },
      },
    });
  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before: { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After: { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

  • publishSkillFromSource() (and collectSkillForPublish()) now return a files field containing the full skill source as a tree of StorageSkillFileNode entries with base64-encoded blob content — handy for storing a UI-facing copy of a skill alongside its content-addressable tree: (#16673)

    const { snapshot, tree, files } = await publishSkillFromSource({ source });
    // files: StorageSkillFileNode[] — name, mimeType, base64 content per node

    Existing callers that only destructure { snapshot, tree } are unaffected; the field is additive.

    Also adds parseSkillSnapshotFromFiles() for parsing skill snapshot frontmatter from a flat file list (used by the registry install flow):

    import { parseSkillSnapshotFromFiles, type SkillSnapshotFile } from '@mastra/core/workspace';
    
    const files: SkillSnapshotFile[] = [{ path: 'SKILL.md', content: '...' }, ...];
    const snapshot = parseSkillSnapshotFromFiles(files);
  • Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. (#16632)

Patch Changes
  • Update provider registry and model documentation with latest models and providers (452036a)

  • Fixed task_update to auto-demote previously in_progress tasks instead of returning an error when moving another task to in_progress. (#16843)

  • Fixed durable agents to honor activeTools when streaming. (#16646)

  • Fixed type error when a tool calls suspend(...) inside execute while also declaring an outputSchema. The execute return type now allows void in addition to the declared output shape, so the idiomatic return await suspend(...) pattern type-checks correctly. (#16799)

  • Fixed a startup bug in MastraCompositeStore.init() when using default or editor. (#16786)

    Before this fix, the composite initialized inner domains directly and could skip parent store initialization. That could skip adapter setup steps and cause missing-table errors during startup (most visibly with LibSQLStore on a local file).

    Now, MastraCompositeStore.init() runs parent default and editor initialization first, then initializes only domains not already covered by those parents. This preserves adapter-specific initialization behavior and prevents startup races.

    Fixes #16782.

  • Fixed CompositeAuth incorrectly advertising SSO, session, and user provider capabilities when no inner provider supports them. Studio would show an SSO login button even when no provider had SSO configured, leading to 401 errors on login attempts. The duck-typing check now verifies that interface methods are actual functions rather than just present on the prototype chain. (#16664)

  • Hide internal workflow spans from Mastra-owned plumbing in exported traces. (#16631)

  • Channels now serialize messages per thread to keep conversations in order, and the tool approval flow is fixed end-to-end: (#16517)

    • Messages arriving while the agent is busy are delivered into the running agent loop instead of starting a new, conflicting stream on the same thread. Each Mastra thread shares one subscription, and channel/author facts (platform, message id, author name) are surfaced on the stored message under providerMetadata.mastra.channels.<platform>.
    • Tool approval flow: approving now drains the resumed run so the card is updated with the tool result and any follow-up assistant text is posted. Denying now resumes the run via declineToolCall instead of leaving it suspended. agent.subscribeToThread() consumers also receive chunks from resumed runs (the subscription used to drop the second registration for a resumed run that kept its original runId).
    • The original ChannelConfig is now exposed via the new AgentChannels.channelConfig field so channel providers can merge with existing adapters instead of replacing them.
    • Bumped chat to ^4.29.0.
  • Added the internalUsage?: UsageStats field to AIBaseAttributes, so any span type can carry token usage rolled up from internal descendant spans. Populated automatically by @mastra/observability when an internal MODEL_GENERATION ends inside a non-internal ancestor. (#16434)

  • Fixed trajectory scorers so tool calls stored only in V2 content.parts are included in extracted eval steps. (#15439)

  • Fixed thread metadata updates to merge with existing fields instead of replacing them. Previously, updating a thread's metadata would silently drop any fields not included in the update. Now existing metadata fields are preserved when updating. (#16846)

    Fixed MockMemory working memory tool to support partial JSON updates when using schema-based working memory. Previously, sending a partial update would overwrite all existing data. Now for schema-based configs, unchanged fields are preserved automatically (matching @mastra/memory behavior).

    Fixed MockMemory constructor to preserve workingMemory config options (like schema) when enableWorkingMemory is true.

  • Improved MastraCode quiet mode so terminal sessions are easier to scan. (#16771)

    • Quiet mode is now the default for new installs, and existing classic users get a one-time prompt to choose whether to enable it.
    • Added compact tool previews with a configurable preview-line limit, including an option to hide previews.
    • Improved repeated tool-call rendering, path continuation handling, task wrapping, shell/error previews, and spacing between tools, messages, plans, and completed subagents.
    • Added edited line ranges to workspace edit results so tool UIs can show where replacements happened.
  • Remove hardcoded /api/ prefix check from registerApiRoute(). The check incorrectly rejected custom routes starting with /api/ even when users configured a different apiPrefix. Reserved-path validation is already handled at the server adapter level using the actual configured prefix. (#16859)

  • Fixed infinite recursion in RequestContext.toJSON() when multiple (#16686) RequestContext instances reference each other through stored values. Previously, serializing such cross-context cycles would cause a CPU hang. Cyclic references are now detected and omitted from the serialized output, consistent with how circular references within a single context are handled.

  • Fixed crash in CacheKeyGenerator.fromAIV4Part when a tool-invocation part has undefined toolInvocation. This can happen when observational memory seals a partially-streamed assistant message. Also guarded MessageMerger against the same condition. (#16773)

  • Agent signals can now coordinate active thread runs across agents that share a PubSub instance, so thread subscribers and signal senders can observe the same run instead of being limited to one runtime instance. (#16665)

    import { Agent } from '@mastra/core/agent';
    import { EventEmitterPubSub } from '@mastra/core/events';
    
    const pubsub = new EventEmitterPubSub();
    const agent = new Agent({
      id: 'agent',
      name: 'Agent',
      instructions: 'Help the user',
      model,
      pubsub,
    });
  • Exposed formatSkillActivation(skill) from @mastra/core/workspace. It returns the activation payload — instructions plus references, scripts, and assets listings — that the built-in skill tool uses, so callers (e.g. an explicit /skill/<name> slash command) can produce the same output without duplicating the formatting logic. (#16618)

    Also preserves the user-invocable skill frontmatter field in workspace skill metadata.

    import { formatSkillActivation } from '@mastra/core/workspace';
    
    const content = formatSkillActivation(skill);
  • Fixed sub-agent streams so nested tool input progress is emitted while tool arguments are still being generated. This lets UIs show delegated agents preparing tool calls before the final tool input is available. Fixes #16422. (#16553)

  • Fixed backgroundTasks: { enabled: true } silently dispatching foreground-only tools to the background. (#16792)

    Previously, enabling backgroundTasks on the Mastra instance injected a system prompt into every agent that taught the LLM to flip any tool to background by passing _background: { enabled: true } in its arguments, and the resolver honored that override unconditionally. Models would readily do this for short, deterministic tools — a plain calculator could return "Background task started…" instead of { result: 42 }, breaking agent.generate() / agent.stream() for tool-using flows.

    The LLM _background override is now treated as a modifier on tools the developer has opted in at the tool or agent layer, not a standalone opt-in. If a tool hasn't been opted in via tool-level background: { enabled: true } or agent-level backgroundTasks: { tools: { … } } (or tools: 'all'), _background.enabled: true from the model is ignored and the tool runs in the foreground. Opted-in tools continue to honor LLM overrides for enabled, timeoutMs, and maxRetries as documented.

    Fixes https://github.com/mastra-ai/mastra/issues/16783

  • Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. (#16805)

    • Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: (status, next_fire_at) on mastra_schedules and (schedule_id, actual_fire_at) on mastra_schedule_triggers. Without these the scheduler performed a full sequential scan on every tick.
    • The scheduler tick loop is now only started when at least one workflow declares a schedule (or scheduler.enabled is set explicitly), so deployments without scheduled workflows no longer poll the database at all.
    • The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever.
    • The scheduler is now lazily initialized inside startWorkers(). Accessing mastra.scheduler before startWorkers() runs throws a descriptive error instead of returning a half-initialized instance.
  • Fixed Mastra getting stuck after a storage startup failure. Previously, if storage couldn't start up (for example, because the database was briefly unreachable), Mastra would keep returning the same error for every operation until the process was restarted. Now the next storage operation tries to start storage again, so brief outages recover on their own. Storage startup failures are also logged, so the problem is visible even when a later retry succeeds. (#16427)

  • Restore MastraCode local command execution to inherit parent environment variables while redacting env-shaped and secret-looking workspace trace data. (#16691)

  • Added a Unix socket PubSub transport and wired the Mastra Code TUI through a per-resource socket so local sessions can coordinate thread streams across processes. Programmatic createMastraCode usage remains opt-in: (#16669)

    await createMastraCode({ unixSocketPubSub: true });
  • Fix in-memory observability storage to match the contract validated against DuckDB/ClickHouse vNext adapters. (#16808)

    Previously, when running Mastra with the default in-memory storage, several observability operations behaved differently than they would against a production database:

    • getSpans threw 'This storage provider does not support batch-fetching spans'. It now batch-fetches spans by id within a trace, enabling the optimized getBranch path on in-memory storage.
    • batchCreateLogs, batchCreateMetrics, createScore/batchCreateScores, createFeedback/batchCreateFeedback appended duplicate records on retry. They now upsert by id, preserving the cursor id so delta polling does not re-emit the record. This makes client retries safe.
    • Discovery operations (getEntityTypes, getEntityNames, getServiceNames, getEnvironments, getTags) only inspected spans. They now also scan logs and metrics, so dimensions emitted on those surfaces are surfaced in discovery results.
    • getMetricTimeSeries merged grouped series whose label values contained the | character (e.g. {segmentA: 'a', segmentB: 'b|c'} collided with {segmentA: 'a|b', segmentB: 'c'}). Series are now keyed on the original label tuple, so colliding display names remain distinct series.
@mastra/ai-sdk@1.4.3
Patch Changes
  • Fixed sub-agent streams so nested tool input progress is emitted while tool arguments are still being generated. This lets UIs show delegated agents preparing tool calls before the final tool input is available. Fixes #16422. (#16553)
@mastra/auth-okta@0.0.3
Patch Changes
  • Fix endpoint URL construction for Okta org authorization servers. (#15694)

    MastraAuthOkta concatenated /v1/authorize (and /token, /keys, /logout) directly onto OKTA_ISSUER. That yields the right endpoint for a custom authorization server (https://{domain}/oauth2/default.../oauth2/default/v1/authorize), but 404s on an Okta org authorization server (https://{domain}.../v1/authorize, whereas the real org endpoint is .../oauth2/v1/authorize).

    An internal endpointBase is now derived from the issuer — verbatim when it already contains /oauth2/, otherwise ${issuer}/oauth2 — and used for the authorize, token, keys, and logout URLs. JWT iss-claim validation still uses the raw issuer so token validation stays correct on both server types. Trailing slashes on the issuer are also normalized so OKTA_ISSUER=https://{domain}/ no longer produces .../oauth2//v1/....

@mastra/auth-workos@1.5.0
Minor Changes
  • FGA check() and require() now accept an array of permissions and short-circuit on the first one that resolves to allow (ANY-of semantics). Single-permission usage continues to work unchanged. (#16605)

    // Before — one permission per call
    await fgaProvider.check({
      user,
      resource: { type: 'agent', id: 'abc' },
      permission: 'agents:read',
    });
    
    // After — single permission or ANY-of array
    await fgaProvider.check({
      user,
      resource: { type: 'agent', id: 'abc' },
      permission: ['agents:read', 'agents:execute'],
    });

    When all permissions in the array are denied, the thrown FGADeniedError lists them as any of [a, b, c] in its message.

Patch Changes
@mastra/clickhouse@1.9.0
Minor Changes
  • Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. (#16632)
Patch Changes
  • Fixed duplicate entries in the ClickHouse v-next observability discovery endpoints — tags, services, environments, entities, metric names, and metric labels now return each value once. Existing deployments are reconciled automatically on next startup; no manual migration required. (#16798)
@mastra/client-js@1.20.0
Minor Changes
  • Added agent.browserSession(threadId?) and agent.closeBrowser(threadId?) to the Agent resource, plus a GetAgentBrowserSessionResponse type. (#16668)

    browserSession probes the server's browser session state before opening a screencast WebSocket, so the connection is only made when the server has screencast support installed and an active session exists for the thread. closeBrowser ends the agent's browser session (or a single thread's session if threadId is passed). Both methods go through the configured client baseUrl and apiPrefix, so they work with servers mounted under a non-default API prefix.

    const probe = await client.getAgent('my-agent').browserSession(threadId);
    if (probe.screencastAvailable && probe.hasSession) {
      // safe to open the screencast WebSocket
    }
    
    await client.getAgent('my-agent').closeBrowser(threadId);
  • Added typed client-side resources for the stored-entity HTTP surface so you no longer have to hand-roll fetch calls. (#16666)

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
    
    // List/get with favorite metadata
    const { items } = await client.storedAgents.list({ page: 1, perPage: 20 });
    const agent = await client.storedAgents.get(items[0].id);
    console.log(agent.favoriteCount, agent.isFavorited);
    
    // Favorite toggle
    await client.storedAgents.favorite(agent.id);
    await client.storedAgents.unfavorite(agent.id);
    
    // Versioning + publish
    const draft = await client.storedSkills.create({
      /* ... */
    });
    const published = await client.storedSkills.publish(draft.id);
    await client.storedSkills.restore(draft.id, { version: 1 });

    Also regenerated route-types.generated.ts to cover the new editor-builder introspection routes (/editor/builder/settings, /editor/builder/infrastructure) and the external skill-registry endpoints under /editor/builder/registries (list, search, popular, preview, install).

  • Added delta polling support for observability list endpoints. (#16632)

    const page = await client.observability.listTraces({
      mode: 'page',
      filters: { entityName: 'agent-1' },
    });
    
    const delta = await client.observability.listTraces({
      mode: 'delta',
      filters: { entityName: 'agent-1' },
      after: page.deltaCursor,
    });

    Use mode: 'delta' to fetch only new items after the last cursor.

    Page-mode responses include pagination and deltaCursor when delta polling is supported. Delta-mode responses include delta and do not include pagination.

    If you read these responses directly in typed code, note that pagination is only included in page mode.

  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before: { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After: { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

Patch Changes
  • Fixed provider and dataset item history response types to include fields returned by the API. (#16213)

  • Fixed an issue where recursive client-tool continuations after resumeStream (and resumeStreamUntilIdle) incorrectly re-hit the one-shot resume endpoint instead of falling back to the regular stream endpoint. The resume routes consume server-side resumeData and cannot be replayed, so client-tool continuations now route to /stream and /stream-until-idle respectively. (#16670)

  • Fixed the playground memory configuration display for agents using observationalMemory: true. (#16213)

@mastra/convex@1.1.0
Minor Changes
  • Added native Convex vector search support for production workloads. The new ConvexNativeVector adapter uses (#16729) Convex schema-defined vector indexes and ctx.vectorSearch instead of loading vectors through ConvexVector and scoring them in JavaScript.

    Define a native vector table in convex/schema.ts:

    import { defineSchema } from 'convex/server';
    import { defineMastraNativeVectorTable } from '@mastra/convex/schema';
    
    export default defineSchema({
      docs_vectors: defineMastraNativeVectorTable({
        dimensions: 1536,
      }),
    });

    Export the native vector handlers:

    import { mastraNativeVectorAction, mastraNativeVectorMutation, mastraNativeVectorQuery } from '@mastra/convex/server';
    
    export const query = mastraNativeVectorAction;
    export const read = mastraNativeVectorQuery;
    export const write = mastraNativeVectorMutation;

    Then configure ConvexNativeVector in your Mastra app:

    import { ConvexNativeVector } from '@mastra/convex';
    
    const vectorStore = new ConvexNativeVector({
      id: 'convex-native-vectors',
      deploymentUrl: process.env.CONVEX_URL!,
      adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
      indexes: {
        docs: {
          tableName: 'docs_vectors',
          vectorIndexName: 'by_embedding',
          dimension: 1536,
        },
      },
    });
  • Added ConvexServerCache so Convex-backed Mastra apps can keep durable stream replay and response cache state in Convex. (#16736)

    import { ConvexServerCache } from '@mastra/convex';
    
    const cache = new ConvexServerCache({
      deploymentUrl: process.env.CONVEX_URL!,
      adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
    });

    The package also exports the Convex cache schema tables and server mutation for mounting the cache handler in a Convex app. Existing Convex users who adopt the cache must add mastra_cache and mastra_cache_list_items to their Convex schema, mount the mastraCache handler, and deploy the schema update.

Patch Changes
@mastra/datadog@1.2.1
Patch Changes
  • Fixed Datadog LLM output tool calls so they render as structured tool call blocks. (#16789)

  • Fixed Datadog LLM span input formatting to remove empty user messages. (#16785)

@mastra/deployer@1.36.0
Minor Changes
  • Added route-specific CORS configuration so credentialed cross-origin access can be limited to selected custom routes and channel webhooks. (#16689)

    registerApiRoute('/customer-webhook', {
      method: 'POST',
      cors: {
        origin: ['https://customer-saas.example'],
        credentials: true,
      },
      handler: async c => c.json({ ok: true }),
    });
    new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: '...',
      model,
      channels: {
        adapters: {
          web: {
            adapter: createWebAdapter(),
            cors: {
              origin: ['https://customer-saas.example'],
              credentials: true,
            },
          },
        },
      },
    });

    Use server.cors for one global CORS policy across the server:

    new Mastra({
      server: {
        cors: {
          origin: '*',
        },
      },
    });
Patch Changes
  • Browser streaming now works for stored agents. The deployer's getToolset first checks the runtime agent registry, then falls back to the editor's stored-agent lookup, so agents created at runtime through the editor can stream browser sessions without being pre-registered in code. (#16778)

  • When browser streaming is unavailable (the ws and @hono/node-ws packages aren't installed, or the deployer is running in a serverless environment), the deployer now registers a fallback GET /api/agents/:agentId/browser/session route that returns { hasSession: false, screencastAvailable: false }. This lets clients detect that screencast won't work and skip the WebSocket upgrade instead of triggering a noisy reconnect loop. (#16668)

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/deployer-cloud@1.36.0
Patch Changes
  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)
@mastra/deployer-cloudflare@1.1.37
Patch Changes
  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)
@mastra/deployer-netlify@1.1.13
Patch Changes
  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)
@mastra/deployer-vercel@1.1.31
Patch Changes
  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)
@mastra/duckdb@1.4.0
Minor Changes
  • Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. (#16632)
Patch Changes
  • Fixed DuckDB observability storage so dev server restarts no longer fail when replaying cursor migrations. (#16803)
@mastra/editor@0.9.0
Minor Changes
  • EditorWorkspaceNamespace can now snapshot a live Workspace for persistence — the reverse of hydrateSnapshotToWorkspace(): (#16673)

    const snapshot = await editor.workspace.snapshotFromWorkspace(runtimeWorkspace);
    await editor.workspace.create({ id: 'my-workspace', ...snapshot });

    snapshotFromWorkspace() is async and awaits sandbox.getInfo() and filesystem.getInfo() so async providers like CompositeFilesystem keep their mount metadata in the stored config.

    Also includes two smaller behavioral fixes:

    • EditorSkillNamespace.publishSkillFromSource() stores the new files field on the published skill version and strips undefined keys before calling update() (libsql/pg adapters reject undefined bind arguments).
    • CrudEditorNamespace.clearCache(id) always calls onCacheEvict(id), even when the entity wasn't cached, so subclasses can clean up runtime registries for version-specific lookups that bypass the editor cache.
  • Added an editor.favorites namespace so direct (non-HTTP) callers can favorite, unfavorite, and query favorited stored agents/skills through the editor instance. (#16749)

    import { MastraEditor } from '@mastra/editor';
    
    const editor = new MastraEditor({ mastra });
    
    // Toggle
    await editor.favorites.favorite({ userId, entityType: 'agent', entityId });
    await editor.favorites.unfavorite({ userId, entityType: 'agent', entityId });
    
    // Lookups
    const isFav = await editor.favorites.isFavorited({ userId, entityType: 'agent', entityId });
    const favSet = await editor.favorites.isFavoritedBatch({ userId, entityType: 'agent', entityIds });
    const ids = await editor.favorites.listFavoritedIds({ userId, entityType: 'agent' });

    The namespace performs the storage mutation only — visibility and ownership enforcement still belong to the caller (the HTTP route handlers in @mastra/server already do this).

Patch Changes
@mastra/express@1.3.23
Patch Changes
  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/fastembed@1.1.0
Minor Changes
  • Replace the abandoned fastembed npm dependency with a maintained, vendored implementation. The public API and all embedding models remain unchanged — no migration needed. (#16772)
@mastra/fastify@1.3.23
Patch Changes
  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/hono@1.4.18
Patch Changes
  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

  • Added GET /agents/:agentId/browser/session endpoint (under the configured apiPrefix, default /api) that reports whether a screencast WebSocket should be opened for an agent and thread. Clients can probe this before upgrading to a WebSocket to avoid idle connections and reconnect storms. (#16668)

    curl "http://localhost:4111/api/agents/my-agent/browser/session?threadId=thread-1"
    # {"hasSession":true,"screencastAvailable":true}

    The response shape is { hasSession: boolean, screencastAvailable: true }. screencastAvailable is always true when this route is registered; the deployer registers a fallback that returns { hasSession: false, screencastAvailable: false } when browser streaming packages aren't installed, so clients can use the same probe in both cases.

    setupBrowserStream now accepts an optional apiPrefix so the probe and existing POST /agents/:agentId/browser/close routes are mounted under the same prefix as the rest of the server. The deployer wires this from mastra.getServer().apiPrefix automatically.

  • The Hono adapter now awaits getToolset calls in browser-stream routes, supporting deployers that resolve agents asynchronously (such as stored agents looked up via the editor). (#16778)

@mastra/koa@1.5.6
Patch Changes
  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/libsql@1.11.1
Patch Changes
  • Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. (#16805)
    • Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: (status, next_fire_at) on mastra_schedules and (schedule_id, actual_fire_at) on mastra_schedule_triggers. Without these the scheduler performed a full sequential scan on every tick.
    • The scheduler tick loop is now only started when at least one workflow declares a schedule (or scheduler.enabled is set explicitly), so deployments without scheduled workflows no longer poll the database at all.
    • The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever.
    • The scheduler is now lazily initialized inside startWorkers(). Accessing mastra.scheduler before startWorkers() runs throws a descriptive error instead of returning a half-initialized instance.
@mastra/mcp@1.8.0
Minor Changes
  • Added MCP tool annotations to the requireToolApproval context and exposed them on tools returned from listTools() / listToolsets(). (#16784)

    The requireToolApproval callback now receives the server-advertised annotations (title, readOnlyHint, destructiveHint, idempotentHint, openWorldHint) alongside toolName and args. This lets you write declarative approval policies instead of hardcoding tool name lists. Annotations are also propagated onto Mastra tools as tool.mcp.annotations so apps can render them in UI.

    Security caveat (per the MCP spec): annotations are hints, not guarantees. Clients MUST treat them as untrusted unless they come from a trusted server. Do not use annotations alone as a security boundary for servers you do not control — set requireToolApproval: true for those. When the server omits annotations entirely, this field is undefined, so policies can distinguish "no annotations" from "annotated as safe".

    import { MCPClient } from '@mastra/mcp';
    
    // Before — hardcoded tool name lists, server-specific
    const mcp = new MCPClient({
      servers: {
        github: {
          url: new URL('https://example.com/mcp'),
          requireToolApproval: ({ toolName }) => toolName === 'delete_repo',
        },
      },
    });
    
    // After — annotation-driven, works across any trusted MCP server
    const mcp = new MCPClient({
      servers: {
        github: {
          url: new URL('https://example.com/mcp'),
          requireToolApproval: ({ annotations }) => {
            if (!annotations) return true;
            if (annotations.readOnlyHint) return false;
            if (annotations.destructiveHint) return true;
            return false;
          },
        },
      },
    });
    
    // Annotations are also visible on tools returned by listTools()
    const tools = await mcp.listTools();
    for (const tool of Object.values(tools)) {
      console.log(tool.mcp?.annotations);
    }

    Closes #16766.

Patch Changes
  • Fixed an issue where OAuth token requests dropped client_id and client_secret for confidential clients. The provider previously shipped an empty addClientAuthentication method that satisfied the MCP SDK's existence check and short-circuited its default credential attachment, causing invalid_request errors on token exchange and refresh against confidential-client OAuth servers. The empty stub has been removed so the SDK's built-in client authentication runs again. See #16854. (#16862)

  • Close previous SSE transport before accepting a new connection in MCPServer.connectSSE(). Previously, sequential SSE connections to the same server would fail with "Already connected to a transport" because the underlying protocol was never closed when the previous client disconnected. (#16695)

@mastra/memory@1.19.0
Minor Changes
  • Added activateAfterIdle: "auto" for Observational Memory early activation. (#16663)

    Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects providerOptions.openai.promptCacheRetention when available.

    const memory = new Memory({
      options: {
        observationalMemory: {
          model: 'google/gemini-2.5-flash',
          activateAfterIdle: 'auto',
          activateOnProviderChange: true,
        },
      },
    });
  • Add observeAttachments to ObservationConfig for Observational Memory. Use it to control whether image/file parts on observed messages are forwarded to the Observer model alongside their placeholder text lines. (#16671)

    • true (default) — forward all attachments (existing behavior).
    • false — drop all attachments; placeholders still appear in the observer transcript.
    • string[] — allowlist of mimeType patterns, e.g. ['image/*'] or ['application/pdf']. Matching is case-insensitive and supports exact, type/*, and * patterns.

    Useful when the Observer model is text-only (some DeepSeek endpoints, etc.) while the main agent uses a multimodal model. Tool-result attachments are filtered with the same rule.

    new Memory({
      options: {
        observationalMemory: {
          observation: {
            model: 'deepseek/deepseek-chat', // text-only observer
            observeAttachments: false, // or e.g. ['image/*', 'application/pdf']
          },
        },
      },
    });
Patch Changes
  • feat(memory): start background buffering of unobserved messages when agent goes idle (#16694)

    In OM buffering mode, when the agent goes idle (turn.end()), any unobserved messages are now buffered in the background via a fire-and-forget buffer() call. This ensures observations are computed proactively rather than waiting for the next turn's step.prepare().

@mastra/nestjs@0.1.7
Patch Changes
  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)
@mastra/observability@1.13.0
Minor Changes
  • Roll up token usage from internal MODEL_GENERATION spans onto the closest exported ancestor span. When tracingPolicy.internal filters a model call out of exported traces, its tokens used to disappear from both the trace UI and metrics. Now: (#16434)

    • The visible ancestor (e.g. PROCESSOR_RUN, AGENT_RUN) gets an internalUsage attribute summing the tokens consumed by its hidden descendants — so a Mastra-owned processor that runs an internal agent (moderation, PII detector, structured output, etc.) shows its aggregate cost on the visible PROCESSOR_RUN span.
    • Token / cost metrics still emit, but are attributed via labels to the visible ancestor instead of the hidden agent.

    No action required — the rollup applies automatically whenever an internal MODEL_GENERATION ends inside a non-internal ancestor.

  • MastraStorageExporter now notifies custom exporters and connected integrations when it cannot persist observability events, such as unsupported storage or retries being exceeded. This matches the behavior already available on DefaultExporter. (#16755)

    Also fixed an issue in both exporters where span updates waiting on their parent span could be silently lost if a later flush in the same cycle failed.

Patch Changes
@mastra/pg@1.11.1
Patch Changes
  • Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. (#16805)
    • Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: (status, next_fire_at) on mastra_schedules and (schedule_id, actual_fire_at) on mastra_schedule_triggers. Without these the scheduler performed a full sequential scan on every tick.
    • The scheduler tick loop is now only started when at least one workflow declares a schedule (or scheduler.enabled is set explicitly), so deployments without scheduled workflows no longer poll the database at all.
    • The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever.
    • The scheduler is now lazily initialized inside startWorkers(). Accessing mastra.scheduler before startWorkers() runs throws a descriptive error instead of returning a half-initialized instance.
@mastra/playground-ui@29.0.0
Minor Changes
  • Added ContextMenu for right-click interactions. Supports submenus, checkbox and radio items, keyboard shortcuts, and a destructive variant for dangerous actions like delete. (#16791)

    import { ContextMenu } from '@mastra/playground-ui';
    
    <ContextMenu>
      <ContextMenu.Trigger className="…">Right click here</ContextMenu.Trigger>
      <ContextMenu.Content>
        <ContextMenu.Item>Rename</ContextMenu.Item>
        <ContextMenu.Item variant="destructive">Delete</ContextMenu.Item>
      </ContextMenu.Content>
    </ContextMenu>;
  • Removed ButtonWithTooltip from @mastra/playground-ui. Use Button with the tooltip prop instead. (#16719)

    Migration

    // before
    import { ButtonWithTooltip } from '@mastra/playground-ui';
    
    <ButtonWithTooltip tooltipContent="Search">
      <Search />
    </ButtonWithTooltip>;
    
    // after
    import { Button } from '@mastra/playground-ui';
    
    <Button tooltip="Search">
      <Search />
    </Button>;

    tooltip supports the same values as tooltipContent. Icon-only buttons that pass a string tooltip now also get it as their aria-label automatically, matching how labelled controls have always behaved. Pass an explicit aria-label to override.

  • Added a custom date range option to the Metrics page date picker. You can now filter metrics by an arbitrary start and end date and time, matching the Traces page, alongside the existing relative presets (last 24 hours, 3, 7, 14, and 30 days). (#16832)

    The selected range is reflected in the URL so it can be bookmarked or shared:

    /metrics?period=custom&dateFrom=2026-05-01T00:00:00.000Z&dateTo=2026-05-07T23:59:59.999Z
  • Refreshed Button + Card design system tokens. (#16769)

    Button variants (breaking): consolidated to default, primary, outline, ghost. The cta, contrast, and unused link variants have been removed. primary now uses a high-contrast neutral6 fill instead of surface4, so it reads clearly as the form submit action in both themes.

    // Before
    <Button variant="cta">Save</Button>
    <Button variant="contrast">Done</Button>
    <Button variant="link">Open</Button>
    
    // After
    <Button variant="primary">Save</Button>     // cta → primary (no brand green; theme-aware high contrast)
    <Button variant="primary">Done</Button>     // contrast → primary (same recipe, renamed)
    <Button as="a" href="…" variant="ghost">Open</Button>  // link → ghost (or plain <a> for inline text links)

    New tokens: --surface-overlay-soft and --surface-overlay-strong — alpha overlays of the opposite-theme color, used by SectionCard header strip and DashboardCard fill so cards read consistently on any surface.

    Other:

    • DashboardCard radius reduced to rounded-xl and padding tightened to px-4 py-3 for better grid density.
    • SectionCard wrapper no longer fills its background — header strip + border carry definition.
    • Dark surface2 / surface3 darkened slightly (16.84% → 16%, 19.13% → 18%) so the main frame reads as a distinct surface.
    • Dark border1 / border2 alphas bumped (6% → 7%, 10% → 11%) for closer dark/light parity.
    • Removed deprecated --section-card-* tokens and their @utility blocks.
Patch Changes
  • Added a destructive variant on DropdownMenu.Item to highlight dangerous actions like delete. (#16791)

    <DropdownMenu.Item variant="destructive">Delete project</DropdownMenu.Item>
  • PopoverContent no longer forwards the underlying library's auto-focus event handlers (onOpenAutoFocus, onCloseAutoFocus). To control focus when the popover opens or closes, use initialFocus and finalFocus. (#16791)

    // Before
    <PopoverContent onOpenAutoFocus={(e) => e.preventDefault()} />
    
    // After
    <PopoverContent initialFocus={false} />
  • Fixed a crash in filter menus with nested submenus (such as the Filter on the Agent review page) that showed "MenuPortal must be used within Menu". The submenu content now uses the design system's DropdownMenu.SubContent instead of the underlying library's portal directly. (#16829)

  • Fixed type definitions and shared UI component types for @mastra/playground-ui. (#16213)

  • AlertDialog's API and behavior are unchanged — asChild on AlertDialog.Trigger, and AlertDialog.Action and AlertDialog.Cancel, all work exactly as before. (Internally it now builds on Base UI primitives.) (#16824)

  • Moved the Collapsible component to Base UI, with a smoother height-based expand and collapse animation. The public API is unchanged — asChild on CollapsibleTrigger still works. (#16825)

  • Upgraded @base-ui/react to 1.5, making popups noticeably faster — components built on Base UI such as Tooltip, Popover, DropdownMenu and ContextMenu now open and close more quickly. (#16819)

  • Moved the Level icon from its own column into the Name column, next to the trace name, on the Observability traces list. (#16712)

  • The Traces list now updates live via delta polling. Previously the list was refetched every 10 seconds, replacing the whole page with no signal about what changed; now new traces appear within a few seconds of being created, with a brief highlight to draw attention. Status changes on already-visible rows (running → success / error) also propagate without intervention, and returning to the tab after being idle re-syncs from a fresh cursor. (#16727)

    New useTraces return fields

    • isRefetching — true while any meaningful refetch is in flight. Use it to drive a heartbeat indicator.
    • autoRefetch / setAutoRefetch — pause / resume all automatic polling so the consumer can render an opt-out toggle.
    • recentlyAddedKeysSet<string> of traceId:spanId for rows that just arrived via delta polling. Drives the temporary highlight in TracesListView.

    New polling config

    Every timing in the hook is tunable per-instance via a new polling option:

    import { useTraces, type TracesPollingConfig } from '@mastra/playground-ui';
    
    useTraces({
      filters,
      listMode,
      polling: {
        deltaPollIntervalMs: 10_000,
        idleGuardThresholdMs: 5 * 60_000,
      },
    });

    Omitted fields fall through to the defaults (delta poll every 5s, idle reset after 15 min, status refresh every 60s, etc).

    TracesListView

    New optional recentlyAddedKeys?: Set<string> prop. Rows whose traceId:spanId is in the set get the animate-row-highlight class — a brief fade-out to transparent, added to index.css.

    Compatibility

    Requires @mastra/server and @mastra/client-js at the versions that ship the observability delta-polling endpoints, and a store that opts into delta polling (@mastra/clickhouse, @mastra/duckdb, and the in-memory store today). When unavailable — older server or a store without delta capability — the hook silently falls back to page-mode interval refetching. No consumer changes required.

  • Added align and stack variants to PageLayout.Row. Use stack="responsive" for top bars that should collapse to a vertical stack on narrow viewports, and align="center" to vertically center children. Applied the new variants to the Prompts and Workflows top bars so the search field and primary action share a single row on desktop and stack on mobile. (#16714)

    <PageLayout.Row align="center" stack="responsive">
      <ListSearch ... />
      <Button ...>Create</Button>
    </PageLayout.Row>
  • Fixed the playground memory configuration display for agents using observationalMemory: true. (#16213)

  • Fixed double-counted cache token costs in the Metrics dashboard. The Model Usage & Cost table and the Token Usage by Agent table were summing cache read/write costs on top of the total input cost, which already includes them. (#16737)

  • Migrated the Tooltip primitive to Base UI while preserving the existing API. The popup explicitly sets role="tooltip" so consumers can keep querying it via getByRole('tooltip') (Base UI does not add this role automatically). Existing <TooltipTrigger asChild> usage continues to work unchanged, and Base UI's native render prop is now also supported on TooltipTrigger so consumers wrapping anchors, custom router links, or icons can pass the element directly without an asChild adapter: (#16713)

    // Still supported
    <TooltipTrigger asChild>
      <Button>Save</Button>
    </TooltipTrigger>
    
    // New: pass the element via Base UI's native API
    <TooltipTrigger render={<Button>Save</Button>} />

    Also fixed the arrow rendering so the diagonal stroke meets the popup outline at the exact same pixel center on every side, removing the ~1px seam previously visible where the arrow joined the popup edge.

  • Migrated the Slider component to base-ui with a refined neutral visual design. (#16788)

    What changed

    • Replaced @radix-ui/react-slider with @base-ui/react/slider as the underlying primitive
    • Refreshed visuals: thin rounded thumb with white border and neutral inside, opacity-based track that adapts to any surface, neutral filled indicator (no green/accent color)
    • Larger click target via padded Slider.Control and an invisible hit area on the thumb so it is easier to grab
    • Added cursor-pointer on the control and cursor-not-allowed when disabled
    • Removed the now unused @radix-ui/react-slider and @radix-ui/react-tabs dependencies

    API compatibility

    The public API is preserved. onValueChange and onValueCommitted are wrapped so consumers always receive number[], even though base-ui returns number | number[] internally. Existing call sites like <Slider value={[temperature]} onValueChange={value => setTemperature(value[0])} /> continue to work without changes.

  • Moved the Dialog component to Base UI. The public API is unchanged — asChild on DialogTrigger and DialogClose still works the same way, and open/close animations behave as before. (#16821)

@mastra/react@0.4.0
Minor Changes
  • Added clientTools option to useChat's generate/stream calls for forwarding browser-side tools to the agent on each invocation. (#16778)

    import { useChat } from '@mastra/react';
    
    const { generate } = useChat({ agentId: 'my-agent' });
    
    await generate({
      messages: [{ role: 'user', content: 'Show a toast that says hi' }],
      clientTools: {
        showToast: {
          description: 'Show a toast to the user',
          inputSchema: z.object({ message: z.string() }),
          execute: ({ message }) => toast(message),
        },
      },
    });

    Client tools are forwarded as-is to the underlying agent.generate() and agent.stream() calls.

  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before: { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After: { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

Patch Changes
@mastra/server@1.36.0
Minor Changes
  • Added delta polling support for observability list endpoints. (#16632)

    const page = await client.observability.listTraces({
      mode: 'page',
      filters: { entityName: 'agent-1' },
    });
    
    const delta = await client.observability.listTraces({
      mode: 'delta',
      filters: { entityName: 'agent-1' },
      after: page.deltaCursor,
    });

    Use mode: 'delta' to fetch only new items after the last cursor.

    Page-mode responses include pagination and deltaCursor when delta polling is supported. Delta-mode responses include delta and do not include pagination.

    If you read these responses directly in typed code, note that pagination is only included in page mode.

  • Added automatic FGA metadata for stored resource routes plus optional request scope isolation for stored resource APIs. Enable protected-route coverage with provider options: (#16651)

    const fga = new MastraFGAWorkos({
      resourceMapping,
      permissionMapping,
      requireForProtectedRoutes: true,
      auditProtectedRoutes: 'warn',
    });
  • Added an HTTP surface for stored agents/skills/workspaces, plus introspection endpoints for the agent-builder and an external skill-registry proxy. Studio and the client SDK use these endpoints to back the new "stored entity" management UI. (#16666)

    # Browse + manage stored entities (responses include favoriteCount + isFavorited)
    GET    /stored/agents?visibility=public&page=1&perPage=20
    GET    /stored/agents/:id
    POST   /stored/agents
    PATCH  /stored/agents/:id
    DELETE /stored/agents/:id
    
    # Versioning
    POST   /stored/skills/:id/publish
    POST   /stored/skills/:id/activate
    POST   /stored/skills/:id/restore
    
    # Favorites
    PUT    /stored/agents/:id/favorite
    DELETE /stored/agents/:id/favorite
    
    # Builder introspection
    GET    /editor/builder/settings
    GET    /editor/builder/infrastructure
    
    # External skill registry proxy (skills.sh)
    GET    /editor/builder/registries
    GET    /editor/builder/registries/:registryId/search
    GET    /editor/builder/registries/:registryId/popular
    GET    /editor/builder/registries/:registryId/skills/:owner/:repo/preview
    POST   /editor/builder/registries/:registryId/skills/:owner/:repo/install

    Highlights:

    • Visibility + authorship gating. Stored agents/skills now resolve a caller's author identity from the request context. Non-admin users only see their own + public entities. Admins see everything.
    • Favorites. List/get responses include favoriteCount and the caller's isFavorited flag. PUT/DELETE /stored/{agents|skills}/:id/favorite toggle the favorite for the caller.
    • Avatar validation. Stored-agent/skill metadata avatars are validated through a new validateMetadataAvatarUrl helper (rejects payloads over the size limit or with malformed base64).
    • Model-policy enforcement. Stored-agent create/update routes invoke assertModelAllowed via the new resolveBuilderModelPolicy helper. Disallowed models map to HTTP 422 with a structured body — { code, attempted, offendingLabel, allowed } — via handleError's new ModelNotAllowedError mapping.
    • Builder introspection. GET /editor/builder/settings returns feature flags, configuration, picker visibility, and model policy. GET /editor/builder/infrastructure reports browser-provider and sandbox status. Both default to { enabled: false } when no MastraEditor is configured.
    • External skill registry. /editor/builder/registries/* proxies the public skills.sh catalog so the builder UI can browse and install registered skills.

    This also bumps the @mastra/core peer dependency floor to >=1.34.0-0 (see the separate changeset) because the new handlers and error mapping import runtime values from @mastra/core/agent-builder/ee.

  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before: { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After: { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

  • Routes can now require any one of multiple permissions by passing an array to requiresPermission. When an array is provided, the request is allowed if the caller holds any of the listed permissions. Existing single-string usage continues to work. (#16605)

    // Before — single permission only
    {
      path: '/v1/things',
      method: 'GET',
      requiresPermission: 'things:read',
      handler,
    }
    
    // After — single permission or ANY-of array
    {
      path: '/v1/things/:id/stream',
      method: 'GET',
      requiresPermission: ['things:read', 'things:execute'],
      handler,
    }

    Denial messages now read Missing required permission: a or b or c when an array is used.

    New endpoint

    GET /api/auth/roles/:roleId/permissions returns the resolved permission list for a role. Useful for client-side gating and admin tooling.

    const res = await fetch('/api/auth/roles/admin/permissions', { credentials: 'include' });
    // { "roleId": "admin", "permissions": ["*"] }

    Namespaced request-context keys (non-breaking)

    coreAuthMiddleware now writes user state under namespaced keys (mastra__user, mastra__userPermissions, mastra__userRoles) in addition to the existing bare keys (user, userPermissions, userRoles). The bare keys are still written for backward compatibility, so existing middleware, integrations, and built-in handlers that read requestContext.get('user') continue to work unchanged.

    New code should prefer the namespaced constants to avoid collisions with caller-supplied request-context entries:

    import { MASTRA_USER_KEY, MASTRA_USER_PERMISSIONS_KEY, MASTRA_USER_ROLES_KEY } from '@mastra/server/auth';
    
    const user = requestContext.get(MASTRA_USER_KEY);
    const permissions = requestContext.get(MASTRA_USER_PERMISSIONS_KEY) as string[] | undefined;
    const roles = requestContext.get(MASTRA_USER_ROLES_KEY) as string[] | undefined;

    The bare keys (user, userPermissions, userRoles) remain populated and are considered the documented public surface for this release; a future major release may deprecate them.

    Route permission derivation

    getEffectivePermission() now recognizes stored resource families (stored-agents, stored-skills, stored-prompt-blocks, stored-mcp-clients, stored-scorers, stored-workspaces) and publish / activate / restore action suffixes on stored-resource routes. Return type widened to string | string[] | null to support routes that map to multiple permissions.

Patch Changes
  • Fixed stored resource updates to preserve existing metadata keys. (#16651)

  • Hardened the stored-agent and stored-skill favorite toggle endpoints (PUT/DELETE /stored/{agents,skills}/:id/favorite) so callers can no longer favorite or unfavorite entities outside their tenant scope. (#16749)

    Deployments that configure storedResources.scope now get the same 404-on-mismatch protection on favorite toggles that already applied to read/update/delete. Single-tenant deployments are unaffected.

    Also corrected JSDoc on stored-agent and stored-skill handlers to reference the canonical resource/action names (stored-agents:read, stored-skills:write).

  • Fix DELETE custom routes forwarding JSON request bodies. (#16857)

  • Fixed CompositeAuth incorrectly advertising SSO, session, and user provider capabilities when no inner provider supports them. Studio would show an SSO login button even when no provider had SSO configured, leading to 401 errors on login attempts. The duck-typing check now verifies that interface methods are actual functions rather than just present on the prototype chain. (#16664)

  • Fixed provider and dataset item history response types to include fields returned by the API. (#16213)

  • Widened BrowserStreamConfig.getToolset to support async lookup. Existing synchronous implementations continue to work — the type now accepts MastraBrowser | undefined or Promise<MastraBrowser | undefined>. (#16778)

    This unblocks server-adapter implementations that need to resolve agents asynchronously (for example, hydrating stored agents from storage on first browser-stream connection).

    import { setupBrowserStream } from '@mastra/server';
    
    await setupBrowserStream(app, {
      getToolset: async agentId => {
        const agent = await resolveAgent(agentId);
        return agent?.browser;
      },
      apiPrefix,
    });
  • Align stored-entity authorship checks with their RBAC resource names. Stored-agent and stored-skill handlers were calling hasAdminBypass / assertReadAccess / assertWriteAccess / resolveAuthorFilter with resource: 'agents' and resource: 'skills', but the routes are gated by stored-agents:* / stored-skills:* permissions. An admin granted stored-agents:* (or stored-skills:*) without the global * wildcard would pass route authorization but be treated as a non-admin inside the handler, so they could not list, read, or update private records owned by other users. Handlers now use stored-agents and stored-skills as the authorship resource string, matching the permission strings emitted by the route layer. (#16666)

  • Restore backwards compatibility for the legacy GET /api/memory/threads?orderBy=<field>&sortDirection=<dir> query shape emitted by @mastra/client-js < 1.18 (and any hand-rolled HTTP clients written against pre-1.0 docs). Server 1.31.0 turned that shape into a hard 400 (Invalid input: expected object, received undefined); the legacy bare-string + sortDirection pair is now transparently fused into the current { orderBy: { field, direction } } object shape before validation, so pinned clients keep working. (#16745)

    Current callers using bracket notation (orderBy[field]=...&orderBy[direction]=...) or a JSON-stringified orderBy are unaffected.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

  • Fixed the playground memory configuration display for agents using observationalMemory: true. (#16213)

@mastra/slack@1.2.1
Patch Changes
    • SlackProvider.connect() now merges with existing channel adapters instead of replacing them, preserving adapters the agent author already configured (e.g. Discord). (#16517)
    • Slack interactive payloads (button clicks, modal submissions) no longer return 400 Malformed JSON body. The provider only JSON-parses the body for the events callback path and forwards form-urlencoded payloads to the adapter's webhook handler unchanged.
    • Bumped chat to ^4.29.0.
@mastra/stagehand@0.2.3
Patch Changes
  • Silence Stagehand console logging by default and route logs through a logger hook to avoid corrupting host TUIs. (#16677)
@mastra/temporal@0.1.6
Patch Changes
  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)
@mastra/voice-openai-realtime@0.12.4
Patch Changes
  • Fixed OpenAI Realtime voice output streaming for current audio event names. (#16763)

  • Fixed user audio transcriptions missing or duplicating segments when OpenAI Realtime sends completion events. (#16759)

Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.37.0

Highlights

Google Cloud Spanner Storage Adapter (@mastra/spanner@1.0.0)

New first-party storage backend for Google Cloud Spanner (GoogleSQL) covering most Mastra storage domains (memory, workflows, schedules, observability, etc.), including persisting tracing spans for Studio and supporting the local Spanner emulator.

Agent Channels Streaming UX + Tool Rendering Controls (Core + Slack)

Major upgrades to channel adapters: opt-in streaming text deltas, adaptive typing indicators, and a new toolDisplay system (cards/text/timeline/grouped/hidden/function) to control how tool execution renders (including plan/timeline widgets and stable toolCallId correlation).

Client-Side Tool Observability End-to-End (@mastra/client-js + @mastra/observability)

Client-executed tools now show up in server traces via CLIENT_TOOL_CALL spans, with a shared observe helper for child spans/logs inside execute; observability exporters can ingest and forward this client telemetry (including duration metrics labeled toolType: 'client').

Agent Builder Routes Now Auto-Registered in Server Adapters

/agent-builder/* endpoints are now served automatically by any @mastra/server-based adapter (Express/Fastify/Hono/Koa/etc.) without manual wiring, while preserving lazy-loading to keep worker bundles small; new EE permissions gate access.

New Workspace Filesystem Provider via FilesSDK (@mastra/files-sdk@0.2.0)

Adds a unified WorkspaceFilesystem implementation backed by FilesSDK, letting you swap storage providers (S3/R2/GCS/Azure Blob/Vercel Blob/local FS, etc.) by changing the FilesSDK adapter without changing Mastra code.

Breaking Changes
  • @mastra/slack: removed cards: boolean and formatToolCall from Slack provider/channel config; migrate to toolDisplay: 'text' (for cards: false) or toolDisplay function form (for formatToolCall).

Changelog

@mastra/core@1.37.0
Minor Changes
  • Improved agent channels UX: (#16937)

    • Streaming text — opt-in per-adapter streaming flag (boolean | { updateIntervalMs?: number }) that pushes the agent's text deltas into the platform message progressively via the Chat SDK.

    • Adaptive typing indicator — the platform's typing status now reflects what the agent is doing (is working… at run start, is thinking… during reasoning, is typing… while generating text, is calling {toolName}… while a tool runs, is saving to memory…/is recalling memory… during memory work, is requesting approval for {toolName}… while a tool is suspended), coalesced so the platform API isn't called on every delta. Skipped while a streaming session is open since the widget itself conveys progress.

    • toolDisplay modes — new ChannelAdapterConfig.toolDisplay controls how tool calls render:

      • 'cards' (default) — per-tool running/result cards in rich Block Kit form.
      • 'text' — per-tool running/result messages as plain text (replaces the old cards: false flag).
      • 'timeline' — every tool gets its own task row in a streaming widget with status icons and args.
      • 'grouped' — all tools in the run collapse into a single streaming widget; args fold inline into the title and successful results are suppressed for an at-a-glance summary (errors keep their full text).
      • 'hidden' — tools run silently; only the typing indicator shows work.
      • Function form (ToolDisplayFn) — pass a function (event, ctx) => { kind: 'post', message } | { kind: 'stream', chunk } | undefined to fully control how every tool event renders. 'post' results post a discrete message (closing/reopening the streaming session when needed); 'stream' results push a task_update/plan_update into the active streaming widget; undefined skips the event. Every ToolDisplayEvent variant (running/result/error/approval) carries a stable toolCallId so user code can correlate events for the same tool invocation (e.g. as the id on a streamed task_update so the SDK updates a row in place rather than appending a new one).

      'timeline' and 'grouped' require streaming: true and fall back to 'cards' with a one-time warn if not enabled. 'cards'/'text' work under both streaming modes — with streaming: true, the driver closes the streaming session around each card, posts it, and reopens on the next chunk. Approve/deny prompts always render as a separate Block Kit card regardless of mode, since inline task entries and plain text can't carry interactive buttons.

      Deprecation (no breaking changes): ChannelAdapterConfig.cards: boolean and ChannelAdapterConfig.formatToolCall are now @deprecated and surface in IDEs with a strikethrough. Both still work at runtime — they're mutually exclusive with toolDisplay at the type level. When toolDisplay is not set: cards: true resolves to toolDisplay: 'cards', cards: false resolves to toolDisplay: 'text', and formatToolCall is shimmed into an equivalent ToolDisplayFn that only fires on result/error events. Migrate at your leisure: cards: falsetoolDisplay: 'text', and formatToolCall: ({ toolName, result }) => msgtoolDisplay: event => event.kind === 'result' ? { kind: 'post', message: msg } : undefined.

    • typingStatus customization — new ChannelAdapterConfig.typingStatus (boolean | (chunk, ctx) => string | false, default true). Set to false to suppress all typing indicators (useful when a live streaming widget already conveys progress), or pass a function to set custom copy per chunk. Compose with the exported defaultTypingStatus helper to fall back to built-in defaults for chunks you don't handle.

    • Signal-aware message boundaries — when a data-user-message signal echoes into the stream mid-reply, any in-flight text is flushed first so the agent's response renders as a new message after the user's signal instead of streaming into the prior reply.

    • Stronger stay-silent prompt — the channel input processor's non-DM system message now explicitly calls out anti-patterns (bracketed status notes, "Got it"/"Noted" acknowledgments, apologizing for silence) and points the model at add_reaction for silent acknowledgments. Empty responses are framed as a first-class action rather than a fallback.

    • Slack DM thread routing — each Slack thread (including top-level DMs) now maps to its own Mastra thread. Previously, replies and tool-approval clicks in a top-level DM could be routed into a sub-thread keyed by the bot's last message, causing follow-ups to thread under that message and tool approvals to fail to find the pending approval.

    • Parallel same-tool approval — fixed a bug where two parallel calls to the same tool with requireApproval: true clobbered each other's pending entry, so only the most recent could be approved.

    • Tool error rendering — failing tools now emit a closing task update in 'timeline'/'grouped' modes (previously the row stayed in_progress and rendered as ⚠ at session close) and edit their card in 'cards'/'hidden' modes. The error text is inlined into the task details (with a ⚠ glyph) while the task itself stays status: 'complete' so a single tool failure doesn't flip the overall plan header to an error state.

    • Observational-memory lifecycle in streaming widgetsdata-om-buffering-* and data-om-activation chunks are routed into the active streaming session in 'timeline'/'grouped' modes as their own task rows (e.g. Saved to memory (10x) with 12.4k → 1.2k tokens), so memory work is visible alongside tool calls. Consecutive observation activations within a session coalesce into a single Recalled memory (Nx) row with running totals instead of stacking — reflection runs often fire several activations back-to-back. The plan title is set to Updating memory on the first OM event so memory-only runs don't show the chat-SDK default of Thinking completed. OM buffering runs async in the background, so any still-in_progress OM task is optimistically marked complete when the streaming session closes — without this, the chat-SDK plan widget would flip the "Saving to memory…" row to an error icon when the stream ends before the buffer flush resolves. In non-plan modes ('cards'/'text'/'hidden'/ToolDisplayFn) OM events are skipped entirely — a phantom Plan widget showing only memory rows would be inconsistent with the mode contract. Approval plan_update/task_update rows are also gated by 'timeline'/'grouped' for the same reason; non-plan modes now post the approval card directly without flashing a single-row Plan widget that closes immediately after.

    • Logger propagation — the Mastra logger is now propagated into AgentChannels on register so channel-level logs flow through the configured logger.

    • Internal refactor (no public API change)consumeAgentStream now dispatches to one of two focused drivers (streaming vs static) instead of switching on toolDisplay inside a single 700-line loop. Tool-call correlation moved into a ToolTracker helper and observational-memory rendering into a dedicated renderOmTaskUpdate helper, both shared between drivers. Invalid combinations now warn and downgrade: streaming: false + 'timeline'/'grouped' falls back to 'cards'. streaming: true + 'cards'/'text' is now valid and uses the streaming driver's close/post/reopen lifecycle.

    import { Agent } from '@mastra/core/agent';
    import { defaultTypingStatus } from '@mastra/core/channels';
    import { createDiscordAdapter } from '@mastra/discord';
    
    const agent = new Agent({
      name: 'support-bot',
      channels: {
        adapters: {
          discord: {
            adapter: createDiscordAdapter(),
            streaming: true,
            toolDisplay: 'grouped', // 'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn
            typingStatus: false, // suppress typing indicator when the widget already shows progress
            // Custom typing status per chunk; fall back to defaults for everything else.
            // typingStatus: (chunk, ctx) => {
            //   if (chunk.type === 'tool-call' && chunk.payload.toolName === 'searchDocs') {
            //     return 'is searching docs…';
            //   }
            //   return defaultTypingStatus(chunk, ctx);
            // },
            // Custom tool rendering via the function form: skip the running message,
            // post a single line on result. `undefined` skips rendering that event.
            // toolDisplay: event => {
            //   if (event.kind !== 'result') return undefined;
            //   return { kind: 'post', message: `🛠 ${event.toolName} → ${event.resultText}` };
            // },
          },
        },
      },
    });
  • Client-side tools now appear in your traces when observability is configured. When an agent calls a tool that executes in the browser via @mastra/client-js, a CLIENT_TOOL_CALL span is recorded on the server trace so you can see which client tools were invoked, what arguments they received, and how they relate to the rest of the agent run. (#16425)

    Tools also gain an observe helper on their execution context for recording child spans and logs from inside execute:

    execute: async ({ userId }, { observe }) => {
      observe.log('info', 'fetching user', { userId });
      return observe.span('fetch user', () => fetch(`/api/users/${userId}`));
    };
  • Added signal delivery option attributes API that conditionally merges branch attributes based on whether a signal is delivered to an active agent run (ifActive.attributes) or an idle run (ifIdle.attributes). This enables contextual signal delivery — for example, tagging user messages as while-active when the agent is actively working. (#16923)

  • Add per-provider capability files and auto mode for observeAttachments (#16922)

    • Generate per-provider capability files (e.g. capabilities/openai.json) alongside the model router registry, sourced from models.dev API
    • Export modelSupportsAttachments(modelRouterId) from @mastra/core/llm to check whether a model supports image/file attachments
    • Extend observeAttachments config to accept 'auto' in addition to boolean | string[]
    • When set to 'auto', the observer resolves the model (including function-based models) and checks the capability registry before deciding to forward or drop attachment parts
Patch Changes
  • Update provider registry and model documentation with latest models and providers (cfa2e3a)

  • Added sub-agent token usage to onDelegationComplete results, so apps can track per-sub-agent token costs (#15825)

  • Fixed thread subscription streams stalling or deadlocking when multiple consumers observe the same active run. Thread streams are now multicast to subscribers so each subscriber receives the run without competing for the underlying stream, and follow-up messages can continue while a subscription is active. (#16946)

  • Connecting or disconnecting an agent through a channel (e.g. Slack) now requires the same write permission as editing the underlying stored agent. The check runs on POST /channels/:platform/connect and POST /channels/:platform/:agentId/disconnect whenever the target agent has a record in the stored-agents store. Callers without write access receive a 404 Not found, matching the behavior of the stored-agent edit routes. Agents defined in code (no stored-agents record) are unaffected and continue to honor only the route's existing auth requirement. (#16949)

    The caller must either own the stored agent, have admin bypass, or hold agents:edit (or a scoped agents:edit:<agentId>).

    POST /channels/slack/connect
    Authorization: Bearer <token-with-agents:edit>
    Content-Type: application/json
    
    { "agentId": "support-bot" }
    POST /channels/slack/support-bot/disconnect
    Authorization: Bearer <token-with-agents:edit>

    @mastra/core is bumped as a patch to ship the regenerated permission definitions that back this check.

  • Made provider capabilities loading lazy instead of bulk-syncing on every registry access. Removed subscription setup from harness switchMode() — subscriptions are now lazily ensured at send time. Added 10s TTL cache and invalidation method for listAvailableModels(). (#17008)

  • Preserve this when resolving permissions for the "View as role" picker in buildCapabilities. Class-based RBAC providers (e.g. @mastra/auth-workos) read state from this inside getPermissionsForRole, but the method was being detached to a bare variable before invocation. This caused a TypeError: Cannot read properties of undefined (reading 'options') to be logged on every authenticated request and silently emptied the available roles list. The error was swallowed by a surrounding try/catch so admins saw an empty picker instead of a crash. (#17112)

  • Fixed workflow.parallel() type-checking for steps that declare requestContextSchema. (#16989) Steps with matching request context now type-check correctly. Steps with mismatched request context still fail with a type error. Fixes #16975.

  • Fixed split-brain broker election race in UnixSocketPubSub. When a broker process dies and multiple clients recover concurrently, an exclusive lock file now serializes the election so exactly one process becomes the new broker. (#16955)

  • Fixed a crash that could occur when background execution is enabled for tools with Zod v3 input schemas. (#16915)

    Tools with Zod v3, Zod v4, and JSON Schema input definitions now work consistently with background execution.

  • Fixed processor workflow steps so sendSignal is available when processors inject Agent signals, and updated Observational Memory temporal gap markers to use Agent signals. (#16927)

  • Fixed an issue where thread subscriptions could appear idle after a run finished. Subsequent runs now stream promptly, and post-finish signals correctly start from an idle state. (#16928)

  • Fixed agent responses being ordered before the user message that triggered them in long conversations. This prevents duplicate tool calls in the next step. This regression started in 1.35.0. Fixes https://github.com/mastra-ai/mastra/issues/16893. (#16913)

  • Fixed agent crash when backgroundTasks.enabled: true is combined with a tool whose inputSchema uses Zod's .refine() or .superRefine(). On Zod v4, such agents threw Cannot overwrite keys on object schemas containing refinements on repeated invocations and became unusable. Tools with refined input schemas now work with background tasks. (#16966)

  • UnixSocketPubSub: skip serialization when broker has 0 remote clients, lazily build ServerFrame only when a subscribed client exists, and automatically elect a new broker with resubscription when the active broker disconnects. (#16939)

  • Fixed v1 agent tools to preserve legacy tool result output while still allowing usage data in delegation hooks. (#17070)

  • Fixed thread stream subscriptions so streamed agent responses are saved to memory while still supporting multiple subscribers. (#16982)

  • Gate stored-workspace handlers by author. Previously any authenticated caller within a tenant could list, read, update, or delete another user's workspace. (#16974)

    Behavior changes

    • POST /stored/workspaces — server stamps authorId from the authenticated caller; any body-provided authorId is ignored.
    • GET /stored/workspaces/:id, PATCH /stored/workspaces/:id, DELETE /stored/workspaces/:id — return 404 Not found unless the caller is the owner, an admin (*), or holds stored-workspaces:<action>[:<id>].
    • GET /stored/workspaces — filters to the caller's own rows plus legacy unowned records; admins still see every row.
    • Legacy workspaces created before this change (no authorId) remain accessible to any authenticated caller for backwards compatibility.

    Example

    // Client POST body — authorId is ignored if sent
    await fetch('/stored/workspaces', {
      method: 'POST',
      body: JSON.stringify({ name: 'My workspace', authorId: 'someone-else' }),
    });
    
    // Stored row — authorId is stamped from the authenticated caller
    // {
    //   id: 'my-workspace',
    //   name: 'My workspace',
    //   authorId: 'user_abc123', // from requestContext, NOT from body
    //   ...
    // }

    Migration

    • Existing rows with authorId === null/undefined remain readable/writable by any authenticated caller — no action required for backwards compatibility.
    • To lock down legacy rows, backfill authorId directly in the workspaces table with the original creator's id.
    • For service accounts or tooling that need cross-user access, grant stored-workspaces:* (or per-id stored-workspaces:<action>:<id>) instead of relying on the legacy unowned bypass.
    • Admins (callers with *) continue to see and mutate every row regardless of authorId.

    The @mastra/core patch regenerates permissions.generated.ts to include the auth and infrastructure resources that already had routes on main.

  • Agent Builder action routes (/agent-builder/*) are now registered automatically through the standard server route pipeline. Any adapter built on @mastra/server (Hono, Express, Fastify, Koa, etc.) serves the 15 /agent-builder/* endpoints without consumers wiring them manually. (#17085)

    Example

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
    
    // `/agent-builder/*` routes are now reachable out-of-the-box
    const actions = await client.getAgentBuilderActions();
    
    const action = client.getAgentBuilderAction('generate-agent');
    const { runId } = await action.createRun();
    const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId);

    Why

    Previously, AGENT_BUILDER_ROUTES was a type-only entry in the route registry to keep @mastra/agent-builder out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of @mastra/agent-builder is preserved — handlers still resolve the workflow module on first request via dynamic import(), so Cloudflare bundles are unaffected.

    New EE permissions

    The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes:

    • agent-builder:read
    • agent-builder:write
    • agent-builder:execute

    Two legacy stream routes (STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE, OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE) are now registered through the standard pipeline as well.

  • Fixed a race that could cause an immediate auto-resume of a suspended tool call to fail on some storage backends. Resume now succeeds reliably whether the underlying storage is fast or slow. (#17015)

    Fixes #16158.

  • Fixed a race in Harness sendMessage where a phantom agent_end: 'complete' event could fire before any chunks arrived. Subscribers, such as apps running on Cloudflare Workers or Durable Objects, will no longer miss text deltas, messages, or tool events when an agent run completes. (#17024)

    The cause was AgentThreadStreamRuntime.subscribeToThread's activeRunId() returning null during the gap between sendSignal reserving a runId and registerRun populating the stream record, which made waitForCurrentThreadStreamIdle() exit immediately and sendMessage emit a synthetic agent_end. The subscriber now treats both a reserved-but-not-yet-registered local run and an active remote run as live, matching sendSignal's own behavior.

  • Suppressed noisy gateway fetch errors when models.dev is unreachable. The registry no longer retries or logs errors on network failure since all model data is already bundled at publish time. (#16984)

@mastra/acp@0.2.0
Minor Changes
  • Added programmatic model selection for ACP agents using the model option. (#17010)

    You can now set the model directly when creating AcpAgent or createACPTool, instead of relying on environment variables.

    const codeAgent = new AcpAgent({
      id: 'code-agent',
      description: 'ACP-compatible coding agent',
      command: 'claude',
      args: ['--acp'],
      model: 'claude-sonnet-4-20250514',
    });

    Discover available models with getAvailableModels() and change the model at runtime with setModel(). Invalid model IDs throw a descriptive error listing valid options.

Patch Changes
  • Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. (#16726)
@mastra/client-js@1.21.0
Minor Changes
  • Client-side tool tracing is now built in. When server-side observability is configured, the SDK automatically measures execution duration and ships it back to the server. To add child spans and structured logs from inside your tool's execute(input, context) function, use the observe helper on the execution context: (#16425)

    execute: async ({ userId }, { observe }) => {
      observe.log('info', 'fetching user', { userId });
      return observe.span('fetch user', () => fetch(`/api/users/${userId}`));
    };

    The createTool() helper now calls execute(input, context) so client tools receive the same execution context shape as core tools.

Patch Changes
  • Fixed client-side tools getting stuck in input-available state in React's useChat messages. After a client tool finished executing, the React UI never observed a terminal tool-result (or tool-error) chunk for it, so the matching dynamic-tool part stayed at state: 'input-available' indefinitely. The client now emits a synthetic Mastra-shaped terminal chunk into the streamed response right after the client tool resolves or rejects, so the React reducer correctly flips the part to output-available (or output-error) and renders the tool result. (#16916)

    Also fixed the client stream parser so final tool-call chunks are not treated as partial streaming tool calls while preparing client-tool continuation messages.

  • Port the yj/magnificent-marquess frontend stack onto rain-purpose. (#17105)

    • @mastra/client-js: new ToolProvider resource and a getModelPolicy accessor on the root client. Route types regenerated for the new endpoints.
    • @internal/playground: Agent Builder routes (agents, skills, infrastructure, favorite, library) wired into the router, RoutePermissionGuard and RoleImpersonationProvider applied to the app shell, new login layout, role-impersonation banner, useRestoreFocus hook, StudioIndexRedirect home, and supporting tweaks across agents, browser view, LLM, and CMS surfaces.

    Existing client-tools-on-signals work and the unrouted Agent Builder view/edit pages are preserved.

  • Made optional memory response fields optional in server schemas and generated client types. (#17070)

  • Add StoredSkill.favorite() and StoredSkill.unfavorite() methods, mirroring the existing StoredAgent favorite API. Both are idempotent and call PUT/DELETE /api/stored/skills/:id/favorite. (#17101)

  • Agent Builder action routes (/agent-builder/*) are now registered automatically through the standard server route pipeline. Any adapter built on @mastra/server (Hono, Express, Fastify, Koa, etc.) serves the 15 /agent-builder/* endpoints without consumers wiring them manually. (#17085)

    Example

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
    
    // `/agent-builder/*` routes are now reachable out-of-the-box
    const actions = await client.getAgentBuilderActions();
    
    const action = client.getAgentBuilderAction('generate-agent');
    const { runId } = await action.createRun();
    const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId);

    Why

    Previously, AGENT_BUILDER_ROUTES was a type-only entry in the route registry to keep @mastra/agent-builder out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of @mastra/agent-builder is preserved — handlers still resolve the workflow module on first request via dynamic import(), so Cloudflare bundles are unaffected.

    New EE permissions

    The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes:

    • agent-builder:read
    • agent-builder:write
    • agent-builder:execute

    Two legacy stream routes (STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE, OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE) are now registered through the standard pipeline as well.

  • Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. (#17045)

    Enable automatic reconnection with subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true }).

  • Fixed clientTools being silently dropped — and never executed — on thread-backed chats. When a chat had a threadId, the React useChat hook routed messages through the new agent signals path but did not pass the clientTools map into the signal startup flow, so client-side tools were unavailable when the model requested them. (#16540)

    The signals path now carries clientTools and other per-send stream options on sendSignal. When the subscribed stream finishes with tool-calls, the client executes matching local tools with observability support, emits tool result chunks, and posts a continuation with the assistant tool-call messages plus tool-result messages so the run resumes on the same thread with the same per-send options.

@mastra/convex@1.2.0
Minor Changes
  • Convex can now persist channel installations and provider configuration. (#16718)

    import { ConvexStore } from '@mastra/convex';
    
    const storage = new ConvexStore({
      id: 'app-storage',
      deploymentUrl: process.env.CONVEX_URL!,
      adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
    });
    
    const channels = await storage.getStore('channels');
    
    await channels?.saveInstallation({
      id: 'slack-agent-1',
      platform: 'slack',
      agentId: 'agent-1',
      status: 'active',
      webhookId: 'webhook-1',
      data: { teamId: 'T123', botUserId: 'U123' },
      createdAt: new Date(),
      updatedAt: new Date(),
    });
  • Workflow schedules can now be stored in Convex. (#16710)

    import { ConvexStore } from '@mastra/convex';
    
    const storage = new ConvexStore({
      id: 'app-storage',
      deploymentUrl: process.env.CONVEX_URL!,
      adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
    });
    
    const schedules = await storage.getStore('schedules');
    
    await schedules?.createSchedule({
      id: 'daily-summary',
      target: { type: 'workflow', workflowId: 'summary-workflow' },
      cron: '0 9 * * *',
      status: 'active',
      nextFireAt: Date.now(),
      createdAt: Date.now(),
      updatedAt: Date.now(),
    });
Patch Changes
  • Improved Convex background task reliability with safer lifecycle updates, faster filtering, and smoother upgrades from older storage rows. (#16724)
@mastra/deployer@1.37.0
Patch Changes
  • Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. (#16726)
@mastra/editor@0.10.0
Minor Changes
  • Ship EditorAgentBuilder and Agent Builder runtime through the @mastra/editor/ee subpath. (#16948)

    • Adds EditorAgentBuilder class and supporting types under @mastra/editor/ee (dormant unless MastraEditorConfig.builder is configured).
    • Wires builder resolution on MastraEditor: hasEnabledBuilderConfig(), resolveBuilder(), ensureBuilderWorkspaces(), and reconcileBuilderWorkspaces().
    • Adds builder defaults plumbing in the agent namespace (applyBuilderDefaults, BUILDER_BASELINE_DEFAULTS enabling observationalMemory: true by default for Builder-created agents).
    • Adds a defense-in-depth license guard inside MastraEditor.resolveBuilder() that mirrors the server-startup check in MastraServer.validateAgentBuilderLicense(). Dev environments bypass via isEEEnabled(); production without a valid MASTRA_EE_LICENSE throws [mastra/auth-ee] Agent Builder is configured but no valid EE license was found.
    • Bumps the @mastra/core peer dependency to >=1.34.0-0 <2.0.0-0 to cover the @mastra/core/agent-builder/ee and @mastra/core/auth/ee subpaths consumed by the builder runtime.

    Opt-in usage:

    import { Mastra } from '@mastra/core';
    import { MastraEditor } from '@mastra/editor';
    
    const editor = new MastraEditor({
      builder: {
        enabled: true,
        configuration: {
          agent: {
            models: { default: { provider: 'openai', modelId: 'gpt-4o-mini' } },
          },
        },
      },
    });
    
    new Mastra({ storage, editor });
    
    // Later, on demand:
    const builder = await editor.resolveBuilder();
    // `builder` is undefined when the builder is not configured/enabled.
    // In production it requires a valid MASTRA_EE_LICENSE; dev environments bypass.

    This is plumbing — no UI consumer ships in this release.

Patch Changes
  • Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. (#16726)
@mastra/evals@1.2.3
Patch Changes
  • Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. (#16726)
@mastra/express@1.3.24
Patch Changes
  • Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. (#16335)

    registerApiRoute('/stream', {
      method: 'GET',
      handler: async c => {
        const stream = await agent.stream(prompt, {
          abortSignal: c.req.raw.signal,
        });
    
        return stream.toTextStreamResponse();
      },
    });
  • Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. (#17045)

    Enable automatic reconnection with subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true }).

@mastra/fastembed@1.1.1
Patch Changes
  • Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. (#16726)
@mastra/fastify@1.3.24
Patch Changes
  • Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. (#16335)

    registerApiRoute('/stream', {
      method: 'GET',
      handler: async c => {
        const stream = await agent.stream(prompt, {
          abortSignal: c.req.raw.signal,
        });
    
        return stream.toTextStreamResponse();
      },
    });
@mastra/files-sdk@0.2.0
Minor Changes
  • Added @mastra/files-sdk workspace filesystem provider — a unified storage adapter backed by FilesSDK. Supports any FilesSDK adapter (S3, R2, GCS, Azure Blob, Vercel Blob, local filesystem, and more) through a single FilesSDKFilesystem class that implements the WorkspaceFilesystem interface. (#17027)

    Usage

    import { Files } from 'files-sdk';
    import { s3 } from 'files-sdk/s3';
    import { FilesSDKFilesystem } from '@mastra/files-sdk';
    
    const files = new Files({ adapter: s3({ bucket: 'my-bucket', region: 'us-east-1' }) });
    
    const filesystem = new FilesSDKFilesystem({ files });

    Swap adapters without changing code — just replace s3() with r2(), gcs(), azure(), fs(), etc.

Patch Changes
@mastra/hono@1.4.19
Patch Changes
  • Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. (#16335)

    registerApiRoute('/stream', {
      method: 'GET',
      handler: async c => {
        const stream = await agent.stream(prompt, {
          abortSignal: c.req.raw.signal,
        });
    
        return stream.toTextStreamResponse();
      },
    });
  • Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. (#17045)

    Enable automatic reconnection with subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true }).

  • Fixed SSE streams so browser clients can connect to long-lived subscriptions before the first stream event is available. (#16540)

@mastra/koa@1.5.7
Patch Changes
  • Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. (#16335)

    registerApiRoute('/stream', {
      method: 'GET',
      handler: async c => {
        const stream = await agent.stream(prompt, {
          abortSignal: c.req.raw.signal,
        });
    
        return stream.toTextStreamResponse();
      },
    });
  • Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. (#17045)

    Enable automatic reconnection with subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true }).

@mastra/mcp@1.8.1
Patch Changes
  • Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. (#16726)
@mastra/memory@1.20.0
Minor Changes
  • Add per-provider capability files and auto mode for observeAttachments (#16922)
    • Generate per-provider capability files (e.g. capabilities/openai.json) alongside the model router registry, sourced from models.dev API
    • Export modelSupportsAttachments(modelRouterId) from @mastra/core/llm to check whether a model supports image/file attachments
    • Extend observeAttachments config to accept 'auto' in addition to boolean | string[]
    • When set to 'auto', the observer resolves the model (including function-based models) and checks the capability registry before deciding to forward or drop attachment parts
Patch Changes
  • Fixed observational memory so in-progress messages are not removed during observation cleanup. (#16913)

  • Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. (#16726)

  • Fixed processor workflow steps so sendSignal is available when processors inject Agent signals, and updated Observational Memory temporal gap markers to use Agent signals. (#16927)

@mastra/observability@1.14.0
Minor Changes
  • Support ingesting client-side tool telemetry. Spans, logs, and duration metrics captured by the client SDK during tool execution are forwarded through the observability bus to your existing exporters. Client tool durations are reported via the existing mastra_tool_duration_ms metric with a toolType: 'client' label to distinguish them from server-side tool durations. (#16425)
Patch Changes
  • Paused observability uploads after invalid credentials so exporters stop repeatedly sending unauthorized requests. (#16743)
@mastra/playground-ui@30.0.0
Minor Changes
  • Added a Drawer component — a panel that slides in from any edge of the screen with swipe-to-dismiss gestures. (#16958)

    The Drawer can be anchored to any of the four screen edges and supports snap points, nested stacking, controlled state, non-modal mode, swipe-to-open areas, and detached triggers.

    import {
      Drawer,
      DrawerTrigger,
      DrawerContent,
      DrawerHeader,
      DrawerTitle,
      DrawerDescription,
      DrawerFooter,
      DrawerClose,
      Button,
    } from '@mastra/playground-ui';
    
    <Drawer side="right">
      <DrawerTrigger asChild>
        <Button>Open</Button>
      </DrawerTrigger>
      <DrawerContent>
        <DrawerHeader>
          <DrawerTitle>Library</DrawerTitle>
          <DrawerDescription>A panel that slides in from the right edge.</DrawerDescription>
        </DrawerHeader>
        <DrawerFooter>
          <DrawerClose asChild>
            <Button variant="outline">Close</Button>
          </DrawerClose>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>;
  • Added a reusable HoverCard component (HoverCard, HoverCardTrigger, HoverCardContent) built on Base UI. You can now use these exported components to add hover card interactions anywhere in your UI. (#16919)

    import { HoverCard, HoverCardTrigger, HoverCardContent } from '@mastra/playground-ui';
    
    <HoverCard>
      <HoverCardTrigger>Weather Agent</HoverCardTrigger>
      <HoverCardContent>Answers questions about current conditions and forecasts.</HoverCardContent>
    </HoverCard>;
Patch Changes
  • Migrated the Switch component to Base UI for smoother animations and consistent behavior. No API changes — checked, defaultChecked, onCheckedChange, and disabled work exactly as before. (#16891)

  • Improved the Select component by migrating it to Base UI for more reliable positioning and accessibility. The public API (Select, SelectTrigger, SelectContent, SelectItem, SelectValue, SelectGroup) is unchanged, so no consumer updates are needed. (#16918)

  • Added DataList.RowStatic, a non-interactive row primitive. It renders a row that looks identical to other list rows but does not respond to clicks and shows no hover/focus state — use it alongside DataList.RowButton / DataList.RowLink when only some rows are clickable (e.g. error or placeholder entries in an otherwise navigable list). (#16970)

    {
      rows.map(row =>
        row.href ? (
          <DataList.RowLink key={row.id} to={row.href} LinkComponent={Link}>
            {row.cells}
          </DataList.RowLink>
        ) : (
          <DataList.RowStatic key={row.id}>{row.cells}</DataList.RowStatic>
        ),
      );
    }
  • Added DataList primitives and props for building selection-aware, condensed list rows that match the Traces/Logs visual style. (#16820)

    New cells on DataList:

    • IdCell — compact mono cell that truncates long IDs to 8 chars.
    • MonoCell — compact mono + truncate text cell (for input previews, JSON summaries, etc.).
    • DateCell — compact date cell rendering "Today" or "MMM dd".
    • TimeCell — compact mono time cell rendering HH:mm:ss.SSS with the millisecond portion tinted.
    • SelectCell — labelled checkbox cell with a shift-key range-select handler.
    • TopSelectCell — header version with indeterminate support for "select all".
    • TopCells — non-interactive header sibling of RowButton, for hosting top cells beside a leading select cell.

    New props on DataList.RowButton and DataList.RowLink:

    • flushLeft — drops the default left margin when wrapped beside a leading cell.
    • colStart — places the row starting at a column line (e.g. colStart={2} to leave column 1 for a leading cell).
    • featured — applies the highlighted background to mark the active row.

    New props on existing wrappers:

    • as on DataList.Cell and DataList.TopCell — render the cell as any HTML element (e.g. <label> so the whole cell is clickable).
    • hasLeadingCell on DataList.Top — drops default gap and left padding so a leading cell sits flush, mirroring how Row + RowButton compose.

    Example — selection row with a checkbox in column 1 and an interactive button spanning the rest:

    <DataList.Row>
      <DataList.SelectCell checked={isSelected} onToggle={shiftKey => toggle(id, shiftKey)} />
      <DataList.RowButton flushLeft colStart={2} featured={isFeatured} onClick={onRowClick}>
        <DataList.IdCell id={item.id} />
        <DataList.MonoCell>{item.input}</DataList.MonoCell>
      </DataList.RowButton>
    </DataList.Row>

    Internally the Traces and Logs list views now use the shared primitives — no behavior change for those consumers.

  • Added support for trailing cells in DataList rows. DataList.RowButton and DataList.RowLink now accept colEnd and flushRight (mirrors of the existing colStart/flushLeft), so a row can sit beside a non-interactive trailing cell (e.g. an actions column) and stay aligned with the header. Rows wrapped in DataList.Row now render a full-width separator that extends through the leading and trailing cells. DataList.MonoCell also gained an optional height prop so non-compact lists can use it without forcing compact padding. (#16888)

    Usage

    <DataList.Row>
      <DataList.RowButton flushLeft flushRight colEnd={-2} onClick={onClick}>
        {/* main row content */}
      </DataList.RowButton>
      <DataList.Cell>
        {/* trailing actions, e.g. icon buttons */}
      </DataList.Cell>
    </DataList.Row>
    
    <DataList.MonoCell height="default">long mono text…</DataList.MonoCell>
  • Migrated the Label component off Radix UI. It now renders a native <label> element with the same props and styling — htmlFor, className, and children behave exactly as before. (#16892)

  • Fixed Studio Settings page (and other default-height PageLayout pages) clipping their content with no scrollbar on viewports shorter than the form. Users on short laptop screens (under ~991px tall) could not reach the Save button under the Mastra Connection headers form, making it impossible to apply changes. Default-height PageLayout pages now grow with their content and scroll through the studio chrome wrapper; height="full" pages (Logs, Traces, Metrics, etc.) are unchanged. (#16999)

  • Restyled scrollbars across the studio UI to match the design system — thin, themed thumb on a transparent track — replacing the default OS scrollbars that clashed with dark and light surfaces. (#16918)

  • Exported ContextMenu from the package entry so it can be imported alongside other Base UI primitives. (#17062)

  • Design-system additions to support theming: (#17059)

    • Avatar now accepts optional color and textColor props for per-instance tinting, and falls back to the initial when the image fails to load.
    • Searchbar accepts an optional className to let consumers tune layout without forking.
    • TabList accepts a style prop and the active-tab indicator now reads from the --tab-indicator-color CSS variable, letting parents theme the indicator (e.g. per-agent accent color).
    • stringToColor now accepts any number for the lightness argument and defaults to 90 instead of 75 for a lighter fallback chip.
    • Global body rule enables font-smoothing / -webkit-font-smoothing for crisper UI text.
  • Removed EntryList and its sub-components (EntryList.Header, EntryList.Entries, EntryList.Entry, EntryList.EntryText, EntryList.Pagination, EntryList.NoMatch, EntryListSkeleton, etc.) from the public API. All in-repo list views have migrated to DataList, which is the recommended replacement. (#16910)

    Migration:

    // Before
    import { EntryList, EntryListSkeleton } from '@mastra/playground-ui';
    
    <EntryList>
      <EntryList.Trim>
        <EntryList.Header columns={columns} />
        <EntryList.Entries>
          {items.map(item => (
            <EntryList.Entry key={item.id} columns={columns} entry={item} onClick={}>
              {columns.map(col => <EntryList.EntryText key={col.name}>{item[col.name]}</EntryList.EntryText>)}
            </EntryList.Entry>
          ))}
        </EntryList.Entries>
      </EntryList.Trim>
      <EntryList.Pagination …/>
    </EntryList>
    
    // After
    import { DataList } from '@mastra/playground-ui';
    
    <DataList columns={gridColumns}>
      <DataList.Top>
        {columns.map(col => <DataList.TopCell key={col.name}>{col.label}</DataList.TopCell>)}
      </DataList.Top>
      {items.map(item => (
        <DataList.RowButton key={item.id} onClick={}>
          {columns.map(col => <DataList.Cell key={col.name}>{item[col.name]}</DataList.Cell>)}
        </DataList.RowButton>
      ))}
      <DataList.Pagination …/>
    </DataList>
  • Improved the Checkbox component by migrating it to Base UI. The public API is unchanged — checked (including the 'indeterminate' value), defaultChecked, onCheckedChange, and disabled all behave as before. (#16905)

  • Fixed MetricsDataTable sticky header and column backgrounds to use surface3 token, matching DashboardCard surface (#16828)

  • Improved the RadioGroup component by migrating it to Base UI. The public API (RadioGroup, RadioGroupItem, value, onValueChange, disabled) is unchanged. Also fixes the radio indicator sizing/centering — the control now stays square and the inner dot is properly centered. (#16904)

@mastra/posthog@1.0.26
Patch Changes
  • Fixed negative PostHog cost calculations when cache tokens are present. (#16876)
@mastra/react@0.4.1
Patch Changes
  • Added support for reasoning-start and reasoning-end stream chunks so reasoning blocks are opened and closed with provider metadata preserved. (#17061)

  • Fixed clientTools being silently dropped — and never executed — on thread-backed chats. When a chat had a threadId, the React useChat hook routed messages through the new agent signals path but did not pass the clientTools map into the signal startup flow, so client-side tools were unavailable when the model requested them. (#16540)

    The signals path now carries clientTools and other per-send stream options on sendSignal. When the subscribed stream finishes with tool-calls, the client executes matching local tools with observability support, emits tool result chunks, and posts a continuation with the assistant tool-call messages plus tool-result messages so the run resumes on the same thread with the same per-send options.

@mastra/sentry@1.1.0
Minor Changes
  • Added the gen_ai.conversation.id span attribute to the Sentry exporter, sourced from metadata.threadId. Spans from the same chat thread now group together in Sentry's Conversations view (part of AI Agent Monitoring). (#16925)

    const agent = mastra.getAgent('chat');
    
    // Pass threadId as before — the Sentry exporter now emits it as gen_ai.conversation.id
    await agent.generate('Hello', {
      memory: { thread: 'thread-123', resource: 'user-1' },
    });
Patch Changes
@mastra/server@1.37.0
Minor Changes
  • Connecting or disconnecting an agent through a channel (e.g. Slack) now requires the same write permission as editing the underlying stored agent. The check runs on POST /channels/:platform/connect and POST /channels/:platform/:agentId/disconnect whenever the target agent has a record in the stored-agents store. Callers without write access receive a 404 Not found, matching the behavior of the stored-agent edit routes. Agents defined in code (no stored-agents record) are unaffected and continue to honor only the route's existing auth requirement. (#16949)

    The caller must either own the stored agent, have admin bypass, or hold agents:edit (or a scoped agents:edit:<agentId>).

    POST /channels/slack/connect
    Authorization: Bearer <token-with-agents:edit>
    Content-Type: application/json
    
    { "agentId": "support-bot" }
    POST /channels/slack/support-bot/disconnect
    Authorization: Bearer <token-with-agents:edit>

    @mastra/core is bumped as a patch to ship the regenerated permission definitions that back this check.

  • Gate stored-workspace handlers by author. Previously any authenticated caller within a tenant could list, read, update, or delete another user's workspace. (#16974)

    Behavior changes

    • POST /stored/workspaces — server stamps authorId from the authenticated caller; any body-provided authorId is ignored.
    • GET /stored/workspaces/:id, PATCH /stored/workspaces/:id, DELETE /stored/workspaces/:id — return 404 Not found unless the caller is the owner, an admin (*), or holds stored-workspaces:<action>[:<id>].
    • GET /stored/workspaces — filters to the caller's own rows plus legacy unowned records; admins still see every row.
    • Legacy workspaces created before this change (no authorId) remain accessible to any authenticated caller for backwards compatibility.

    Example

    // Client POST body — authorId is ignored if sent
    await fetch('/stored/workspaces', {
      method: 'POST',
      body: JSON.stringify({ name: 'My workspace', authorId: 'someone-else' }),
    });
    
    // Stored row — authorId is stamped from the authenticated caller
    // {
    //   id: 'my-workspace',
    //   name: 'My workspace',
    //   authorId: 'user_abc123', // from requestContext, NOT from body
    //   ...
    // }

    Migration

    • Existing rows with authorId === null/undefined remain readable/writable by any authenticated caller — no action required for backwards compatibility.
    • To lock down legacy rows, backfill authorId directly in the workspaces table with the original creator's id.
    • For service accounts or tooling that need cross-user access, grant stored-workspaces:* (or per-id stored-workspaces:<action>:<id>) instead of relying on the legacy unowned bypass.
    • Admins (callers with *) continue to see and mutate every row regardless of authorId.

    The @mastra/core patch regenerates permissions.generated.ts to include the auth and infrastructure resources that already had routes on main.

  • Agent Builder action routes (/agent-builder/*) are now registered automatically through the standard server route pipeline. Any adapter built on @mastra/server (Hono, Express, Fastify, Koa, etc.) serves the 15 /agent-builder/* endpoints without consumers wiring them manually. (#17085)

    Example

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
    
    // `/agent-builder/*` routes are now reachable out-of-the-box
    const actions = await client.getAgentBuilderActions();
    
    const action = client.getAgentBuilderAction('generate-agent');
    const { runId } = await action.createRun();
    const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId);

    Why

    Previously, AGENT_BUILDER_ROUTES was a type-only entry in the route registry to keep @mastra/agent-builder out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of @mastra/agent-builder is preserved — handlers still resolve the workflow module on first request via dynamic import(), so Cloudflare bundles are unaffected.

    New EE permissions

    The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes:

    • agent-builder:read
    • agent-builder:write
    • agent-builder:execute

    Two legacy stream routes (STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE, OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE) are now registered through the standard pipeline as well.

Patch Changes
  • Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. (#16335)

    registerApiRoute('/stream', {
      method: 'GET',
      handler: async c => {
        const stream = await agent.stream(prompt, {
          abortSignal: c.req.raw.signal,
        });
    
        return stream.toTextStreamResponse();
      },
    });
  • Made optional memory response fields optional in server schemas and generated client types. (#17070)

  • Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. (#17045)

    Enable automatic reconnection with subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true }).

@mastra/slack@1.3.0
Minor Changes
  • Improved Slack channel UX: (#16937)

    • Flat adapter configSlackProvider now accepts per-adapter options (formatError, streaming, typingStatus, toolDisplay) directly at the top level instead of nesting under adapterConfig. The adapterConfig field still works as a deprecated fallback.
    • Breaking change — the cards: boolean and formatToolCall fields have been removed from SlackProviderConfig/SlackAdapterChannelConfig. Migrate cards: falsetoolDisplay: 'text', and formatToolCall: (info) => msgtoolDisplay: (event) => event.kind === 'result' ? { kind: 'post', message: msg } : undefined.
    • Opinionated defaultsSlackProvider now defaults streaming: true and toolDisplay: 'grouped' since the grouped "Thinking Steps" widget renders well in Slack's AI Assistant UI. When streaming: false is explicitly set, toolDisplay falls back to 'cards' since 'grouped' requires streaming. Override either per-config to restore other modes.
    • AI Assistant manifestassistant:write is now part of DEFAULT_BOT_SCOPES and the generated manifest declares the matching assistant_view feature, so newly generated app manifests support the AI Assistant surface and thread context in DMs.
    • Slack DM tool-approval routing — clicks on tool-approval cards in Slack DMs now resume the correct Mastra thread.
    import { SlackProvider } from '@mastra/slack';
    
    const slack = new SlackProvider({
      // Top-level options (preferred):
      streaming: true,
      toolDisplay: 'grouped', // or 'cards' | 'text' | 'timeline' | 'hidden' | ToolDisplayFn
    });
Patch Changes
@mastra/spanner@1.0.0
Major Changes
  • Added a Google Cloud Spanner storage adapter (@mastra/spanner) targeting the GoogleSQL dialect. The adapter implements the memory, workflows, scores, backgroundTasks, agents, mcpClients, mcpServers, skills, blobs, promptBlocks, scorerDefinitions, schedules, and observability storage domains and works with both managed Cloud Spanner instances and the local Spanner emulator. The schedules domain plugs into Mastra's built-in WorkflowScheduler for cron-driven workflow triggers, and the observability domain persists AI tracing spans in mastra_ai_spans to power the Studio traces UI (JSON containment filters compile to per-key JSON_VALUE equality and EXISTS over JSON_QUERY_ARRAY since Spanner has no @> operator). (#15955)

    import { SpannerStore } from '@mastra/spanner';
    
    const storage = new SpannerStore({
      id: 'spanner-storage',
      projectId: process.env.SPANNER_PROJECT_ID!,
      instanceId: process.env.SPANNER_INSTANCE_ID!,
      databaseId: process.env.SPANNER_DATABASE_ID!,
    });
Patch Changes
@mastra/voice-murf@0.12.1
Patch Changes
  • Replaced ky HTTP client with native fetch and built-in retry logic, removing the external dependency (#16960)
Other updated packages

The following packages were updated with dependency changes only:

FGA route policy coverage; favorites domain across storage backends

This release3 featuresNew capabilities1 enhancementImprovements to existing features4 fixesBug fixesAI-tallied from the release notes
@mastra/core@1.35.0

Highlights

FGA Route Policy Coverage + Resolver Hooks (Core/Server/WorkOS)

New FGA route policy coverage controls add guardrails for protected routes, with built-in resource route metadata resolution and resolver hooks so you can centrally map route → {resourceType, resourceId, permission} while still overriding per-route config.

Favorites Storage Domain for Agents/Skills (Core + Storage Adapters)

A new favorites storage domain lets users favorite/unfavorite stored agents and skills, and adds visibility (private|public) and favoriteCount fields so listings can be filtered and ordered by favorite state (e.g., pinFavoritedFor, favoritedOnly).

Favorites Support Across Major Storage Backends

Favorites is implemented across multiple adapters (@mastra/pg, @mastra/libsql, @mastra/mongodb, @mastra/clickhouse, @mastra/cloudflare), enabling consistent favoriting/visibility queries regardless of your chosen persistence layer.

Observational Memory: Accurate Token Estimation for Large File Parts

@mastra/memory improves local token estimation for non-image file parts (notably large PDFs), preventing observational memory from missing thresholds and replaying excessive history when provider token-count endpoints aren’t available.

Reliability Fixes for Workspaces, Workflows, and Route Matching

Multiple fixes improve correctness: workspace PATCH no longer overwrites stored values with undefined, scheduled workflows created via @mastra/core/workflows apply schedules correctly, NestJS adapter prefix matching is stricter, and a circular ESM import crash in @mastra/core/workflows/workflow is resolved.

Breaking Changes
  • None called out in this changelog.

Changelog

@mastra/core@1.35.0
Minor Changes
  • Added FGA route policy coverage controls, built-in resource route metadata resolution, and resolver hooks. (#16485)

    For example:

    import { MastraFGAWorkos } from '@mastra/auth-workos';
    import type { FGARouteConfig, FGARouteResolver, IFGAProvider } from '@mastra/core/auth/ee';
    import { createRoute } from '@mastra/server/server-adapter';
    
    const routeFGA = {
      'GET /billing/:accountId': {
        resourceType: 'account',
        resourceIdParam: 'accountId',
        permission: 'billing:read',
      },
    } satisfies Record<string, FGARouteConfig>;
    
    const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`];
    
    const fga: IFGAProvider = new MastraFGAWorkos({
      apiKey: process.env.WORKOS_API_KEY!,
      clientId: process.env.WORKOS_CLIENT_ID!,
      requireForProtectedRoutes: true,
      auditProtectedRoutes: 'warn',
      resolveRouteFGA,
      validatePermissions: async permissions => {
        /* validate mappings */
      },
    });
    
    export const getProjectRoute = createRoute({
      method: 'GET',
      path: '/projects/:projectId',
      responseType: 'json',
      requiresAuth: true,
      fga: {
        resourceType: 'project',
        resourceIdParam: 'projectId',
        permission: 'projects:read',
      },
      handler: async () => {
        return { project: null };
      },
    });
  • Added a favorites storage domain that lets users mark stored agents and skills as favorites, plus visibility ('private' | 'public') and favoriteCount fields on stored agents and skills so callers can list, filter, and order by favorite state. (#16580)

    Existing rows without visibility or favoriteCount continue to work; the new fields and APIs are opt-in.

    Example

    const favorites = await storage.getStore('favorites');
    
    await favorites?.favorite({ userId: 'u1', entityType: 'agent', entityId: 'agent-123' });
    
    const favoritedIds = await favorites?.listFavoritedIds({ userId: 'u1', entityType: 'agent' });
    
    // List agents the user has favorited, surfaced first
    const { agents } = await storage.getStore('agents').list({
      pinFavoritedFor: 'u1',
      favoritedOnly: true,
    });
Patch Changes
  • Update provider registry and model documentation with latest models and providers (b661349)

  • Fixed a workspace PATCH bug in the inmemory workspace adapter: omitted config fields in a PATCH no longer overwrite previously-persisted values with undefined. (#16580)

  • Fixed active signals so they stay in the correct order when conversations are reloaded from memory. (#16623)

  • Fixed scheduled workflows created from the public @mastra/core/workflows entry point so declared schedules are applied correctly. (#16637)

  • Fixed a crash when importing @mastra/core/workflows/workflow from tests or apps, which previously failed with TypeError: Class extends value undefined is not a constructor or null (caused by a circular ESM import through the workflows barrel). (#16661)

@mastra/auth-workos@1.4.0
Minor Changes
  • Added FGA route policy coverage controls, built-in resource route metadata resolution, and resolver hooks. (#16485)

    For example:

    import { MastraFGAWorkos } from '@mastra/auth-workos';
    import type { FGARouteConfig, FGARouteResolver, IFGAProvider } from '@mastra/core/auth/ee';
    import { createRoute } from '@mastra/server/server-adapter';
    
    const routeFGA = {
      'GET /billing/:accountId': {
        resourceType: 'account',
        resourceIdParam: 'accountId',
        permission: 'billing:read',
      },
    } satisfies Record<string, FGARouteConfig>;
    
    const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`];
    
    const fga: IFGAProvider = new MastraFGAWorkos({
      apiKey: process.env.WORKOS_API_KEY!,
      clientId: process.env.WORKOS_CLIENT_ID!,
      requireForProtectedRoutes: true,
      auditProtectedRoutes: 'warn',
      resolveRouteFGA,
      validatePermissions: async permissions => {
        /* validate mappings */
      },
    });
    
    export const getProjectRoute = createRoute({
      method: 'GET',
      path: '/projects/:projectId',
      responseType: 'json',
      requiresAuth: true,
      fga: {
        resourceType: 'project',
        resourceIdParam: 'projectId',
        permission: 'projects:read',
      },
      handler: async () => {
        return { project: null };
      },
    });
Patch Changes
  • Fixed WorkOS FGA missing resources so authorization checks deny access instead of surfacing provider errors. (#16485)
@mastra/clickhouse@1.8.0
Minor Changes
  • Added favorites support to storage adapters so callers can favorite/unfavorite stored agents and skills, query favorite state alongside list results, and filter listings by visibility. (#16580)

    Example

    const storage = new LibSQLStore({
      /* config */
    });
    const favorites = await storage.getStore('favorites');
    
    await favorites?.favorite({
      userId: 'user-1',
      entityType: 'agent',
      entityId: 'agent-42',
    });
Patch Changes
  • Bumped @mastra/core peer dependency floor to >=1.34.0-0 so the new @mastra/core/storage/domains/favorites subpath is available. Older @mastra/core versions don't ship the FavoritesStorage base class these adapters now extend. (#16580)
@mastra/cloudflare@1.4.0
Minor Changes
  • Added favorites support to storage adapters so callers can favorite/unfavorite stored agents and skills, query favorite state alongside list results, and filter listings by visibility. (#16580)

    Example

    const storage = new LibSQLStore({
      /* config */
    });
    const favorites = await storage.getStore('favorites');
    
    await favorites?.favorite({
      userId: 'user-1',
      entityType: 'agent',
      entityId: 'agent-42',
    });
Patch Changes
  • Bumped @mastra/core peer dependency floor to >=1.34.0-0 so the new @mastra/core/storage/domains/favorites subpath is available. Older @mastra/core versions don't ship the FavoritesStorage base class these adapters now extend. (#16580)
@mastra/libsql@1.11.0
Minor Changes
  • Added favorites support to storage adapters so callers can favorite/unfavorite stored agents and skills, query favorite state alongside list results, and filter listings by visibility. (#16580)

    Example

    const storage = new LibSQLStore({
      /* config */
    });
    const favorites = await storage.getStore('favorites');
    
    await favorites?.favorite({
      userId: 'user-1',
      entityType: 'agent',
      entityId: 'agent-42',
    });
Patch Changes
  • Bumped @mastra/core peer dependency floor to >=1.34.0-0 so the new @mastra/core/storage/domains/favorites subpath is available. Older @mastra/core versions don't ship the FavoritesStorage base class these adapters now extend. (#16580)

  • Fixed a workspace PATCH bug: omitted config fields in a PATCH no longer overwrite previously-persisted values with undefined. (#16580)

@mastra/memory@1.18.2
Patch Changes
  • Fixed thread deletion so it also clears thread-scoped observational memory. (#16628)

  • Fixed Observational Memory missing the observation threshold for messages with large file parts. Previously TokenCounter's local/sync counting path only stringified the file descriptor (type, mimeType, filename) for non-image files, so a 100KB PDF looked like ~8 tokens to OM and the conversation kept replaying the full unobserved history past every reasonable threshold. (#16562)

    TokenCounter now estimates non-image file part tokens locally from the attachment's byte size and mime type using a per-provider heuristic, mirroring the existing local image-token estimator:

    • Anthropic PDFs ≈ bytes / 3 (floor 1500)
    • Google PDFs ≈ bytes / 20 (floor 258)
    • OpenAI / unknown PDFs ≈ bytes / 4 (floor 500)
    • Text-ish mime types (text/*, JSON, XML, YAML) ≈ bytes / 4
    • Other binary ≈ bytes / 4

    URL-only file parts (no body to size) keep the previous descriptor-only local estimate. countMessagesAsync() continues to prefer provider token-count endpoints for supported providers; this change only improves the local fallback used when no provider endpoint is available.

    // Before: this PDF counted as ~8 tokens locally regardless of size, so OM never triggered.
    const part = {
      type: 'file',
      data: largePdfBase64,
      mimeType: 'application/pdf',
      filename: 'report.pdf',
    };
    // counter.countMessage(message) ≈ 8
    
    // After: estimated locally from byte size on the active provider.
    // counter.countMessage(message) ≈ tens of thousands of tokens
    //   → OM threshold trips as expected.

    The internal token-estimate cache version was bumped, which invalidates persisted estimates from older @mastra/memory releases on the next read; entries are recomputed automatically.

    Fixes https://github.com/mastra-ai/mastra/issues/16522

@mastra/mongodb@1.9.0
Minor Changes
  • Added favorites support to storage adapters so callers can favorite/unfavorite stored agents and skills, query favorite state alongside list results, and filter listings by visibility. (#16580)

    Example

    const storage = new LibSQLStore({
      /* config */
    });
    const favorites = await storage.getStore('favorites');
    
    await favorites?.favorite({
      userId: 'user-1',
      entityType: 'agent',
      entityId: 'agent-42',
    });
Patch Changes
  • Bumped @mastra/core peer dependency floor to >=1.34.0-0 so the new @mastra/core/storage/domains/favorites subpath is available. Older @mastra/core versions don't ship the FavoritesStorage base class these adapters now extend. (#16580)
@mastra/nestjs@0.1.6
Patch Changes
  • Fixed NestJS route matching so configured prefixes are enforced and partial prefix matches are ignored. For example, a prefix of /api no longer matches /apiish/agents; only /api and /api/* are treated as Mastra routes. (#16637)
@mastra/pg@1.11.0
Minor Changes
  • Added favorites support to storage adapters so callers can favorite/unfavorite stored agents and skills, query favorite state alongside list results, and filter listings by visibility. (#16580)

    Example

    const storage = new LibSQLStore({
      /* config */
    });
    const favorites = await storage.getStore('favorites');
    
    await favorites?.favorite({
      userId: 'user-1',
      entityType: 'agent',
      entityId: 'agent-42',
    });
Patch Changes
  • Fixed a workspace PATCH bug: omitted config fields in a PATCH no longer overwrite previously-persisted values with undefined. (#16580)

  • Bumped @mastra/core peer dependency floor to >=1.34.0-0 so the new @mastra/core/storage/domains/favorites subpath is available. Older @mastra/core versions don't ship the FavoritesStorage base class these adapters now extend. (#16580)

@mastra/playground-ui@28.0.1
Patch Changes
  • Fixed the Observability traces page on storage providers that don't support the Branches list mode. The page now falls back to Traces mode, hides the Branches option in the filter, and shows a dismissible notice instead of a full-screen error. (#16601)

  • Improved the Observability traces list to make the with-subtraces view more discoverable. (#16643)

    • Added: A Level column whose icon distinguishes top-level Trace rows from nested Subtrace rows.
    • Added: A tooltip legend on the Level header showing both icons side by side.
    • Added: A standalone Show subtraces toggle next to Add Filter — off keeps the default top-level view, on includes subtraces.
    • Removed: The List mode entry from the Add Filter menu (now driven by the toggle).

    Usage: Open Observability → Traces → switch Show subtraces on. The Level column updates: top-level rows keep the Trace icon, nested rows show the Subtrace (↳) icon. The toggle is hidden automatically when the active storage provider doesn't support subtraces.

  • Removed the "Group traces by thread" option from the Observability traces page. The list now always displays a flat view of traces, without thread-id subheaders. To narrow results to a specific thread, use the Thread ID property in the Add filter menu (open Observability → Traces → Add filter → Thread ID → paste the threadId). (#16633)

  • Polished the property filter dropdown so the chevron icon next to each option keeps the same shape when its side panel opens (#16635)

@mastra/server@1.35.0
Minor Changes
  • Added FGA route policy coverage controls, built-in resource route metadata resolution, and resolver hooks. (#16485)

    For example:

    import { MastraFGAWorkos } from '@mastra/auth-workos';
    import type { FGARouteConfig, FGARouteResolver, IFGAProvider } from '@mastra/core/auth/ee';
    import { createRoute } from '@mastra/server/server-adapter';
    
    const routeFGA = {
      'GET /billing/:accountId': {
        resourceType: 'account',
        resourceIdParam: 'accountId',
        permission: 'billing:read',
      },
    } satisfies Record<string, FGARouteConfig>;
    
    const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`];
    
    const fga: IFGAProvider = new MastraFGAWorkos({
      apiKey: process.env.WORKOS_API_KEY!,
      clientId: process.env.WORKOS_CLIENT_ID!,
      requireForProtectedRoutes: true,
      auditProtectedRoutes: 'warn',
      resolveRouteFGA,
      validatePermissions: async permissions => {
        /* validate mappings */
      },
    });
    
    export const getProjectRoute = createRoute({
      method: 'GET',
      path: '/projects/:projectId',
      responseType: 'json',
      requiresAuth: true,
      fga: {
        resourceType: 'project',
        resourceIdParam: 'projectId',
        permission: 'projects:read',
      },
      handler: async () => {
        return { project: null };
      },
    });
Patch Changes
Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.34.0

Highlights

ACP Coding Agents as Tools or Subagents (New @mastra/acp)

New @mastra/acp@0.1.0 lets you run ACP-compatible coding agents as Mastra tools (createACPTool) or lightweight subagents (AcpAgent) with incremental streaming, usable anywhere Mastra accepts SubAgent (supervisors, workflows, and the Inngest adapter).

Realtime xAI Voice Provider (New @mastra/voice-xai-realtime)

New @mastra/voice-xai-realtime@0.1.0 adds a realtime voice integration for the xAI Grok Voice Agent API, enabling Agents to connect, stream audio, and run voice turns via XAIRealtimeVoice.

Agent Metadata for Filtering, Cloning, and Dynamic Resolution

Agents can now include optional metadata (static or DynamicArgument) retrievable via agent.getMetadata(), surfaced in /agents responses for client-side filtering, and preserved when cloning agents via the editor (unless overridden).

Enterprise Foundations for Admin Policies + Role-Aware Capabilities

Two new entry points (@mastra/core/agent-builder/ee and @mastra/core/auth/ee) provide building blocks for model allowlists/admin model policies and RBAC enhancements (optional IRBACProvider.getAvailableRoles() / getPermissionsForRole()), with @mastra/auth-workos implementing these methods.

Better File Handling in workspace.read_file (Native Media Parts + Safer Binaries)

read_file now returns supported media (images/PDF) as native model file/image parts (with configurable mediaTypes and maxMediaBytes), keeps text files as text, and avoids dumping unsupported/oversized binaries as base64 by returning metadata unless an explicit encoding is requested.

Observability: Lightweight Trace Listing API + Improved Retry Visibility

New GET /observability/traces/light (plus ClickHouse/DuckDB/server/client support) enables paginated trace lists without span payloads, and background-task retries now appear as separate workflow steps in run history/event traces for easier debugging.

Breaking Changes
  • None noted in this changelog.

Changelog

@mastra/core@1.34.0
Minor Changes
  • You can now run ACP-compatible coding agents as Mastra tools or lightweight subagents. ACP agents support incremental response streaming and can be used anywhere Mastra accepts a SubAgent, including supervisor delegation and workflow steps. (#16423)

    import { createACPTool, AcpAgent } from '@mastra/acp';
    
    export const codingTool = createACPTool({
      id: 'coding-agent',
      command: 'my-acp-agent',
    });
    
    export const codingAgent = new AcpAgent({
      id: 'coding-agent',
      command: 'my-acp-agent',
    });

    You can also wire an AcpAgent into a supervisor or workflow as a SubAgent-compatible implementation:

    import { Agent } from '@mastra/core/agent';
    
    export const supervisor = new Agent({
      name: 'supervisor',
      instructions: 'Delegate coding tasks to the ACP agent.',
      model,
      agents: {
        codingAgent,
      },
    });

    Workflows and the Inngest workflow adapter now recognize SubAgent-compatible implementations when creating agent-backed workflow steps.

  • Added optional metadata to code-defined agents. Pass a metadata record to new Agent({...}), read it back with agent.getMetadata(), and clients can filter on it from the existing /agents and /agents/:agentId responses without encoding the data into IDs or names. (#16603)

    Metadata supports the same DynamicArgument form as other agent config fields, so it can also be resolved per request from the request context.

    Stored agents loaded via the editor also expose their metadata through agent.getMetadata(), so clients can filter these agents as well. Cloning a runtime agent via editor.agent.clone() now carries the source agent's metadata over to the stored clone when the caller does not provide one explicitly.

    // Static
    const supportAgent = new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: 'You help customers with support requests.',
      model: 'openai/gpt-5',
      metadata: { type: 'support' },
    });
    
    supportAgent.getMetadata(); // { type: 'support' }
    
    // Dynamic
    const tenantAgent = new Agent({
      id: 'tenant-agent',
      name: 'Tenant Agent',
      instructions: 'You help customers with tenant-specific tasks.',
      model: 'openai/gpt-5',
      metadata: ({ requestContext }) => ({
        type: 'support',
        tenant: requestContext.get('tenant'),
      }),
    });
    
    await tenantAgent.getMetadata({ requestContext }); // { type: 'support', tenant: 'acme' }
  • Added an opt-in foundation for building agent-builder admin policies and role-aware capabilities, available under two new entry points. (#16578)

    @mastra/core/agent-builder/ee

    Exposes types, validators, and picker utilities for working with model allowlists and admin model policies on stored agents — for example normalizing model candidates, choosing a default from a configured allowlist, and producing typed errors when a request violates policy.

    @mastra/core/auth/ee

    Adds optional methods on IRBACProvider for listing available roles and resolving the permissions for a given role:

    interface IRBACProvider {
      // existing methods...
      getAvailableRoles?(): Promise<RoleDescriptor[]>;
      getPermissionsForRole?(role: string): Promise<PermissionDescriptor[]>;
    }

    Static defaults, an expanded permissions catalog, and a capabilities helper that surfaces availableRoles to clients when the provider supports it are also included. Providers that do not implement the new methods continue to work unchanged.

    Also adds a StorageBrowserRef shape to @mastra/core/storage for referencing a configured headless browser on stored agents.

  • Improved how the workspace read_file tool returns files to the model. Reads now branch on file type: (#16570)

    1. Media files (default: image/png, image/jpeg, image/webp, application/pdf) are surfaced as native file/image parts the model can directly view, instead of being dumped as base64 text. Capped at 10 MiB by default so large media don't get base64-encoded into context and persisted in storage — configurable via maxMediaBytes.
    2. Text-readable files (anything text/*, common code/config mime types, or unknown extensions) are returned as text content as before.
    3. Unsupported binaries (e.g. image/png when mediaTypes is disabled, application/zip, oversized media, etc.) now return a short metadata description (path, size, mime type) instead of dumping useless base64 into the conversation. Pass an explicit encoding to opt back into the raw base64/hex dump.

    The set of mime types treated as native media parts and the inline size cap are configurable per workspace:

    import { Workspace, WORKSPACE_TOOLS } from '@mastra/core/workspace';
    
    const workspace = new Workspace({
      filesystem,
      tools: {
        [WORKSPACE_TOOLS.FILESYSTEM.READ_FILE]: {
          // Broaden to any image (e.g. SVG, BMP, HEIC) — may fail on some providers
          mediaTypes: ['image/*', 'application/pdf'],
    
          // Raise the inline-media cap to 25 MiB
          maxMediaBytes: 25 * 1024 * 1024,
    
          // Or a custom predicate
          // mediaTypes: (mime) => mime.startsWith('image/'),
    
          // Or disable media parts entirely
          // mediaTypes: false,
        },
      },
    });

    The default mediaTypes is intentionally the cross-provider-safe intersection — formats universally supported across Anthropic, OpenAI, and Gemini.

  • Changed background process output retention. (#16574)

    Before: Spawned process handles retained all stdout and stderr, which could grow without bound for long-running background processes.

    After: Spawned process handles now retain the latest 1 MiB of stdout and stderr per stream by default. Pass maxRetainedBytes to processes.spawn() to customize the limit, use 0 to disable retained polling output, or use Infinity to keep the previous retain-all behavior.

    const handle = await sandbox.processes.spawn('npm run dev', {
      maxRetainedBytes: 512 * 1024,
    });

    Streaming callbacks and reader streams still receive every chunk in full. Handles also expose truncation and dropped-byte counters so callers can detect when stdout, stderr, or wait() results only include retained output.

    The built-in executeCommand() implementation still retains full output by default; pass maxRetainedBytes there only when you want bounded command results.

Patch Changes
  • Update provider registry and model documentation with latest models and providers (784ad98)

  • Improved background-task observability. (#16590)

    Retry attempts now appear as separate workflow steps in run history and event traces, making retry progression easier to debug. No public API changes.

  • Fixed approval resume for tools loaded by processor workflows. (#16365)

  • Fixed durable agents that could drop object-form system instructions when provider options like cacheControl were used. These instructions are now preserved so provider-specific options are respected. (#16599)

  • Fixed approval resume for tools loaded with ToolSearchProcessor. (#16365)

  • Fixed listMessages perPage=0 behavior in the in-memory store to match other adapters. (#16602)

  • Fixed non-deterministic ordering of cross-thread semantic recall messages. (#16600)

    When messages recalled from other threads shared the same timestamp, they were rendered into the system prompt in whatever order the vector query returned them — driven by similarity scores that can vary between equivalent runs. This made any test or evaluation that snapshots prompt output (or hashes the outbound LLM request) flaky.

    Recalled cross-thread messages are now sorted by createdAt, then threadId, then role (user → assistant → tool → system), then id before formatting, so the same set of recalled messages always produces the same prompt.

  • Expose GET /observability/traces/light and storage support for fetching paginated trace-list rows without span payload data. (#16608)

  • GET /api/observability/discovery/metric-names and GET /api/observability/discovery/metric-label-values now accept limit as a URL query parameter without pre-parsing. Previously, passing ?limit=10 was rejected as a validation error; callers can now use these endpoints directly from HTTP clients, consistent with other query endpoints (e.g. pagination). (#16489)

  • Fixed agent signals so standalone agents coordinate thread streams through a shared runtime. (#16581)

@mastra/acp@0.1.0
Minor Changes
  • You can now run ACP-compatible coding agents as Mastra tools or lightweight subagents. ACP agents support incremental response streaming and can be used anywhere Mastra accepts a SubAgent, including supervisor delegation and workflow steps. (#16423)

    import { createACPTool, AcpAgent } from '@mastra/acp';
    
    export const codingTool = createACPTool({
      id: 'coding-agent',
      command: 'my-acp-agent',
    });
    
    export const codingAgent = new AcpAgent({
      id: 'coding-agent',
      command: 'my-acp-agent',
    });

    You can also wire an AcpAgent into a supervisor or workflow as a SubAgent-compatible implementation:

    import { Agent } from '@mastra/core/agent';
    
    export const supervisor = new Agent({
      name: 'supervisor',
      instructions: 'Delegate coding tasks to the ACP agent.',
      model,
      agents: {
        codingAgent,
      },
    });

    Workflows and the Inngest workflow adapter now recognize SubAgent-compatible implementations when creating agent-backed workflow steps.

Patch Changes
@mastra/auth-workos@1.3.0
Minor Changes
  • Added optional getAvailableRoles and getPermissionsForRole methods to the WorkOS RBAC provider, so consumers can list configured roles and inspect their permissions through @mastra/auth-workos. (#16578)

    import { MastraRBACWorkos } from '@mastra/auth-workos';
    
    const rbac = new MastraRBACWorkos({
      /* config */
    });
    
    // List all available roles
    const roles = await rbac.getAvailableRoles();
    // [{ id: 'admin', name: 'Admin' }, { id: 'member', name: 'Member' }]
    
    // Get permissions for a specific role
    const permissions = await rbac.getPermissionsForRole('member');
    // ['agents:read', 'workflows:read']
Patch Changes
@mastra/clickhouse@1.7.2
Patch Changes
  • Expose GET /observability/traces/light and storage support for fetching paginated trace-list rows without span payload data. (#16608)
@mastra/client-js@1.19.0
Minor Changes
  • Added optional metadata to code-defined agents. Pass a metadata record to new Agent({...}), read it back with agent.getMetadata(), and clients can filter on it from the existing /agents and /agents/:agentId responses without encoding the data into IDs or names. (#16603)

    Metadata supports the same DynamicArgument form as other agent config fields, so it can also be resolved per request from the request context.

    Stored agents loaded via the editor also expose their metadata through agent.getMetadata(), so clients can filter these agents as well. Cloning a runtime agent via editor.agent.clone() now carries the source agent's metadata over to the stored clone when the caller does not provide one explicitly.

    // Static
    const supportAgent = new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: 'You help customers with support requests.',
      model: 'openai/gpt-5',
      metadata: { type: 'support' },
    });
    
    supportAgent.getMetadata(); // { type: 'support' }
    
    // Dynamic
    const tenantAgent = new Agent({
      id: 'tenant-agent',
      name: 'Tenant Agent',
      instructions: 'You help customers with tenant-specific tasks.',
      model: 'openai/gpt-5',
      metadata: ({ requestContext }) => ({
        type: 'support',
        tenant: requestContext.get('tenant'),
      }),
    });
    
    await tenantAgent.getMetadata({ requestContext }); // { type: 'support', tenant: 'acme' }
Patch Changes
  • Expose GET /observability/traces/light and storage support for fetching paginated trace-list rows without span payload data. (#16608)
@mastra/docker@0.2.0
Minor Changes
  • Docker sandbox containers now support resource limits and security hardening through Docker HostConfig options. Configure memory, CPU quota, process IDs, capabilities, security options, read-only root filesystems, and tmpfs mounts. (#16577)

    const sandbox = new DockerSandbox({
      memory: 512 * 1024 * 1024,
      memorySwap: 512 * 1024 * 1024,
      cpuQuota: 100_000,
      pidsLimit: 256,
      readonlyRootfs: true,
      capDrop: ['ALL'],
      securityOpt: ['no-new-privileges:true'],
    });
Patch Changes
@mastra/duckdb@1.3.2
Patch Changes
  • Expose GET /observability/traces/light and storage support for fetching paginated trace-list rows without span payload data. (#16608)
@mastra/editor@0.8.0
Minor Changes
  • Added optional metadata to code-defined agents. Pass a metadata record to new Agent({...}), read it back with agent.getMetadata(), and clients can filter on it from the existing /agents and /agents/:agentId responses without encoding the data into IDs or names. (#16603)

    Metadata supports the same DynamicArgument form as other agent config fields, so it can also be resolved per request from the request context.

    Stored agents loaded via the editor also expose their metadata through agent.getMetadata(), so clients can filter these agents as well. Cloning a runtime agent via editor.agent.clone() now carries the source agent's metadata over to the stored clone when the caller does not provide one explicitly.

    // Static
    const supportAgent = new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: 'You help customers with support requests.',
      model: 'openai/gpt-5',
      metadata: { type: 'support' },
    });
    
    supportAgent.getMetadata(); // { type: 'support' }
    
    // Dynamic
    const tenantAgent = new Agent({
      id: 'tenant-agent',
      name: 'Tenant Agent',
      instructions: 'You help customers with tenant-specific tasks.',
      model: 'openai/gpt-5',
      metadata: ({ requestContext }) => ({
        type: 'support',
        tenant: requestContext.get('tenant'),
      }),
    });
    
    await tenantAgent.getMetadata({ requestContext }); // { type: 'support', tenant: 'acme' }
Patch Changes
  • Fixed stored agent tool overrides so conditional tool configuration no longer recursively calls the forked agent when merging code-defined tools. (#16544)
@mastra/inngest@1.4.1
Patch Changes
  • You can now run ACP-compatible coding agents as Mastra tools or lightweight subagents. ACP agents support incremental response streaming and can be used anywhere Mastra accepts a SubAgent, including supervisor delegation and workflow steps. (#16423)

    import { createACPTool, AcpAgent } from '@mastra/acp';
    
    export const codingTool = createACPTool({
      id: 'coding-agent',
      command: 'my-acp-agent',
    });
    
    export const codingAgent = new AcpAgent({
      id: 'coding-agent',
      command: 'my-acp-agent',
    });

    You can also wire an AcpAgent into a supervisor or workflow as a SubAgent-compatible implementation:

    import { Agent } from '@mastra/core/agent';
    
    export const supervisor = new Agent({
      name: 'supervisor',
      instructions: 'Delegate coding tasks to the ACP agent.',
      model,
      agents: {
        codingAgent,
      },
    });

    Workflows and the Inngest workflow adapter now recognize SubAgent-compatible implementations when creating agent-backed workflow steps.

@mastra/memory@1.18.1
Patch Changes
  • Added a public escape hatch so callers can supply an authoritative token estimate for file parts whose binary payload has been stripped before persistence (for example, files uploaded to cloud storage with a hidden reference token left in data and re-hydrated by LLM middleware before inference). (#16565)

    For those pipelines TokenCounter has no on-device file size to measure, so Observational Memory thresholds and context budgets undercount large attachments. Callers can now stamp an estimate directly on the part:

    part.providerMetadata = {
      mastra: {
        tokenEstimate: { v: 0, source: 'client', key: 'client', tokens: 25_000 },
      },
    };

    When present, TokenCounter returns those tokens from both the sync and async paths and skips provider fetches. Invalid entries (NaN, negative, non-numeric) fall through to the default estimator. Parts without a client estimate are unaffected.

    Related to https://github.com/mastra-ai/mastra/issues/16522

@mastra/nestjs@0.1.5
Patch Changes
  • Fixed @mastra/nestjs coercing query parameter values to booleans, null, numbers, and parsed JSON objects/arrays before route schema validation. A route declaring queryParamSchema: z.object({ filter: z.string() }) could reject a valid request like ?filter={"a":1} because the adapter had already turned the string into an object. NestJS now forwards query values as the raw strings (or string arrays) the HTTP layer delivered — matching @mastra/hono, @mastra/express, @mastra/fastify, and @mastra/koa. (#16268)

    Routes that want type coercion should opt in via the schema, e.g. z.coerce.boolean(), z.coerce.number(), or a JSON preprocessor on the field.

    Fixes #16114.

@mastra/playground-ui@28.0.0
Minor Changes
  • Added a List mode filter to the Observability traces page for switching between Traces and Branches mode, and changed the default to Traces mode. Previously, the page opened in Branches mode; now it opens in Traces mode, and users can switch modes via the new "List mode" property in the Add filter menu. For example: open Observability → Traces (now defaults to Traces) → Add filter → List mode → pick Branches or Traces. (#16587)
Patch Changes
  • Removed the inset top gloss from the shadow-dialog token. The gloss read as a faint highlight band along the top edge of dropdown menus, popovers, selects, comboboxes, dialogs, tooltips, side dialogs and the main app container in dark mode. The token now applies a drop-shadow only and is consistent across light and dark themes. (#16544)

  • Restyled MainSidebar and swapped sidebar icons to the new Figma design system set. (#16544)

    • Section titles are larger and medium-weight (text-ui-sm, font-medium), lowercase, muted — replacing the previous uppercase + wide-tracking treatment. Underline divider beneath the title removed in both expanded and collapsed states. Active indicator bar on the left edge removed.
    • Nav items render flush: icons align horizontally with the section title, hover/active state now uses theme-aware sidebar surface tokens without item borders or shadows. The legacy indent option is still accepted but no longer changes layout.
    • New sidebar icons: WorkspacesIcon, RequestContextIcon, ScorersIcon, DatasetsIcon, ExperimentsIcon, MetricsIcon. Existing icons AgentIcon, PromptIcon, WorkflowIcon, ProcessorIcon, McpServerIcon, ToolsIcon, LogsIcon, TraceIcon updated to match the Figma artwork. All icons accept React.SVGProps<SVGSVGElement> and inherit color via currentColor.
  • Improved Studio main content framing, persistent page breadcrumbs, accessible page headings, panel layering, and theme-aware navigation/card contrast. (#16544)

  • Fixed a React/React DOM version mismatch that prevented Storybook from rendering in @mastra/playground-ui. (#16529)

@mastra/server@1.34.0
Minor Changes
  • Added optional metadata to code-defined agents. Pass a metadata record to new Agent({...}), read it back with agent.getMetadata(), and clients can filter on it from the existing /agents and /agents/:agentId responses without encoding the data into IDs or names. (#16603)

    Metadata supports the same DynamicArgument form as other agent config fields, so it can also be resolved per request from the request context.

    Stored agents loaded via the editor also expose their metadata through agent.getMetadata(), so clients can filter these agents as well. Cloning a runtime agent via editor.agent.clone() now carries the source agent's metadata over to the stored clone when the caller does not provide one explicitly.

    // Static
    const supportAgent = new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: 'You help customers with support requests.',
      model: 'openai/gpt-5',
      metadata: { type: 'support' },
    });
    
    supportAgent.getMetadata(); // { type: 'support' }
    
    // Dynamic
    const tenantAgent = new Agent({
      id: 'tenant-agent',
      name: 'Tenant Agent',
      instructions: 'You help customers with tenant-specific tasks.',
      model: 'openai/gpt-5',
      metadata: ({ requestContext }) => ({
        type: 'support',
        tenant: requestContext.get('tenant'),
      }),
    });
    
    await tenantAgent.getMetadata({ requestContext }); // { type: 'support', tenant: 'acme' }
Patch Changes
  • Expose GET /observability/traces/light and storage support for fetching paginated trace-list rows without span payload data. (#16608)

  • GET /api/observability/discovery/metric-names and GET /api/observability/discovery/metric-label-values now accept limit as a URL query parameter without pre-parsing. Previously, passing ?limit=10 was rejected as a validation error; callers can now use these endpoints directly from HTTP clients, consistent with other query endpoints (e.g. pagination). (#16489)

  • Fixed agent signal wakeups so idle runs receive request context values while preserving server-owned resource identifiers. (#16557)

@mastra/voice-xai-realtime@0.1.0
Minor Changes
  • Added @mastra/voice-xai-realtime, a realtime voice provider for the xAI Grok Voice Agent API. (#16507)

    Use XAIRealtimeVoice with Mastra's Agent voice primitive to connect, stream audio, and run xAI voice turns:

    import { Agent } from '@mastra/core/agent';
    import { XAIRealtimeVoice } from '@mastra/voice-xai-realtime';
    
    const voice = new XAIRealtimeVoice({
      apiKey: process.env.XAI_API_KEY,
      model: 'grok-voice-think-fast-1.0',
      speaker: 'eve',
      turnDetection: { type: 'server_vad' },
    });
    
    const agent = new Agent({
      id: 'voice-agent',
      name: 'Voice Agent',
      instructions: 'You are a helpful voice assistant.',
      model: 'xai/grok-4.3',
      voice,
    });
    
    await agent.voice.connect();
    agent.voice.on('speaker', audioStream => playAudio(audioStream));
    await agent.voice.speak('How can I help you today?');
    await agent.voice.send(microphoneStream);
Patch Changes
Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.33.0

Highlights

Push-capable PubSub + HTTP workflow event ingestion

Mastra now supports push delivery for workflow events: PubSubs declare supportedModes, Mastra.handleWorkflowEvent(event) is the unified entry point, and servers expose POST /api/workflows/events so brokers like GCP Pub/Sub push/SNS/EventBridge can deliver events over HTTP (no pull worker required).

Response caching for identical LLM steps (ResponseCache)

A new opt-in ResponseCache input processor can skip model calls by replaying cached Responses per step in agent loops, with per-request overrides (key, scope, bust) via RequestContext. Adds InMemoryServerCache for local dev and per-entry TTL support in the cache interface (also implemented by @mastra/redis).

Agent Signals for “send while streaming” threaded chat

Agents can now accept contextual signals mid-run (agent.sendSignal) and clients can follow thread activity via agent.subscribeToThread, enabling reliable follow-ups while responses stream. This is supported end-to-end across @mastra/server, @mastra/client-js, and @mastra/react (stable signal IDs, dedupe, stop/abort behavior, and thread subscriptions).

Azure OpenAI Responses API (v1 routing) + WebSocket streaming transport

AzureOpenAIGateway now supports the Azure OpenAI Responses API with v1 routing controls (useResponsesAPI: true) and adds WebSocket transport for streaming agent/tool loops, improving streaming reliability and supporting real-time tool loop execution over WS.

New storage and integration options: Aurora DSQL + Bright Data tools

Introduces @mastra/dsql (Amazon Aurora DSQL storage with IAM auth) for persisting threads/workflows/observability, and @mastra/brightdata with brightdata-search/brightdata-fetch tools to bypass bot detection/CAPTCHAs for SERP + page fetching.

Observability upgrades: MODEL_INFERENCE spans, eval score unification, and OTEL logs

Adds MODEL_INFERENCE spans under MODEL_STEP to isolate pure provider latency, and routes Mastra Eval/scorer results through the unified score pipeline (and forwards them to multiple exporters). OTEL integrations now support log forwarding/export (@mastra/otel-bridge, @mastra/otel-exporter), and SensitiveDataFilter is applied by default to prevent secret leakage.

Breaking Changes
  • @mastra/inngest now requires inngest@^4 (and Inngest Dev Server v1.18.0+); remove @inngest/realtime and import realtime helpers from inngest/realtime.
  • @mastra/client-js: paginated list methods now require orderBy: { field, direction } (flat sortDirection removed from affected param types); AgentBuilder.stream(...) now requires runId; createStoredSkill(...) now requires description.
  • @mastra/playground-ui: ScrollArea now fades edges by default (opt out with mask={false}); removed exports Threads, ThreadList, ThreadItem, ThreadLink, ThreadDeleteButton (no longer available downstream).

Changelog

@mastra/core@1.33.0
Minor Changes
  • Added processLLMRequest, a processor hook that runs after messages are converted to the provider-facing prompt and before the model request is sent. The hook lets processors make temporary, model-aware prompt changes without mutating stored message history, memory, UI history, or later provider calls. (#16176)

    ProviderHistoryCompat now uses this hook to prevent reasoning-history incompatibilities when switching providers. It strips reasoning parts from Cerebras-bound prompts that would otherwise be sent as rejected reasoning_content, and strips non-Anthropic reasoning from Anthropic-bound prompts while preserving Anthropic-native thinking blocks.

  • Improved Harness support for Agent thread signals. (#16231)

    Harness thread subscriptions now own stream processing for followed runs, echo user-message signal data with stable IDs, and support idle signal starts without delaying optimistic rendering.

    const { id, accepted } = harness.sendSignal({
      type: 'user-message',
      contents: 'Follow up while the agent is still streaming',
    });
    await accepted;
  • Added target-aware tool payload transforms for display streams and transcript messages. Tool authors can transform tool input, output, errors, approval payloads, and suspension payloads without changing raw runtime behavior or toModelOutput. See https://github.com/mastra-ai/mastra/issues/16054. (#16103)

    Use transform on tools, agents, Mastra, or individual generation calls to configure these payload transforms. Runtime callers using the previous toolPayloadProjection shape continue to be normalized for compatibility.

    const lookupCustomer = createTool({
      execute: async ({ customerId, internalPath }) => lookupCustomerRecord(customerId, internalPath),
      transform: {
        display: {
          input: ({ input }) => ({ customerId: input?.customerId }),
          output: ({ output }) => ({ displayName: output?.displayName }),
        },
        transcript: {
          input: ({ input }) => ({ customerId: input?.customerId }),
          output: ({ output }) => ({ displayName: output?.displayName }),
        },
      },
    });
  • Added Azure OpenAI Responses API and v1 routing controls. (#16246)

    Use useResponsesAPI: true to resolve Azure deployments through the Responses API with the Azure v1 route by default:

    new AzureOpenAIGateway({
      resourceName: 'my-openai-resource',
      apiKey: process.env.AZURE_API_KEY!,
      useResponsesAPI: true,
      deployments: ['my-gpt-5-4-deployment'],
    });

    When useDeploymentBasedUrls: false is used directly, the gateway now defaults apiVersion to "v1" to match the AI SDK Azure provider's v1 URL route. Passing apiVersion: "v1" by itself keeps the existing deployment-based URL default for compatibility.

  • Mastra Eval results are now emitted once through the unified observability score pipeline. (#16185)

  • Added Azure OpenAI Responses WebSocket transport support for streaming agent and tool loops. (#16246)

    Configure the Azure gateway with useResponsesAPI: true, then opt into WebSocket streaming per request:

    const stream = await agent.stream('Review this task', {
      providerOptions: {
        azure: {
          transport: 'websocket',
          websocket: { closeOnFinish: false },
        },
      },
    });

    Responses WebSocket streams now preserve transport handles through agent loops, reuse explicit API-key router connections safely, clean up cancelled streams, and reject overlapping previous_response_id continuations on the same connection.

  • Added preserveModelOutput to ToolCallFilter so filtered tool history can keep compact model-facing output without raw tool args or results. (#16060)

    import { ToolCallFilter } from '@mastra/core/processors';
    
    const filter = new ToolCallFilter({
      preserveModelOutput: true,
    });
  • Added a SubAgent interface for custom supervisor subagents. (#16359)

  • Added ResponseCache input processor (#16283)

    Cache identical LLM steps to skip the model call and replay a previously cached response. Useful for prompt templates, suggested-prompt buttons, agentic search re-asks, or guardrail LLMs that classify the same input over and over.

    Caching is opt-in: register ResponseCache explicitly on inputProcessors. There is no agent-level option — this keeps the surface small while we collect feedback on the processor API. Per-call overrides flow through RequestContext.

    import { Agent } from '@mastra/core/agent';
    import { InMemoryServerCache } from '@mastra/core/cache';
    import { ResponseCache } from '@mastra/core/processors';
    
    const cache = new InMemoryServerCache();
    
    const agent = new Agent({
      name: 'Search Agent',
      instructions: 'You answer questions concisely.',
      model: 'openai/gpt-5',
      inputProcessors: [new ResponseCache({ cache, ttl: 600 })],
    });
    
    // First call: cache miss → LLM call
    await agent.generate('What is the capital of France?');
    
    // Second identical call: cache hit → no LLM call
    await agent.generate('What is the capital of France?');

    Per-call overrides via RequestContext:

    import { ResponseCache } from '@mastra/core/processors';
    import { RequestContext } from '@mastra/core/request-context';
    
    // Force a fresh call but still update the cache.
    await agent.stream(prompt, {
      requestContext: ResponseCache.context({ bust: true }),
    });
    
    // Or merge into an existing context.
    const ctx = new RequestContext();
    ResponseCache.applyContext(ctx, { key: 'custom-key' });
    await agent.stream(prompt, { requestContext: ctx });

    Three fields are overridable per call: key, scope, bust. cache, ttl, and agentId stay on the constructor.

    A key function receives { agentId, scope, model, prompt, stepNumber } and returns a string (or Promise<string>):

    await agent.stream(prompt, {
      requestContext: ResponseCache.context({
        key: ({ model, prompt }) => `qa:${model.modelId}:${JSON.stringify(prompt).slice(-200)}`,
      }),
    });

    The cache key is derived from the resolved prompt Mastra is about to send to the model — i.e. after memory loading and earlier input processors have run — so cached entries are tenant-isolated and don't leak context across users with shared prompts but different memory state. Each step in an agentic tool loop is independently cached. By default, the cache scope falls back to MASTRA_RESOURCE_ID_KEY from the request context for automatic per-user isolation. Failed runs (errors, tripwire activations) are not cached. See Response caching for details.

    Also adds:

    • InMemoryServerCache (in @mastra/core/cache) for local development. ResponseCache accepts any MastraServerCache directly — use RedisCache from @mastra/redis for production.
    • MastraServerCache.set() now accepts an optional ttlMs argument so implementations can override the configured default TTL on a per-entry basis. InMemoryServerCache and RedisCache (in @mastra/redis) both honor this.
    • New paired processor hooks processLLMRequest and processLLMResponse. ProcessLLMRequestResult may return { response } to short-circuit the LLM call with a cached payload.
  • Added workflow state reader helpers to inspect persisted workflow runs and recover suspended or long-running workflows. (#16091)

    The reader exposes suspended steps, resume labels, step payloads, and step outputs from the public WorkflowState returned by workflow.getWorkflowRunById(), and WorkflowState step results now reflect foreach array entries.

  • Added Agent signals for sending contextual messages into agent thread loops and subscribing to thread activity. (#16229)

    Call agent.sendSignal() to inject context into a running agent loop. When the thread is idle, that same signal becomes the prompt that starts the next loop by default. Use ifActive.behavior and ifIdle.behavior to deliver, persist, discard, or wake from a signal.

    Use agent.subscribeToThread() to follow the raw stream chunks for a memory thread, observe signal echoes with stable IDs, and abort the active stream for that thread.

    const subscription = await agent.subscribeToThread({ resourceId, threadId });
    
    void (async () => {
      for await (const part of subscription.stream) {
        if (part.type === 'finish') {
          subscription.unsubscribe();
        }
      }
    })();
    
    agent.sendSignal({ type: 'user-message', contents: 'Use the latest answer' }, { resourceId, threadId });
  • Add metadata filtering support to semantic recall. (#9256)

  • Fixed Azure and OpenAI Responses item handling so multi-step reasoning and tool-call histories round-trip correctly without item ID collisions. (#16246)

    Added provider-neutral response item helpers to @mastra/core/agent/message-list. Existing in-memory message cache entries are regenerated after upgrade.

  • Improved foreach workflow execution to keep concurrency slots filled as iterations finish. (#12860)

  • Added processor sendSignal support and routed built-in system reminders through signal messages. (#16438)

  • Added structured drop event types and an onDroppedEvent hook so exporters and bridge integrations can observe events dropped by the observability pipeline. (#16111)

  • Added stable IDs to Harness task items plus task_update and task_complete for updating or completing one tracked task by ID. Task tools now return structured task snapshots, and task_check returns summary and incompleteTasks fields so agents and UIs can restore and verify task state without parsing text. (#16254)

    Harness also exports TaskItemSnapshot, assignTaskIds, and harness.restoreDisplayTasks() for UI history replay, serializes task reads and mutations against the latest task state snapshot, and returns task-tool errors inside forked subagents so sidecar work cannot mutate parent task state.

  • Added new MODEL_INFERENCE span type under MODEL_STEP, covering only the model provider call. Use it to measure model latency separately from input/output processors and tool executions. (#16267)

  • Added experimental support for using remote A2A agents as Mastra subagents. (#16348)

    What changed

    • Mastra agents can register remote A2A endpoints through A2AAgent and delegate to them like other subagents.
    • Remote A2A subagents support generate, resumeGenerate, stream, and resumeStream so parent agents can use them in normal subagent flows.
    • Agent Cards can be cached and verified with pluggable verification hooks before remote execution begins.
    • Browser environments can import shared A2A types and errors from @mastra/core/a2a/client.

    Example

    import { Agent } from '@mastra/core/agent';
    import { A2AAgent } from '@mastra/core/a2a';
    
    const agent = new Agent({
      name: 'Support Agent',
      instructions: 'Use the remote billing specialist for billing questions.',
      model: 'openai/gpt-4o-mini',
      agents: {
        billingSpecialist: new A2AAgent({
          url: 'https://billing.example.com/.well-known/agent-card.json',
        }),
      },
    });
    
    const result = await agent.generate('Can you check the latest invoice status?');

    Why This lets Mastra agents compose with remote A2A agents without exposing those integrations as plain tools or depending directly on the client SDK.

  • Worker review fixes: (#16309)

    • Step-execution endpoint (POST /workflows/:id/runs/:runId/steps/execute) is now gated by Mastra's standard requiresAuth: true + authenticateToken pipeline rather than a parallel "worker secret" body field. The previously introduced workerSecret config knob and MASTRA_WORKER_SECRET env var have been removed (they were never released). To gate the endpoint on a standalone-worker deployment, configure an auth provider on the server's Mastra instance — without one the framework currently treats requiresAuth: true as a no-op for this route.
    • HttpRemoteStrategy now sends credentials as a normal Authorization: Bearer <token> header. The token comes from the new MASTRA_WORKER_AUTH_TOKEN env var or an explicit auth constructor option.
    • Honor the caller's abortSignal in HttpRemoteStrategy by combining it with the per-request timeout via AbortSignal.any (with a manual fallback for runtimes that don't expose it).
    • Implement comma-separated name filtering for the MASTRA_WORKERS env var. MASTRA_WORKERS=scheduler,backgroundTasks now boots only those named workers; MASTRA_WORKERS=false still disables all workers.
    • Restore Mastra.startEventEngine / stopEventEngine as @deprecated aliases for the renamed startWorkers / stopWorkers.
    • BackgroundTaskWorker now subscribes to PubSub in start() instead of init(), matching the lifecycle of the other workers and making isRunning accurately reflect subscription state.
    • RedisStreamsPubSub adds a maxDeliveryAttempts option (default 5) that drops events after the configured number of failed deliveries instead of redelivering forever, and replaces empty catch {} blocks with logger.warn/logger.debug calls.
    • RedisStreamsPubSub.unsubscribe(topic, cb) now honors the topic argument so the same callback can be subscribed to multiple topics independently.
    • PullTransport guards the async router callback against unhandled promise rejections by attaching a .catch that nacks the message.
    • Drop the dead MASTRA_WORKER_NAME env var injection in the CLI worker spawn (the bundle entrypoint already passes the worker name directly).
    • Add a real cross-process e2e auth suite (pubsub/redis-streams/src/auth-e2e.test.ts) covering happy path, wrong token, missing token, anonymous direct hits, and the no-auth-provider pin-down behavior.
    • Step-execution route now has a response schema, satisfying schema-consistency.test.ts.
    • Internal type cleanups (drop several as any casts in worker strategies and BackgroundTaskWorker).
    • RedisStreamsPubSub.maxDeliveryAttempts now rejects negative / NaN values at construction. 0 still means "no cap" for back-compat but emits a one-time warning; pass Infinity to disable the cap explicitly.
    • PullTransport accepts a logger and uses it for unhandled router-callback rejections instead of console.error.
    • BackgroundTaskWorker.start() now throws if init() was not called, matching the contract of the other workers.
    • Cross-process integration tests now spawn a single user-owned project (test-fixtures/cli-project/src/mastra/index.ts) through two generic entries that mirror what BuildBundler and WorkerBundler emit. The previous one-off server.entry.ts / worker.entry.ts / scheduler.entry.ts / background.entry.ts files have been deleted — they implied users hand-roll entry files, which they don't. Worker role is selected via MASTRA_WORKERS exactly as in production.

    Push-capable PubSub:

    • The PubSub abstract class now declares a supportedModes getter (defaulting to ['pull'] for backward compatibility) so consumers can tell whether a broker delivers events through a pull loop, an in-process push, or an out-of-process HTTP push. EventEmitterPubSub reports ['pull', 'push'] (EventEmitter dispatches synchronously and works for either path), @mastra/redis-streams reports ['pull'].
    • Mastra now exposes a public handleWorkflowEvent(event) method backed by a shared WorkflowEventProcessor. It is the single entry point used by the existing pull-mode OrchestrationWorker, by in-process push pubsubs (auto-wired during startWorkers()), and by the new POST /api/workflows/events route which lets push-mode brokers (GCP Pub/Sub push, SNS, EventBridge) deliver events over HTTP.
    • When the configured pubsub does not support 'pull', Mastra automatically skips creating an OrchestrationWorker and OrchestrationWorker.init() throws a clear error if it is constructed against a push-only pubsub.
    • WorkflowEventProcessor gains a handle(event) method that returns a structured { ok, retry } result. The original process(event, ack?) method is preserved as a thin wrapper for back-compat.

    Public-API example for a push-capable PubSub:

    import { Mastra } from '@mastra/core/mastra';
    import { EventEmitterPubSub } from '@mastra/core/pubsub';
    
    const mastra = new Mastra({
      // A push-capable broker (GCP Pub/Sub push, SNS, EventEmitter, …).
      // EventEmitterPubSub reports supportedModes = ['pull', 'push'].
      pubsub: new EventEmitterPubSub(),
      workflows: { myWorkflow },
    });
    
    // In-process push pubsubs are auto-wired here. For out-of-process
    // push (e.g. HTTP webhook from a cloud broker), POST the event to
    // /api/workflows/events on your Mastra server instead.
    await mastra.startWorkers();
    
    // Direct invocation (e.g. inside an HTTP handler that bridges from a
    // cloud broker's push delivery):
    await mastra.handleWorkflowEvent({
      id: 'evt-1',
      type: 'workflow.start',
      runId: 'run-1',
      createdAt: new Date(),
      data: { workflowId: 'myWorkflow', inputData: { name: 'world' } },
    });

    CI follow-ups:

    • Mastra only auto-registers SchedulerWorker when storage is configured. Without storage the worker would crash on startup (deps.storage.getStore on undefined); the scheduler now silently no-ops in that case, matching the pre-worker scheduler behavior.
    • SchedulerWorker.init defensively logs and returns when called without storage instead of throwing a TypeError.
    • RECEIVE_WORKFLOW_EVENT_ROUTE (POST /workflows/events) createdAt is now a plain z.string() on the wire and the handler converts it to a Date (validating "Invalid Date" -> 400). The previous union(...).transform().refine() schema couldn't be exercised by the shared adapter test suite because the generator didn't unwrap Zod 4's ZodPipe.
    • _test-utils/route-test-utils recognizes Zod 4's number_format check (used for int() / safeint()), and generateContextualValue now produces a valid ISO timestamp for createdAt / updatedAt fields.
  • Configured workflow step scorers now automatically emit scores through the observability score pipeline. (#16371)

Patch Changes
  • Update provider registry and model documentation with latest models and providers (ac47842)

  • Fixed two bugs that affected scheduled workflows. (#16510)

    Scheduled workflow with mismatched id could not be dispatched (#16471)

    When a workflow's id differed from the key it was registered under, the scheduler published events the event processor could not resolve, causing the run to fail with "Workflow not found." The dispatcher now looks up workflows by .id first (falling back to the registration key), so the following now works as expected:

    const workflow = createWorkflow({ id: 'daily-report', schedule: { cron: '0 9 * * *' } });
    new Mastra({ workflows: { dailyReport: workflow } });

    Deleted scheduled workflows caused infinite event redelivery

    Removing a scheduled workflow from code used to leave its schedule row in storage. The scheduler kept firing for the missing workflow and the event processor kept telling the transport to redeliver the event forever. On boot, Mastra now cleans up declarative schedule rows (those it wrote itself, prefixed with wf_) for workflows that are no longer registered. User-created schedules made via the schedules API are left untouched. The event processor also handles in-flight events for missing workflows by emitting a single terminal workflow.fail instead of looping.

  • Fixed processor-combined workflows to use the agent logger so processor step failures are logged through the configured logger instead of console output. (#16369)

  • Fixed plan approval so accepting a plan can switch modes after the waiting plan tool resolves, clears stale abort state before starting the approved goal, and injects the goal trigger directly instead of queueing a follow-up. (#16340)

  • Fixed guardrail processor schemas so structured output works with Anthropic models. (#15739)

  • Propagate cache metrics (cachedInputTokens, cacheCreationInputTokens) through harness token usage. The step-finish handler now extracts cachedInputTokens from AI SDK usage and propagates it through usage_update events, getTokenUsage(), display state, and thread metadata persistence. (#14746)

  • Fixed workflow resume to reuse suspended step input payloads when previous step output is stale. Fixes https://github.com/mastra-ai/mastra/issues/16051. (#16066)

  • A tool's toModelOutput result is now computed before the tool-result chunk is emitted, so it travels with the chunk on providerMetadata.mastra.modelOutput. Previously toModelOutput only ran later when the message list was updated, meaning live stream consumers couldn't see it without re-running the tool. (#16457)

    The harness now forwards that providerMetadata on tool_result content (both streaming and replayed history) and on tool_end events, so UIs can render rich tool output (e.g. screenshot images) inline.

    harness.on('tool_end', event => {
      const modelOutput = event.providerMetadata?.mastra?.modelOutput;
      // e.g. { type: 'content', value: [{ type: 'image-data', mediaType: 'image/png', data: '...' }] }
    });
  • Fixed ToolCallFilter so assistant messages with top-level text are preserved when tool parts are removed. (#16077)

  • Fixed AI SDK v6 dynamic tool UI messages so MessageList preserves tool state during history normalization. Fixes #16046. (#16062)

  • Fixed tool result media content not reaching the model. Tools using toModelOutput to return images or files (e.g. screenshot tools) now work correctly with all AI SDK providers (Anthropic, OpenAI, Google). (#16449)

  • Added A2A Agent Card signing config types for server configuration. (#16207)

    Example

    const mastra = new Mastra({
      server: {
        a2a: {
          agentCardSigning: {
            privateKey: process.env.A2A_AGENT_CARD_PRIVATE_KEY!,
            protectedHeader: {
              alg: 'ES256',
              kid: 'agent-card-key',
            },
          },
        },
      },
    });
  • Fixed a Harness issue where reopening a thread could apply the wrong model for (#16278) the saved mode. Threads now reopen with the correct model for that mode, including when no explicit per-mode model was selected.

  • Fixed Workflow runs no longer fail to persist when request context contains non-serializable values (for example functions, circular references, or platform proxy objects). This prevents errors when saving workflow snapshots and scorer results. See #12301. (#12573)

  • Added /goal to Mastra Code, a persistent autonomous task loop similar to the goal modes in Codex and Hermes Agent. (#16065)

    A user can start a goal with /goal <objective>. Mastra Code saves that objective to the current thread, runs the normal assistant turn, then asks a separate judge model whether the goal is done, should continue, or is waiting on an explicit user checkpoint. When the judge says to continue, Mastra Code feeds the judge feedback back into the conversation and keeps working until the goal is complete, paused, cleared, or reaches the configured attempt limit.

    Use /judge to configure the default judge model and max attempts used by future goals.

    Approved plans can be selected as a goal from the inline plan approval UI, slash commands can opt into /goal/<command> with top-level goal: true, and skills can opt into goal commands with metadata.goal: true. /goal objectives can also span multiple lines.

  • Fixed nested workflow runs retaining abort listeners after completion. (#16212)

    • Fixed foreach state update and foreach bail in evented workflows (#16436)
    • Fixed suspend-resume in evented-workflows legacy stream.
  • Hide internal spans from Mastra-owned processors in exported traces. The PROCESSOR_RUN span still appears, but the agent, model, and tool spans that processors create under the hood are now marked internal and filtered out by default. (#16424)

    Affects the moderation, PII detector, language detector, prompt-injection detector, system-prompt scrubber, and structured-output processors.

    To inspect the internals (e.g. for debugging a Mastra-owned processor's behavior), set includeInternalSpans: true on your Observability config and the full subtree will be exported.

  • Fixed goal reminders in MastraCode to continue through signals without duplicating prompts. Updated core signal stream completion handling so idle-started reminder runs emit the expected lifecycle events. (#16231)

  • Fixed requestContext serialization in workflow suspend/resume. The snapshot persistence used instanceof check which fails across module boundaries, causing context values to be lost on resume. (#16082)

  • Fixed timeTravel() on evented workflows so jumping to a .branch() step no longer returns empty results for branches that did not run. Branch results now include only the branch that ran, matching the default workflow engine. (#16428)

  • Fixed an issue where trajectory and step scorer results from runEvals were not saved to storage. These scores now persist correctly and appear alongside agent and workflow scores in Studio's observability section. (#16249)

  • Tool toModelOutput invocations now emit a MAPPING tracing span, showing the transformed output the model receives. (#16347)

  • Default top-level observational memory early activation settings to observations only, while allowing per-phase overrides under observation and reflection. (#16367)

  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Fixed assistant message tracking when ObservationalMemory clears step-1 output to memory and step-2 text merges into the same assistant message, so merged text is not lost on the next response clear. (#15277)

  • Added subagent display names to Harness display state so UIs can render configured subagent names without duplicating agent type lookup. (#16237)

  • Fixed title generation to send plain text instead of JSON-serialized part objects to the title model. Previously, internal metadata like providerOptions and framework details leaked into the title prompt. Now formats messages using role-prefixed plain text (similar to observational memory formatting), supporting both single-message and multi-turn conversations. (#15798)

  • Fixed a bug where orphaned provider-executed tool calls (e.g. Anthropic web_search, Gemini code_execution) could brick a thread. When a provider dropped the tool-result chunk (#15668) or a run aborted mid-stream (#14148), the unresolved call was replayed on every subsequent request — causing Gemini to return empty text and Anthropic to reject the tool-call/tool-result invariant. (#15682)

    sanitizeV5UIMessages now only keeps an input-available provider-executed tool part on the most recent assistant message, and only when no user turn has followed it. Orphans on earlier assistant turns are dropped so the outgoing history always satisfies the tool-call/tool-result pairing required by provider APIs.

  • Fixed TokenLimiterProcessor crashing when counting text that contains special token strings. (#16302)

  • Fix nested loops in evented workflow (#16312)

  • Fixed agent requests to Anthropic failing with 400 unexpected tool_use_id when recalled history contained an incomplete tool_use / tool_result pair. This most commonly hit agents using parallel tool calls when an earlier run was interrupted before every tool finished — pulling that broken exchange into a new prompt no longer crashes the next request. (#16201)

    Closes #16193

  • Stop logging auto-recoverable provider cache corruption warnings when ~/.cache/mastra/ contains stale content from another Mastra version. Corrupted cache files are still deleted on read so they cannot propagate into a project's dist/, and the next gateway sync rewrites valid files. (#16332)

  • Fix evented workflow foreach timing out when payload is an empty array (#16358)

  • Fixed FGA EE license gating so production servers require an Enterprise license when FGA is configured. (#16362)

  • Fixed a bug where message-level providerOptions could be lost or applied to the wrong turn after tool calls. Anthropic cacheControl markers now stay attached to the intended message in tool-using conversations. (#16133)

  • Fixed MODEL_INFERENCE span timing so it measures pure model latency. (#16357)

  • Added extra defensive checks to prevent edge cases where system messages may have already been stored in message history. (#15787)

  • Fixes tool call args being lost when split across messages in client tools. When a tool invocation spans multiple messages (call with args, result with empty args), the findToolCallArgs function now continues searching for non-empty args instead of returning the first match. (#12454)

  • Replace js-tiktoken with tokenx in @mastra/core to reduce bundle size by removing the bundled BPE rank tables. Token limiting and truncation now use heuristic token estimates, which is appropriate for output limiting and truncation. (#16326)

  • Fix abort() not cancelling evented workflows (#16416)

  • Fixed suspend and resume for evented workflows that use parallel steps, .branch(), dountil/dowhile loops, and nested workflows — previously these only worked reliably for simple linear flows. (#16476)

    Parallel & .branch() steps — when more than one branch suspends at the same time (e.g. each branch waits on its own approval), every suspended branch can now be resumed, the workflow stays suspended until all of them have been resumed, and the branch outputs are merged correctly. Before, only the last branch to suspend was resumable, and resuming one branch could prematurely complete the run.

    dountil / dowhile loops — a loop body that calls suspend() now suspends the workflow instead of crashing the run. And after a resume, subsequent loop iterations run fresh instead of re-receiving the resume data — which previously made loops either run forever or skip their own suspend logic.

    Nested workflows — resuming a suspended step inside a nested workflow now gives it the correct input (the output of the step right before it, not the nested workflow's own input), so it produces correct results, even when workflows are nested several levels deep. The suspended-step path returned in a workflow result is also correct now, so you can pass it straight back into resume({ step }).

  • Added support for attaching a browser instance to the harness after initialization so consumers can defer browser creation until it is needed: (#16513)

    const harness = new Harness({ agent, mastra });
    await harness.init();
    harness.setBrowser(browser);
  • Added validation to evented workflows to ensure execution prerequisites are met. EventedWorkflow.createRun() now throw clear error messages when the workflow execution flow is empty (missing .then(), .branch(), etc. calls) or when the step graph has uncommitted changes (missing .commit() call). This catches configuration errors early rather than failing during execution. (#16361)

  • Background tasks now run as evented workflows. Each task is dispatched as a workflow run that owns executor invocation, retries, and suspend/resume; pubsub topics, lifecycle event shapes, concurrency gating, and the BackgroundTaskManager.stream() contract are unchanged. (#16260)

    Add suspend/resume to background tasks. Tools can call ctx.agent.suspend(data) from execute to pause a task and release the concurrency slot; resume with mastra.backgroundTaskManager.resume(taskId, resumeData) or agent.resumeStreamUntilIdle(resumeData, { runId, toolCallId }). Surfaces background-task-suspended / background-task-resumed chunks on backgroundTaskManager.stream() and agent.streamUntilIdle().fullStream.

  • Fixed message part ordering in agent streaming responses. Message parts (text, reasoning, tool calls) now appear in the correct order they arrived in the stream, preventing incorrect step sequencing and agent loop behavior issues. (#16073)

@mastra/agent-browser@0.2.2
Patch Changes
  • Added screenshot tool to @mastra/stagehand (stagehand_screenshot) and @mastra/agent-browser (browser_screenshot). Captures a PNG screenshot and returns image content for vision-capable models. (#16074)

    Added excludeTools config option to opt out of specific tools:

    const browser = new StagehandBrowser({
      excludeTools: ['stagehand_screenshot'],
    });
@mastra/ai-sdk@1.4.2
Patch Changes
  • Fixed cache write tokens not being set on the AI SDK v6 usage object. inputTokenDetails.cacheWriteTokens now reflects the prompt cache creation tokens reported by the provider instead of always being undefined. Previously this value was only accessible via providerMetadata.anthropic.cacheCreationInputTokens. (#16354)

  • Added support for showing different tool values to users than the values used internally during execution. AI SDK streams now read Mastra display values for tool call input, streamed input deltas, tool results, tool errors, approvals, and suspensions. (#16103)

    const lookupCustomer = createTool({
      // Runtime still receives the full input and returns the full output.
      execute: async ({ customerId, internalPath }) => lookupCustomerRecord(customerId, internalPath),
      transform: {
        display: {
          input: ({ input }) => ({ customerId: input?.customerId }),
          output: ({ output }) => ({ displayName: output?.displayName }),
          error: () => ({ message: 'Customer lookup failed' }),
        },
      },
    });

    This lets chat UIs show safe display values while runtime code keeps the original payloads. See https://github.com/mastra-ai/mastra/issues/16054.

@mastra/arize@1.1.0
Minor Changes
  • Update PHOENIX_ENDPOINT to PHOENIX_COLLECTOR_ENDPOINT environment variable (#16341)
Patch Changes
@mastra/braintrust@1.1.0
Minor Changes
  • Added a Braintrust current span resolver option so eval traces can nest correctly when applications and Mastra resolve different installed Braintrust SDK copies. (#16396)

  • Mastra Eval results are now forwarded to Braintrust. (#16185)

Patch Changes
@mastra/brightdata@0.2.0
Minor Changes
  • Added @mastra/brightdata integration with brightdata-search and brightdata-fetch tools backed by Bright Data's SERP API and Web Unlocker. The tools bypass bot detection and CAPTCHAs out of the box. (#16392)

    import { Agent } from '@mastra/core/agent';
    import { createBrightDataTools } from '@mastra/brightdata';
    
    const agent = new Agent({
      id: 'research-agent',
      name: 'Research Agent',
      model: 'anthropic/claude-sonnet-4-6',
      instructions: 'Use brightdata-search to find pages and brightdata-fetch to read them.',
      tools: createBrightDataTools(),
    });

    Set BRIGHTDATA_API_TOKEN in your environment, or pass { apiKey } explicitly.

Patch Changes
@mastra/clickhouse@1.7.1
Patch Changes
  • Fixed agent streams intermittently hanging when observability storage was backed by Replicated/Shared ClickHouse. Startup no longer re-applies no-op schema updates (e.g. ADD COLUMN IF NOT EXISTS, ADD INDEX IF NOT EXISTS, MODIFY TTL), so it no longer triggers replica-lag retry errors that could leave storage in a stuck state. (#16420)

  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/client-js@1.18.0
Minor Changes
  • Added experimental A2A Agent Card signature verification to getAgentCard. (#16207)

    Example

    const card = await a2a.getAgentCard({
      verifySignature: {
        algorithms: ['ES256'],
        keyProvider: async ({ kid, jku }) => {
          return fetchTrustedPublicJwk({ kid, jku });
        },
      },
    });

    When verification is configured, client-js now verifies signed Agent Cards when the server includes signatures. Unsigned cards are still returned unchanged.

    This also adds the new exported types GetAgentCardOptions, VerifyAgentCardSignatureOptions, AgentCardVerificationKey, and AgentCardSignatureKeyProviderInput.

  • Added client, React, and Studio support for Agent signals in threaded chat. Threaded user messages now send through Agent signals, stream output is consumed from the thread subscription, echoed user messages are deduped by signal ID, file and image message contents are preserved, Studio can send follow-ups while a response is streaming, Studio subscribes to open threads so additional tabs can observe active streams, and the Studio stop button aborts the active thread subscription. React chat also falls back to legacy threaded streaming when it connects to a server or core version that does not support signal routes yet. (#16338)

    const { sendMessage } = useChat({ agentId, resourceId, threadId });
    await sendMessage({ message: 'Follow up while streaming', threadId });
  • Added streamed function-call argument events to @mastra/client-js Responses streams. You can now read finalized tool arguments directly from the stream: (#16285)

    for await (const event of stream) {
      if (event.type === 'response.function_call_arguments.done') {
        console.log(event.arguments);
      }
    }
  • Added Agent signals for sending contextual messages into agent thread loops and subscribing to thread activity. (#16229)

    Call agent.sendSignal() to inject context into a running agent loop. When the thread is idle, that same signal becomes the prompt that starts the next loop by default. Use ifActive.behavior and ifIdle.behavior to deliver, persist, discard, or wake from a signal.

    Use agent.subscribeToThread() to follow the raw stream chunks for a memory thread, observe signal echoes with stable IDs, and abort the active stream for that thread.

    const subscription = await agent.subscribeToThread({ resourceId, threadId });
    
    void (async () => {
      for await (const part of subscription.stream) {
        if (part.type === 'finish') {
          subscription.unsubscribe();
        }
      }
    })();
    
    agent.sendSignal({ type: 'user-message', contents: 'Use the latest answer' }, { resourceId, threadId });
  • Fix orderBy shape mismatch for paginated list methods. (#16323)

    The server expects orderBy as a structured object ({ field, direction }), but several SDK methods were sending orderBy and sortDirection as flat strings, which caused server-side schema validation to fail.

    Affected methods:

    • MastraClient.listMemoryThreads
    • Agent.listVersions
    • StoredAgent.listVersions
    • StoredPromptBlock.listVersions
    • StoredScorer.listVersions

    Before:

    client.listMemoryThreads({ orderBy: 'createdAt', sortDirection: 'DESC' });

    After:

    client.listMemoryThreads({ orderBy: { field: 'createdAt', direction: 'DESC' } });

    The flat sortDirection parameter has been removed from the affected param types in favor of the nested orderBy.direction field.

  • Fix client-js bugs surfaced by the SDK ↔ server contract audit. (#16439)

    • MastraClient.getAgentBuilderActions() previously requested /agent-builder/ (trailing slash) and 404'd. Now hits /agent-builder.
    • AgentBuilder.stream(params, runId) now requires runId. The server route requires it; calls without it failed with a server-side validation error. The SDK now both types runId as required and guards at runtime.
    • MastraClient.createStoredSkill(...) now requires description in its parameter type. The server schema has always required it; the SDK type used to mark it optional, so omitting it produced a runtime 400 instead of a compile error.

    Migration:

    // Before
    await agentBuilder.stream({ inputData });
    
    // After
    await agentBuilder.stream({ inputData }, runId);
    // Before
    await client.createStoredSkill({ name, instructions });
    
    // After
    await client.createStoredSkill({ name, description, instructions });
  • Add agent.resumeStreamUntilIdle() to resume a suspended agent stream and keep the SSE connection open through the follow-up turn. (#16260)

Patch Changes
  • Fixed memory thread write methods (update, delete, deleteMessages, clone) silently sending requests without the required agentId. The methods now resolve agentId from a per-call argument first, then the constructor, and throw a clear error if neither is set — before any HTTP request is issued. Reads are unchanged. (#16310)

    // Either set agentId on the thread once...
    const thread = client.getMemoryThread({ threadId: 't1', agentId: 'a1' });
    await thread.update({ title: 'Renamed' });
    await thread.delete();
    
    // ...or pass it per call.
    const thread = client.getMemoryThread({ threadId: 't1' });
    await thread.update({ agentId: 'a1', title: 'Renamed' });
    await thread.delete({ agentId: 'a1' });

    Fixed MastraClient.deleteThread() issuing DELETE /api (an empty URL) when called without agentId or networkId. The method now requires exactly one of the two, enforced both at runtime and in the type signature.

    await client.deleteThread('t1', { agentId: 'a1' });
    await client.deleteThread('t1', { networkId: 'n1' });
  • Regenerate route types to include TRAJECTORY and STEP entityType variants on the score response (follow-up to #16249). (#16288)

  • Fix MCPTool.execute sending an empty/undefined request body when called without data or requestContext. The server's tool-execute endpoint expects an object body (with optional data), so calls like client.getMcpServerTool(serverId, toolId).execute({}) would fail with Invalid request body. The SDK now always POSTs a JSON object body, defaulting to {} when no parameters are provided. (#16488)

  • Removed Agent Builder routes from the default generated API route contracts. (#16499)

@mastra/cloudflare@1.3.3
Patch Changes
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/cloudflare-d1@1.0.6
Patch Changes
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/convex@1.0.11
Patch Changes
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/datadog@1.2.0
Minor Changes
  • Mastra Eval results are now forwarded to Datadog. (#16185)

  • Mapped MODEL_INFERENCE spans to Datadog's llm kind (with token usage and model/provider attached) and MODEL_STEP to workflow. Falls back to the previous mapping when paired with an older @mastra/core that does not emit MODEL_INFERENCE. (#16363)

Patch Changes
  • Fixed double-encoded JSON on span input in Datadog. (#16256)
@mastra/deployer@1.33.0
Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)
@mastra/deployer-cloud@1.33.0
Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)
@mastra/deployer-cloudflare@1.1.33
Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)
@mastra/deployer-netlify@1.1.9
Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)
@mastra/deployer-vercel@1.1.27
Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)
@mastra/dsql@1.0.0
Minor Changes
  • Added Amazon Aurora DSQL storage provider with IAM authentication support. (#10930)

    Enables storing threads, messages, workflows, traces, and agent data in Amazon Aurora DSQL clusters.

    import { DSQLStore } from '@mastra/dsql';
    
    const storage = new DSQLStore({
      id: 'my-dsql-store',
      host: 'abc123.dsql.us-east-1.on.aws',
    });
    
    await storage.init();

    Related: #10929

Patch Changes
@mastra/duckdb@1.3.1
Patch Changes
  • Improved DuckDB observability initialization by batching schema setup statements on one connection while preserving migration order. (#16239)
@mastra/dynamodb@1.0.7
Patch Changes
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/e2b@0.3.1
Patch Changes
  • Fixed S3 mounts in E2B sandboxes by honoring the configured region and verifying that the FUSE mount attached successfully. (#16222)

    Mount failures that previously appeared successful now surface a clear error, making region, credential, and endpoint compatibility problems easier to diagnose.

@mastra/editor@0.7.24
Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)

  • Fixed @mastra/editor integrations (Composio, Arcade) collapsing every tool call onto a shared 'default' user. Tools resolved during agent.generate now scope to the authenticated resource from the request context, so per-user OAuth connections route to the correct account instead of a shared one. (#16122)

@mastra/express@1.3.19
Patch Changes
  • Improved Studio agent serialization by making Studio mode and auth-related request context server-controlled across adapters. Playground requests now identify Studio traffic consistently, body and query request context cannot set reserved server values, and Studio placeholder fallback is limited to instruction rendering while serialized models, workspace, skills, tools, and default options use the real request context. (#16152)

  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)

@mastra/fastify@1.3.19
Patch Changes
  • Improved Studio agent serialization by making Studio mode and auth-related request context server-controlled across adapters. Playground requests now identify Studio traffic consistently, body and query request context cannot set reserved server values, and Studio placeholder fallback is limited to instruction rendering while serialized models, workspace, skills, tools, and default options use the real request context. (#16152)

  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)

  • Fixed Fastify stream cleanup and route abort signals when clients disconnect before streamed responses finish. (#16308)

@mastra/google-cloud-pubsub@1.0.4
Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)
@mastra/hono@1.4.14
Patch Changes
  • Improved Studio agent serialization by making Studio mode and auth-related request context server-controlled across adapters. Playground requests now identify Studio traffic consistently, body and query request context cannot set reserved server values, and Studio placeholder fallback is limited to instruction rendering while serialized models, workspace, skills, tools, and default options use the real request context. (#16152)

  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)

@mastra/inngest@1.4.0
Minor Changes
  • Updated @mastra/inngest to use Inngest SDK v4. (#15377)

    Breaking: Requires inngest@^4 and Inngest Dev Server v1.18.0 or later. The @inngest/realtime package is no longer needed — its functionality is now included in inngest v4. Remove it from your dependencies and import realtime helpers from inngest/realtime instead.

      // package.json
      "dependencies": {
    -   "@inngest/realtime": "^0.x",
    -   "inngest": "^3.x"
    +   "inngest": "^4.0.0"
      }
    - import { realtimeMiddleware } from '@inngest/realtime/middleware';
    - import { subscribe } from '@inngest/realtime';
    + import { subscribe } from 'inngest/realtime';
    
      const inngest = new Inngest({
        id: 'mastra',
    -   middleware: [realtimeMiddleware()],
      });

    In v4, subscribe() and realtime.publish() are first-class methods on the client; the standalone middleware is no longer required. InngestPubSub publishes via inngest.realtime.publish() instead of the function-context publish argument that no longer exists in v4, restoring realtime workflow events and agent stream events.

    Improved: Workflow result polling now uses snapshot-based polling, resulting in significantly faster retrieval (~83x).

Patch Changes
  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)

  • Updated the serve and createServe JSDoc adapter examples to register Inngest at /inngest/api instead of /api/inngest, matching the Inngest deployment guide and in-repo example projects. (#16186)

    Why

    Mastra reserves the /api prefix for built-in routes (agents, workflows, memory). Custom apiRoutes[].path values that start with the server's apiPrefix (default /api) are rejected at startup, so the previous JSDoc snippets threw Custom API route "/api/inngest" must not start with "/api" when copy-pasted into a current Mastra project.

    Migration

    If you registered Inngest with the previous guide or JSDoc example:

    // Before
    apiRoutes: [
      {
        path: '/api/inngest',
        method: 'ALL',
        createHandler: async ({ mastra }) => serve({ mastra, inngest }),
      },
    ];
    
    // After
    apiRoutes: [
      {
        path: '/inngest/api',
        method: 'ALL',
        createHandler: async ({ mastra }) => serve({ mastra, inngest }),
      },
    ];

    Update the dev server URL (npx inngest-cli dev -u http://localhost:4111/inngest/api) and, in production, set the URL field on your Inngest app to match.

    If you cannot change the path, set server.apiPrefix (for example /_mastra) to relocate the built-in routes and remember to update server.auth.protected and any MastraClient apiPrefix to match. See the Inngest deployment guide for the full walkthrough.

@mastra/koa@1.5.2
Patch Changes
  • Improved Studio agent serialization by making Studio mode and auth-related request context server-controlled across adapters. Playground requests now identify Studio traffic consistently, body and query request context cannot set reserved server values, and Studio placeholder fallback is limited to instruction rendering while serialized models, workspace, skills, tools, and default options use the real request context. (#16152)

  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)

  • Fixed TypeError: Cannot read properties of undefined (reading 'length') thrown during MastraServer.init() when a subclass forwards a non-Koa app-like object (for example a koa-router instance, a mounted sub-app, or a custom wrapper) to super.registerRoute(app, route, opts). The dispatcher-reuse optimization introduced in 1.5.0 now requires the target to expose an app.middleware array; otherwise it falls back to registering a fresh dispatcher per route via app.use, matching the pre-1.5.0 per-route behavior. (#16484)

    Example (subclass that previously crashed):

    import { MastraServer } from '@mastra/koa';
    import Router from 'koa-router';
    
    class CustomKoaMastraServer extends MastraServer {
      private router = new Router();
    
      async registerCustomApiRoutes() {
        const routes = this.mastra.getServer()?.apiRoutes ?? [];
        for (const route of routes) {
          // The router has no `middleware` array — this used to throw at init.
          await super.registerRoute(this.router as any, route, { prefix: this.prefix });
        }
        this.app.use(this.router.routes());
      }
    }
@mastra/laminar@1.1.0
Minor Changes
  • Mastra Eval results are now forwarded to Laminar. (#16185)
Patch Changes
@mastra/lance@1.0.6
Patch Changes
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/langfuse@1.3.0
Minor Changes
  • Mastra Eval results are now forwarded to Langfuse. (#16185)
Patch Changes
@mastra/langsmith@1.2.0
Minor Changes
  • Mastra Eval results are now forwarded to LangSmith. (#16185)
Patch Changes
@mastra/libsql@1.10.1
Patch Changes
  • Fixed Workflow run snapshots no longer lose fields when serialized for storage. The libsql safeStringify cycle-detection treated any object that appeared more than once in a snapshot as a circular reference and dropped it. Because snapshot.result and the final step's context[step].output share the same reference on success, snapshot.result was being silently stripped on every persist. This caused listWorkflowRuns to return runs with snapshot.result === undefined and broke workflow resume when suspended-state fields were shared elsewhere in the snapshot. (#16368)

  • Fixed Workflow runs no longer fail to persist when request context contains non-serializable values (for example functions, circular references, or platform proxy objects). This prevents errors when saving workflow snapshots and scorer results. See #12301. (#12573)

  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Improved local LibSQL startup performance by applying conservative local SQLite performance settings before initialization, exposing local PRAGMA overrides, and reducing schema initialization contention. (#16513)

  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

  • Added LibSQL indexes for thread message history queries to speed up recent-message and observational-memory loading. (#16513)

@mastra/memory@1.18.0
Minor Changes
  • Add metadata filtering support to semantic recall. (#9256)
Patch Changes
  • Fixed an issue where tool results containing AI SDK v5 image-data content blocks (returned via toModelOutput) were stringified into the observational memory prompt as raw base64 text. The base64 data overflowed the observer's context, causing token-limit errors and degenerate output. (#16117)

    Image and file blocks (image-data, image-url, file-data, file-url, and media) inside tool results are now hoisted into the observer's input as proper attachments, the same way image and file message parts already are. The text body shows a placeholder like [Image #1: image/png] so the observer keeps positional context without seeing the bytes.

  • Default top-level observational memory early activation settings to observations only, while allowing per-phase overrides under observation and reflection. (#16367)

  • Auto-recover from transient transport errors (e.g. undici terminated, fetch failed, UND_ERR_*, 5xx, 429) in the OM observer and reflector LLM calls. Adds an internal retry wrapper with exponential backoff (1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s — per-attempt delay capped at 2 minutes, ~4 minute total budget per call) so a single network blip from any provider no longer kills the actor turn during long-running sessions. Non-transient errors (auth, validation, etc.) and user-initiated aborts still fail fast. No public API changes. (#16454)

  • Added extra defensive checks to prevent edge cases where system messages may have already been stored in message history. (#15787)

  • Fixed read-only observational memory so existing context is still loaded. (#16059)

@mastra/mongodb@1.8.1
Patch Changes
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/mssql@1.3.0
Minor Changes
  • Add agents storage domain to MSSQL adapter — brings @mastra/mssql to parity with @mastra/mongodb and @mastra/libsql for the agents domain. The Studio "Agents" tab and mastra.getEditor() now work against MSSQL. (#16376)

    import { MSSQLStore } from '@mastra/mssql';
    
    const store = new MSSQLStore({
      id: 'mssql-storage',
      connectionString: process.env.MSSQL_URL!,
    });
    
    const agents = await store.getStore('agents');
    const agent = await agents?.getById('agent-id');
    const page = await agents?.list({ status: 'published', perPage: 20 });
Patch Changes
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/nestjs@0.1.3
Patch Changes
  • Improved Studio agent serialization by making Studio mode and auth-related request context server-controlled across adapters. Playground requests now identify Studio traffic consistently, body and query request context cannot set reserved server values, and Studio placeholder fallback is limited to instruction rendering while serialized models, workspace, skills, tools, and default options use the real request context. (#16152)

  • Fixed peer dependency ranges so packages that use the Mastra server require a compatible Mastra core version. (#16208)

@mastra/observability@1.12.0
Minor Changes
  • DefaultExporter now notifies custom exporters and connected integrations when it cannot persist observability events, such as unsupported storage or retries being exceeded. (#16111)

  • Renamed two built-in observability exporters to clearer names. The originals are still exported (now deprecated) and continue to work unchanged, including their existing exporter name strings and error IDs, so monitoring rules and dashboards keep matching until you migrate. (#16223)

    • CloudExporterMastraPlatformExporter
    • DefaultExporterMastraStorageExporter

    Before

    import { Observability, DefaultExporter, CloudExporter, SensitiveDataFilter } from '@mastra/observability';
    
    new Observability({
      configs: {
        default: {
          serviceName: 'my-app',
          exporters: [new DefaultExporter(), new CloudExporter()],
          spanOutputProcessors: [new SensitiveDataFilter()],
        },
      },
    });

    After

    import {
      Observability,
      MastraStorageExporter,
      MastraPlatformExporter,
      SensitiveDataFilter,
    } from '@mastra/observability';
    
    new Observability({
      configs: {
        default: {
          serviceName: 'my-app',
          exporters: [new MastraStorageExporter(), new MastraPlatformExporter()],
          spanOutputProcessors: [new SensitiveDataFilter()],
        },
      },
    });
  • Apply SensitiveDataFilter by default (#16234)

    The Observability registry now auto-applies a SensitiveDataFilter span output processor to every configured instance, so secrets (API keys, tokens, passwords, etc.) are redacted before they reach exporters such as the Mastra cloud exporter. This protects against accidentally exporting sensitive data when the filter was not added manually.

    A new top-level sensitiveDataFilter option on the Observability registry config controls this behavior:

    • true (default): apply SensitiveDataFilter with default options.
    • false: opt out of auto-applied filtering.
    • a SensitiveDataFilterOptions object: customize the filter (sensitive fields, redaction token, redaction style).

    If a config already includes a SensitiveDataFilter in spanOutputProcessors, the auto-applied filter is skipped to avoid double redaction. Pre-instantiated ObservabilityInstance values are not modified.

    Before:

    import { Observability, DefaultExporter, CloudExporter, SensitiveDataFilter } from '@mastra/observability';
    
    new Observability({
      configs: {
        default: {
          serviceName: 'mastra',
          exporters: [new DefaultExporter(), new CloudExporter()],
          spanOutputProcessors: [new SensitiveDataFilter()],
        },
      },
    });

    After:

    import { Observability, DefaultExporter, CloudExporter } from '@mastra/observability';
    
    new Observability({
      configs: {
        default: {
          serviceName: 'mastra',
          exporters: [new DefaultExporter(), new CloudExporter()],
        },
      },
      // Optional: customize or disable the auto-applied filter.
      // sensitiveDataFilter: false,
      // sensitiveDataFilter: { sensitiveFields: ['myCustomSecret'] },
    });
  • Added new MODEL_INFERENCE span type under MODEL_STEP, covering only the model provider call. Use it to measure model latency separately from input/output processors and tool executions. (#16267)

Patch Changes
  • Fixed cost estimation for OpenRouter models. The Model Usage & Cost panel now shows costs for OpenRouter vendor/model ids (e.g. openai/gpt-5-mini-2025-08-07, xiaomi/mimo-v2-pro-20260318) that previously rendered an empty cost column. (#16206)

  • Support MASTRA_PLATFORM_ACCESS_TOKEN as the preferred environment variable for MastraPlatformExporter, while retaining MASTRA_CLOUD_ACCESS_TOKEN as a fallback for backward compatibility. (#16500)

  • Score events now include scorer names and target entity types. (#16185)

  • Fixed MODEL_INFERENCE span timing so it measures pure model latency. (#16357)

  • Refreshed the embedded pricing data snapshot used for cost estimation in observability metrics with the latest provider rates. (#16373)

@mastra/otel-bridge@1.1.0
Minor Changes
  • Added log forwarding to @mastra/otel-bridge. The bridge now also subscribes to Mastra log events and forwards them to the globally-registered OpenTelemetry LoggerProvider, alongside the spans it already creates. (#13529)

    Logs that originate inside a Mastra span are emitted under that span's OTEL context, so backends like Datadog, Grafana, and Honeycomb correlate them with the surrounding trace automatically. Logs without trace context fall through to the currently active OTEL context.

    To wire up logs alongside traces, register a logRecordProcessor on NodeSDK:

    import { NodeSDK } from '@opentelemetry/sdk-node';
    import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
    import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
    
    const sdk = new NodeSDK({
      // ...trace config as usual
      logRecordProcessor: new BatchLogRecordProcessor(new OTLPLogExporter()),
    });

    If no LoggerProvider is registered, log emission is a silent no-op — traces continue to work as configured.

Patch Changes
@mastra/otel-exporter@1.1.0
Minor Changes
  • Added log export to @mastra/otel-exporter. Logs emitted on the Mastra observability bus are now forwarded to the configured OTLP endpoint alongside traces, using the same provider configuration. (#13529)

    import { OtelExporter } from '@mastra/otel-exporter';
    
    new OtelExporter({
      provider: {
        custom: { endpoint: 'http://localhost:4318', protocol: 'http/json' },
      },
      // signals.logs defaults to true; set to false to disable.
      signals: { traces: true, logs: true },
    });

    Requires the matching OTLP log exporter package to be installed (e.g. @opentelemetry/exporter-logs-otlp-http for HTTP/JSON, or -proto / -grpc variants).

    Trace correlation: Logs that carry traceId and spanId are attached to the OTEL log record's native trace context, so backends like Datadog, Grafana, and Honeycomb auto-correlate logs to traces.

    Other improvements:

    • Trace and log endpoints are always normalized to a single signal-path suffix, so http://host:4318/, http://host:4318, and http://host:4318/v1/traces/ all produce well-formed URLs instead of malformed variants like //v1/logs.
    • Calling flush() or shutdown() immediately after init no longer drops telemetry — teardown waits for setup to finish before draining providers.
    • Debug log output no longer exposes credential fragments. Provider header values are fully redacted instead of printing prefix/suffix slices.
    • When a dynamically-loaded OTLP exporter package is installed but does not expose the expected named export, Mastra now disables that signal with a clear error message instead of failing later with an opaque "X is not a constructor" error.
Patch Changes
@mastra/pg@1.10.1
Patch Changes
  • Fixed @mastra/pg listing endpoints (agents, MCP clients, MCP servers, prompt blocks, scorer definitions, skills, and workspaces) so a single row with a malformed value no longer returns HTTP 500 and hides every other record in the Mastra Editor. Listings now tolerate the bad row and return the rest. (#16233)

    Fixes #16224.

  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
  • Track suspendedAt and suspendPayload on background tasks. SQL adapters auto-migrate the new columns via alterTable. (#16260)

@mastra/playground-ui@27.0.0
Minor Changes
  • Updated agent traces tab to use the rich observability traces UI (#16405)

    The agent traces tab now shows the dense 7-column trace list with a side-panel detail view featuring colored timeline spans (Agent/Workflow/Model/Scorer), expandable nested spans, Evaluate Trace, and Save as Dataset Item.

    Locked scope filter pills

    When viewing agent-scoped traces, the Primitive Type and Primitive ID filter pills are now read-only — they display the agent context, show a lock icon and tooltip, and cannot be edited or removed. The Add Filter dropdown no longer lists scope-controlled fields so users cannot accidentally override the active scope.

    PropertyFilterApplied accepts a new lockedFieldIds (and optional lockedTooltipContent) prop. PropertyFilterCreator accepts a new hiddenFieldIds prop. Both are opt-in and unset by default, so existing usages are unaffected.

    // Before
    <PropertyFilterApplied fields={fields} tokens={tokens} onTokensChange={setTokens} />
    
    // After — pills for the listed fields render locked with a tooltip
    <PropertyFilterApplied
      fields={fields}
      tokens={tokens}
      onTokensChange={setTokens}
      lockedFieldIds={['rootEntityType', 'entityId']}
      lockedTooltipContent="This filter is set by the current context."
    />
  • Added CodeBlock component with select/tabs switcher, optional shiki syntax highlighting, and Notion-style hover-only copy button (always visible on touch devices via media query). (#16202)

  • Improved ScrollArea to use Base UI internally and added a richer mask API. Edges now fade by default based on orientation (top/bottom for vertical, left/right for horizontal, all four for both), so most scrollers get the polished fade-out automatically. (#16415)

    Heads up — default behavior change: ScrollArea previously rendered without any edge fade unless showMask was passed. It now fades the edges that match orientation by default. Pass mask={false} on the callsites where you want to keep the old hard edges.

    New mask prop. Accepts a boolean (false disables the fade entirely) or an object to override individual sides. The x and y keys are shorthands for the matching axis.

    // Default — fades follow `orientation`
    <ScrollArea>...</ScrollArea>
    
    // Opt out entirely
    <ScrollArea mask={false}>...</ScrollArea>
    
    // Keep only the top fade
    <ScrollArea mask={{ bottom: false }}>...</ScrollArea>
    
    // Vertical fades only on a two-axis scroller
    <ScrollArea orientation="both" mask={{ x: false }}>...</ScrollArea>

    Migrating from showMask. The showMask boolean is now deprecated but still works — mask wins when both are set.

    // Before
    <ScrollArea showMask>...</ScrollArea>
    <ScrollArea showMask={false}>...</ScrollArea>
    
    // After
    <ScrollArea>...</ScrollArea>             // default fade matches orientation
    <ScrollArea mask={false}>...</ScrollArea> // explicitly disable
  • Added an "All traces, nested too" mode to the Observability → Traces page. (#16479)

    The traces list now has a switcher in the toolbar to toggle between two views:

    • Top-level traces only (default) — one row per top-level run, the existing behavior.
    • All traces, nested too — one row per invocation, including every agent, workflow, tool, processor, scorer, and RAG ingestion that ran nested inside another run.

    This makes it possible to find every invocation of a given entity (e.g. "every run of recipe-maker workflow") regardless of how it was triggered. Selecting a row in the new mode opens a detail panel showing just that branch's subtree.

    New hooks for consumers building their own observability UIs:

    • useBranch({ traceId, spanId, depth? }) — fetches the span subtree rooted at an anchor span.
    • useTraceOrBranchSpans({ traceId, spanId, listMode }) — returns trace spans or a branch subtree depending on the active mode.
  • Added opt-in interactivity and per-page filter persistence support for observability UI components. (#15747)

    • MetricsLineChart accepts an onPointClick callback so chart points can drive drilldowns.
    • HorizontalBars accepts row-level and segment-level hrefs for linked metric bars without nested anchors.
    • MetricsDataTable accepts getRowHref(row) for linked rows with consistent hover and focus styling.
    • MetricsCard exposes an Actions slot in the top bar for contextual icon links.
    • Observability filter helpers for Metrics, Traces, and Logs each keep their own saved-filters storage key so pages remember filters independently.

    All additions are optional, so existing consumers continue to render the same way unless they pass the new props.

    <MetricsLineChart
      data={points}
      series={series}
      onPointClick={point => navigate(`/observability?dateFrom=${point.from}&dateTo=${point.to}`)}
    />
    
    <HorizontalBars data={[{ name: 'agent-a', values: [42, 3], href: '/observability?filterEntityName=agent-a' }]} />
    
    <MetricsDataTable columns={cols} data={rows} getRowHref={row => `/observability?filterThreadId=${row.threadId}`} />
    
    <MetricsCard>
      <MetricsCard.TopBar>
        <MetricsCard.TitleAndDescription title="Latency" />
        <MetricsCard.Actions>
          <IconButton href="/observability" />
        </MetricsCard.Actions>
      </MetricsCard.TopBar>
    </MetricsCard>
  • Added a new pill variant on TabList with an animated background indicator that slides behind the active trigger. The default line variant now animates its underline smoothly between tabs as well. Implemented by migrating the underlying Tabs component from Radix UI to Base UI. (#16414)

    // Before — only the line (underline) style was available
    <Tabs defaultTab="overview">
      <TabList>
        <Tab value="overview">Overview</Tab>
        <Tab value="projects">Projects</Tab>
      </TabList>
    </Tabs>
    
    // After — opt into the new pill style via the `variant` prop on TabList
    <Tabs defaultTab="overview">
      <TabList variant="pill">
        <Tab value="overview">Overview</Tab>
        <Tab value="projects">Projects</Tab>
      </TabList>
    </Tabs>

    The public API (Tabs, TabList, Tab, TabContent) is unchanged; existing call-sites keep the default line variant.

  • Added new pill-ghost variant on Tabs and sticky prop on TabList for sticky tab headers. (#16433)

    <Tabs defaultTab="overview">
      <TabList variant="pill-ghost" sticky>
        <Tab value="overview">Overview</Tab>
        <Tab value="settings">Settings</Tab>
      </TabList>
    </Tabs>

    Added variant prop on Combobox (default, ghost, link) for flexible trigger styling. Note: this prop existed previously but was a no-op; it now actually drives the trigger appearance, so callers passing variant will see updated styles.

    // Bordered form input (default)
    <Combobox options={options} value={value} onValueChange={setValue} />
    
    // Borderless, hover-only surface
    <Combobox options={options} value={value} onValueChange={setValue} variant="ghost" />
    
    // Text-only trigger
    <Combobox options={options} value={value} onValueChange={setValue} variant="link" />

    Improved EntityHeader layout — title and children now share a single row with wrapping, and padding is tighter for denser headers.

    Improved ScrollArea to handle vertical/horizontal orientation correctly, preventing forced horizontal scroll when only vertical is needed.

    Improved PanelSeparator with a pill-shaped handle that grows on hover/active for a clearer affordance.

    Removed Threads, ThreadList, ThreadItem, ThreadLink, ThreadDeleteButton exports. These had no consumers outside this repository. Downstream users relying on these exports should compose an equivalent list locally using the underlying DS primitives (Button, Icon, Txt) — the playground package now contains a reference implementation under src/components/thread-list.

    - import { Threads, ThreadList, ThreadItem, ThreadLink, ThreadDeleteButton } from '@mastra/playground-ui';
    + // Compose locally with @mastra/playground-ui primitives (Button, Icon, Txt)
    + // or copy the reference implementation from the playground package.
  • Added MainSidebar.NavLabel — collapse-aware label slot for asChild nav items. When the sidebar collapses to icon-only mode, the label hides via VisuallyHidden (still announced by screen readers) instead of leaking outside the 36px icon rail. The default link={...} path was already collapse-aware; asChild consumers now have a matching primitive. (#16167)

    // Before: text leaked visually when the sidebar collapsed
    <MainSidebar.NavLink asChild>
      <button>
        <Bot />
        Agents
      </button>
    </MainSidebar.NavLink>
    
    // After: wrap labels in MainSidebar.NavLabel
    <MainSidebar.NavLink asChild>
      <button>
        <Bot />
        <MainSidebar.NavLabel>Agents</MainSidebar.NavLabel>
      </button>
    </MainSidebar.NavLink>

    Improved truncation handling for nav items and section headers. Long labels now clip with a single-line ellipsis instead of wrapping to a second line during the collapse/expand transition, eliminating layout jumps at narrow widths.

  • Added SettingsRow primitive for label/description + control rows in settings pages. Markup mirrors the existing platform settings row pattern (flex justify-between with title + optional description on the left, control on the right) so consumers can adopt it without visual regressions. (#16150)

    Removed the redundant SelectField wrapper. Its only internal consumer (Studio settings) was migrated to SettingsRow + Select primitives. For form fields use SelectFieldBlock; for settings rows use SettingsRow.

    Before

    <SelectField name="theme" label="Theme mode" value={theme} onValueChange={setTheme} options={THEME_OPTIONS} />

    After

    <SettingsRow label="Theme mode" htmlFor="theme">
      <Select value={theme} onValueChange={setTheme}>
        <SelectTrigger id="theme" className="w-full sm:w-48">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>{/* items */}</SelectContent>
      </Select>
    </SettingsRow>
  • Added InputGroup and extended ButtonsGroup in playground-ui design system. (#16417)

    New InputGroup component

    Compose inputs with leading or trailing icons, buttons, text labels, and keyboard hints. Supports inline (left/right) and block (top/bottom) addon alignment, and works with both inputs and textareas.

    import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupButton } from '@mastra/playground-ui';
    import { SearchIcon, XIcon } from 'lucide-react';
    
    <InputGroup>
      <InputGroupAddon>
        <SearchIcon />
      </InputGroupAddon>
      <InputGroupInput placeholder="Search..." />
      <InputGroupAddon align="inline-end">
        <InputGroupButton aria-label="Clear">
          <XIcon />
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>;

    Extended ButtonsGroup

    Added orientation (horizontal | vertical), and new ButtonsGroupSeparator and ButtonsGroupText slots. Existing API unchanged.

    <ButtonsGroup spacing="close">
      <Button variant="outline">−</Button>
      <ButtonsGroupText>42</ButtonsGroupText>
      <Button variant="outline">+</Button>
    </ButtonsGroup>
    
    <ButtonsGroup orientation="vertical">
      <Button variant="ghost">Copy</Button>
      <ButtonsGroupSeparator />
      <Button variant="ghost">Cut</Button>
    </ButtonsGroup>

    Tweaked Button ghost variant

    Aligned hover/active progression with the outline variant (surface3surface4) so click feedback is perceptible on transparent backgrounds. Existing ghost buttons throughout the playground will appear one shade lighter on hover and active.

Patch Changes
  • Improved Studio's Traces page to scale smoothly to many traces. The list now renders only the visible window, so scrolling stays responsive and memory usage stays bounded regardless of how many traces are loaded. (#16262)

  • Filter pills (PropertyFilterApplied) now match the rest of the design system — single 1px border, consistent rounded segments, no custom styling. Labels stay on one line and no longer compress when long values are present. (#16426)

    ButtonsGroupText segments also no longer wrap to multiple lines or shrink under flex pressure, which makes them safer to drop into any tight layout.

  • Fixed three issues on the Logs and Traces pages: (#16306)

    • Column widths now stay stable while scrolling — they no longer jump as different rows scroll into view.
    • Scrolling far down the Logs list no longer scrambles rows (duplicates, gaps, or empty rows after additional pages load).
    • Changing a filter or the date range now scrolls the list back to the top, instead of leaving an empty band above the new data until you nudge the scroll. Logs and Traces now behave the same way on filter changes.
  • Added NoTracesInfo component that informs the user there are no traces for the active date range. (#16303)

  • Refreshed the visual style of form controls and popups for a softer, more consistent look: (#16150)

    • Button: thinner border (border instead of border-2); text-mode buttons use rounded-full; icon-mode buttons are circular.
    • Combobox / Select / DropdownMenu / Command: triggers and items aligned on the form-input look — rounded-lg border, transparent background, soft hover/open states, consistent text-ui-smd typography.
    • Popups (Popover / Tooltip / Select / Dropdown content): rounded-xl containers with shadow-dialog; inner items rounded-lg inside p-1.
    • Tokens: bumped the radius scale (sm 2→4px, md 4→6px, lg 6→10px, xl 12→14px); replaced --shadow-dialog's outer 1px ring with an inset top gloss so the dialog shadow stops doubling up with each consumer's own border.
  • Polished Combobox dropdown items (#16411)

    • Moved the selection check to the right of each item so unselected rows no longer carry an awkward left padding gap and the whole list aligns consistently.
    • Tightened popup search/empty padding and softened the trigger hover for a calmer command-palette feel.

    Added ComboboxPrimitive export for advanced compositions

    Re-exports the raw @base-ui/react/combobox parts (Root, Trigger, Input, List, Item, Chips, etc.) so callers needing virtualization, async status, chips, or creatable patterns can compose them directly with the shared comboboxStyles tokens — without growing the monolithic <Combobox> prop surface.

    import { ComboboxPrimitive, comboboxStyles } from '@mastra/playground-ui';
    
    <ComboboxPrimitive.Root items={items}>
      <ComboboxPrimitive.Input className={comboboxStyles.searchInput} />
      <ComboboxPrimitive.Portal>
        <ComboboxPrimitive.Positioner>
          <ComboboxPrimitive.Popup className={comboboxStyles.popup}>
            <ComboboxPrimitive.List className={comboboxStyles.list}>
              {item => (
                <ComboboxPrimitive.Item value={item} className={comboboxStyles.item}>
                  {item.label}
                </ComboboxPrimitive.Item>
              )}
            </ComboboxPrimitive.List>
          </ComboboxPrimitive.Popup>
        </ComboboxPrimitive.Positioner>
      </ComboboxPrimitive.Portal>
    </ComboboxPrimitive.Root>;
  • Aligned Badge variant colors with the Notice and Toast palette so the design system uses one consistent set of semantic colors. Default badges keep their neutral surface, while success, error, info and warning variants now use the same notice tokens as Notices and Toasts. Icons inside badges are sized down to match the badge height. (#16215)

  • Improved Studio's Logs page to scale smoothly to many log records. The list now renders only the visible window, so scrolling stays responsive and memory usage stays bounded regardless of how many logs are loaded. (#16263)

  • Fixed Studio streaming render behavior for interleaved reasoning and improved chat autoscroll during rapid output. (#16331)

  • Restored auto-refresh on the traces list page, polling every 10 seconds. Polling is paused while the request is forbidden (HTTP 403) to avoid flicker. (#16204)

  • Refined Combobox and Select trigger interactions with an active state and fixed value truncation when a leading badge is rendered. Refreshed PanelSeparator with a clearer hover/active affordance, an enlarged hit area, and a focus-visible accent. Removed the default bg-surface2 background from Threads so consumers can control the surface color. (#16269)

  • Changed the default Observability list mode to branches (all traces, including nested). The query logic still recognizes ?listMode=traces to opt back into the top-level-only view. (#16493)

    Before

    /observability → top-level traces only

    After

    /observability → branches (all traces, nested too) /observability?listMode=traces → top-level traces only

  • Fixed Notice component alignment with action: button now stays at default sm size, vertically aligns to first text line via negative margin compensation, and stacks below the message as a full-width button on narrow containers. (#16150)

@mastra/react@0.3.0
Minor Changes
  • Added client, React, and Studio support for Agent signals in threaded chat. Threaded user messages now send through Agent signals, stream output is consumed from the thread subscription, echoed user messages are deduped by signal ID, file and image message contents are preserved, Studio can send follow-ups while a response is streaming, Studio subscribes to open threads so additional tabs can observe active streams, and the Studio stop button aborts the active thread subscription. React chat also falls back to legacy threaded streaming when it connects to a server or core version that does not support signal routes yet. (#16338)

    const { sendMessage } = useChat({ agentId, resourceId, threadId });
    await sendMessage({ message: 'Follow up while streaming', threadId });
Patch Changes
  • Fixed streaming chat messages so sending while an agent is running does not duplicate assistant output or leave the previous response marked as streaming. (#16338)

  • Fixed Studio streaming render behavior for interleaved reasoning and improved chat autoscroll during rapid output. (#16331)

  • Handle background-task-suspended chunks in toUIMessage so suspend metadata is restored after resume. (#16260)

@mastra/redis@1.1.1
Patch Changes
  • Per-key TTL support in RedisCache (#16283)

    RedisCache.set() now accepts an optional ttlMs argument that overrides the configured default TTL for a single entry. Sub-second values are rounded up to one second (Redis EXPIRE granularity); a non-positive value persists the entry without expiry.

    const cache = new RedisCache({ url: 'redis://...' });
    await cache.set('weather:nyc', payload, 60_000); // expires in 60s
    await cache.set('manifest', payload, 0); // persists indefinitely
  • Respect optional resourceId in getThreadById so scoped thread lookups return null when the thread belongs to a different resource. (#14237)

    Example:

    const thread = await memory.getThreadById({
      threadId: 'my-thread-id',
      resourceId: 'my-user-id',
    });
    // Returns null if the thread does not belong to 'my-user-id'.
@mastra/redis-streams@0.0.2
Patch Changes
  • Worker review fixes: (#16309)

    • Step-execution endpoint (POST /workflows/:id/runs/:runId/steps/execute) is now gated by Mastra's standard requiresAuth: true + authenticateToken pipeline rather than a parallel "worker secret" body field. The previously introduced workerSecret config knob and MASTRA_WORKER_SECRET env var have been removed (they were never released). To gate the endpoint on a standalone-worker deployment, configure an auth provider on the server's Mastra instance — without one the framework currently treats requiresAuth: true as a no-op for this route.
    • HttpRemoteStrategy now sends credentials as a normal Authorization: Bearer <token> header. The token comes from the new MASTRA_WORKER_AUTH_TOKEN env var or an explicit auth constructor option.
    • Honor the caller's abortSignal in HttpRemoteStrategy by combining it with the per-request timeout via AbortSignal.any (with a manual fallback for runtimes that don't expose it).
    • Implement comma-separated name filtering for the MASTRA_WORKERS env var. MASTRA_WORKERS=scheduler,backgroundTasks now boots only those named workers; MASTRA_WORKERS=false still disables all workers.
    • Restore Mastra.startEventEngine / stopEventEngine as @deprecated aliases for the renamed startWorkers / stopWorkers.
    • BackgroundTaskWorker now subscribes to PubSub in start() instead of init(), matching the lifecycle of the other workers and making isRunning accurately reflect subscription state.
    • RedisStreamsPubSub adds a maxDeliveryAttempts option (default 5) that drops events after the configured number of failed deliveries instead of redelivering forever, and replaces empty catch {} blocks with logger.warn/logger.debug calls.
    • RedisStreamsPubSub.unsubscribe(topic, cb) now honors the topic argument so the same callback can be subscribed to multiple topics independently.
    • PullTransport guards the async router callback against unhandled promise rejections by attaching a .catch that nacks the message.
    • Drop the dead MASTRA_WORKER_NAME env var injection in the CLI worker spawn (the bundle entrypoint already passes the worker name directly).
    • Add a real cross-process e2e auth suite (pubsub/redis-streams/src/auth-e2e.test.ts) covering happy path, wrong token, missing token, anonymous direct hits, and the no-auth-provider pin-down behavior.
    • Step-execution route now has a response schema, satisfying schema-consistency.test.ts.
    • Internal type cleanups (drop several as any casts in worker strategies and BackgroundTaskWorker).
    • RedisStreamsPubSub.maxDeliveryAttempts now rejects negative / NaN values at construction. 0 still means "no cap" for back-compat but emits a one-time warning; pass Infinity to disable the cap explicitly.
    • PullTransport accepts a logger and uses it for unhandled router-callback rejections instead of console.error.
    • BackgroundTaskWorker.start() now throws if init() was not called, matching the contract of the other workers.
    • Cross-process integration tests now spawn a single user-owned project (test-fixtures/cli-project/src/mastra/index.ts) through two generic entries that mirror what BuildBundler and WorkerBundler emit. The previous one-off server.entry.ts / worker.entry.ts / scheduler.entry.ts / background.entry.ts files have been deleted — they implied users hand-roll entry files, which they don't. Worker role is selected via MASTRA_WORKERS exactly as in production.

    Push-capable PubSub:

    • The PubSub abstract class now declares a supportedModes getter (defaulting to ['pull'] for backward compatibility) so consumers can tell whether a broker delivers events through a pull loop, an in-process push, or an out-of-process HTTP push. EventEmitterPubSub reports ['pull', 'push'] (EventEmitter dispatches synchronously and works for either path), @mastra/redis-streams reports ['pull'].
    • Mastra now exposes a public handleWorkflowEvent(event) method backed by a shared WorkflowEventProcessor. It is the single entry point used by the existing pull-mode OrchestrationWorker, by in-process push pubsubs (auto-wired during startWorkers()), and by the new POST /api/workflows/events route which lets push-mode brokers (GCP Pub/Sub push, SNS, EventBridge) deliver events over HTTP.
    • When the configured pubsub does not support 'pull', Mastra automatically skips creating an OrchestrationWorker and OrchestrationWorker.init() throws a clear error if it is constructed against a push-only pubsub.
    • WorkflowEventProcessor gains a handle(event) method that returns a structured { ok, retry } result. The original process(event, ack?) method is preserved as a thin wrapper for back-compat.

    Public-API example for a push-capable PubSub:

    import { Mastra } from '@mastra/core/mastra';
    import { EventEmitterPubSub } from '@mastra/core/pubsub';
    
    const mastra = new Mastra({
      // A push-capable broker (GCP Pub/Sub push, SNS, EventEmitter, …).
      // EventEmitterPubSub reports supportedModes = ['pull', 'push'].
      pubsub: new EventEmitterPubSub(),
      workflows: { myWorkflow },
    });
    
    // In-process push pubsubs are auto-wired here. For out-of-process
    // push (e.g. HTTP webhook from a cloud broker), POST the event to
    // /api/workflows/events on your Mastra server instead.
    await mastra.startWorkers();
    
    // Direct invocation (e.g. inside an HTTP handler that bridges from a
    // cloud broker's push delivery):
    await mastra.handleWorkflowEvent({
      id: 'evt-1',
      type: 'workflow.start',
      runId: 'run-1',
      createdAt: new Date(),
      data: { workflowId: 'myWorkflow', inputData: { name: 'world' } },
    });

    CI follow-ups:

    • Mastra only auto-registers SchedulerWorker when storage is configured. Without storage the worker would crash on startup (deps.storage.getStore on undefined); the scheduler now silently no-ops in that case, matching the pre-worker scheduler behavior.
    • SchedulerWorker.init defensively logs and returns when called without storage instead of throwing a TypeError.
    • RECEIVE_WORKFLOW_EVENT_ROUTE (POST /workflows/events) createdAt is now a plain z.string() on the wire and the handler converts it to a Date (validating "Invalid Date" -> 400). The previous union(...).transform().refine() schema couldn't be exercised by the shared adapter test suite because the generator didn't unwrap Zod 4's ZodPipe.
    • _test-utils/route-test-utils recognizes Zod 4's number_format check (used for int() / safeint()), and generateContextualValue now produces a valid ISO timestamp for createdAt / updatedAt fields.
@mastra/schema-compat@1.2.10
Patch Changes
  • Fixed Google-compatible schema conversion so Gemini accepts broad nullable tool parameters. (#16129)
@mastra/server@1.33.0
Minor Changes
  • Added in-memory A2A push notification support for task updates. (#16175)

    Clients can now register push notification configs with message/send, message/stream, or the tasks/pushNotificationConfig/* methods. The server advertises push notification support in the agent card and sends the current task snapshot to registered webhooks when a task reaches completed, failed, canceled, or input-required.

    Webhook delivery validates the configured URL and pins outbound delivery to the validated address to reduce DNS rebinding risk. This remains in-memory and best-effort; operators should still use normal egress controls and avoid exposing push delivery to networks with sensitive internal services unless they trust the configured webhook targets.

    await a2a.setTaskPushNotificationConfig({
      taskId: 'task-123',
      pushNotificationConfig: {
        url: 'https://example.com/a2a-webhook',
        token: 'session-token',
      },
    });
  • Allow stored Responses API follow-up requests to use previous_response_id without also passing agent_id. (#16246)

    When callers pass both previous_response_id and an explicit agent_id, mismatched agents now return a clear 400 response instead of looking like a missing stored response.

    The create-response schema now also rejects empty agent_id and previous_response_id strings.

  • Added client, React, and Studio support for Agent signals in threaded chat. Threaded user messages now send through Agent signals, stream output is consumed from the thread subscription, echoed user messages are deduped by signal ID, file and image message contents are preserved, Studio can send follow-ups while a response is streaming, Studio subscribes to open threads so additional tabs can observe active streams, and the Studio stop button aborts the active thread subscription. React chat also falls back to legacy threaded streaming when it connects to a server or core version that does not support signal routes yet. (#16338)

    const { sendMessage } = useChat({ agentId, resourceId, threadId });
    await sendMessage({ message: 'Follow up while streaming', threadId });
  • Added Agent signals for sending contextual messages into agent thread loops and subscribing to thread activity. (#16229)

    Call agent.sendSignal() to inject context into a running agent loop. When the thread is idle, that same signal becomes the prompt that starts the next loop by default. Use ifActive.behavior and ifIdle.behavior to deliver, persist, discard, or wake from a signal.

    Use agent.subscribeToThread() to follow the raw stream chunks for a memory thread, observe signal echoes with stable IDs, and abort the active stream for that thread.

    const subscription = await agent.subscribeToThread({ resourceId, threadId });
    
    void (async () => {
      for await (const part of subscription.stream) {
        if (part.type === 'finish') {
          subscription.unsubscribe();
        }
      }
    })();
    
    agent.sendSignal({ type: 'user-message', contents: 'Use the latest answer' }, { resourceId, threadId });
  • Responses streams now emit tool call events so clients can track tool arguments and results in real time. (#16285)

    Tool outputs now use consistent IDs (<toolCallId>:output) so streamed arguments can be matched to completed results.

    for await (const event of stream) {
      if (event.type === 'response.function_call_arguments.delta') {
        console.log(event.delta);
      }
    
      if (event.type === 'response.output_item.done' && event.item.type === 'function_call') {
        console.log(event.item.id);
      }
    }
  • Added support for signed A2A Agent Cards. (#16207)

    Example

    const mastra = new Mastra({
      server: {
        a2a: {
          agentCardSigning: {
            privateKey: process.env.A2A_AGENT_CARD_PRIVATE_KEY!,
            protectedHeader: {
              alg: 'ES256',
              kid: 'agent-card-key',
            },
            header: {
              issuer: 'mastra',
            },
          },
        },
      },
    });

    Mastra now conditionally signs served A2A Agent Cards via signAgentCard(...) when server.a2a.agentCardSigning is configured, and the A2A Agent Card response schema now includes the signatures array.

  • Added mastra api, a machine-readable runtime CLI for calling Mastra server resources with JSON input and output. (#16128)

    The new API CLI supports agents, workflows, tools, MCP servers, memory threads, working memory, observability traces/logs/scores, datasets, and experiments. It includes schema-aware request handling so a single JSON input is split into path, query, and body fields based on server route contracts, plus ergonomic raw-input wrapping for tool execution.

    Exposed a route-derived server API schema manifest at runtime and generated CLI route metadata from it, enabling --schema output, response-shape-aware normalization, and server-aligned pagination output.

  • Add POST /api/agents/:agentId/resume-stream-until-idle SSE route, mirroring agent.resumeStreamUntilIdle(). (#16260)

  • Worker review fixes: (#16309)

    • Step-execution endpoint (POST /workflows/:id/runs/:runId/steps/execute) is now gated by Mastra's standard requiresAuth: true + authenticateToken pipeline rather than a parallel "worker secret" body field. The previously introduced workerSecret config knob and MASTRA_WORKER_SECRET env var have been removed (they were never released). To gate the endpoint on a standalone-worker deployment, configure an auth provider on the server's Mastra instance — without one the framework currently treats requiresAuth: true as a no-op for this route.
    • HttpRemoteStrategy now sends credentials as a normal Authorization: Bearer <token> header. The token comes from the new MASTRA_WORKER_AUTH_TOKEN env var or an explicit auth constructor option.
    • Honor the caller's abortSignal in HttpRemoteStrategy by combining it with the per-request timeout via AbortSignal.any (with a manual fallback for runtimes that don't expose it).
    • Implement comma-separated name filtering for the MASTRA_WORKERS env var. MASTRA_WORKERS=scheduler,backgroundTasks now boots only those named workers; MASTRA_WORKERS=false still disables all workers.
    • Restore Mastra.startEventEngine / stopEventEngine as @deprecated aliases for the renamed startWorkers / stopWorkers.
    • BackgroundTaskWorker now subscribes to PubSub in start() instead of init(), matching the lifecycle of the other workers and making isRunning accurately reflect subscription state.
    • RedisStreamsPubSub adds a maxDeliveryAttempts option (default 5) that drops events after the configured number of failed deliveries instead of redelivering forever, and replaces empty catch {} blocks with logger.warn/logger.debug calls.
    • RedisStreamsPubSub.unsubscribe(topic, cb) now honors the topic argument so the same callback can be subscribed to multiple topics independently.
    • PullTransport guards the async router callback against unhandled promise rejections by attaching a .catch that nacks the message.
    • Drop the dead MASTRA_WORKER_NAME env var injection in the CLI worker spawn (the bundle entrypoint already passes the worker name directly).
    • Add a real cross-process e2e auth suite (pubsub/redis-streams/src/auth-e2e.test.ts) covering happy path, wrong token, missing token, anonymous direct hits, and the no-auth-provider pin-down behavior.
    • Step-execution route now has a response schema, satisfying schema-consistency.test.ts.
    • Internal type cleanups (drop several as any casts in worker strategies and BackgroundTaskWorker).
    • RedisStreamsPubSub.maxDeliveryAttempts now rejects negative / NaN values at construction. 0 still means "no cap" for back-compat but emits a one-time warning; pass Infinity to disable the cap explicitly.
    • PullTransport accepts a logger and uses it for unhandled router-callback rejections instead of console.error.
    • BackgroundTaskWorker.start() now throws if init() was not called, matching the contract of the other workers.
    • Cross-process integration tests now spawn a single user-owned project (test-fixtures/cli-project/src/mastra/index.ts) through two generic entries that mirror what BuildBundler and `Worker
@mastra/core@1.32.0

Highlights

Fine-Grained Authorization (FGA) across Mastra (core + server adapters + MCP)

Mastra now supports relationship-based, resource-level authorization with centralized enforcement before agent runs (generate()/stream()), tool/workflow execution, and memory thread access via new IFGAProvider/IFGAManager + checkFGA()/FGADeniedError. FGA is also enforced consistently in @mastra/server handlers, all built-in server adapters (Express/Fastify/Hono/Koa), and MCP tool execution.

WorkOS-backed FGA provider (@mastra/auth-workos)

New MastraFGAWorkos implements IFGAManager on top of the WorkOS Authorization API, including check/require/filterAccessible, resource CRUD, role assignments, org scoping, JWT/bearer-token context handling, and permission/resource mappings for multi-tenant setups.

Scheduled Workflows (cron-native) + scheduler + full HTTP/Studio/client support

Workflows can now declare schedule in createWorkflow() to run on cron with automatic promotion to the evented engine, type-checked inputData/initialState/requestContext, and safe multi-instance claiming (CAS) via WorkflowScheduler. End-to-end support includes new schedules storage domains/adapters (PG/LibSQL/MongoDB + in-memory for tests), new @mastra/server routes + @mastra/client-js schedule methods, and new Studio UI for listing schedules, viewing trigger history, and durable pause/resume.

MCP Apps: interactive UI resources over MCP (SEP-1865) + resource listing/reading APIs

MCP servers can now publish ui:// HTML app resources via appResources, with new listResources()/readResource() on MCPServerBase and client/server endpoints (MastraClient.getMcpServerResources() / readMcpServerResource(), plus @mastra/server routes). @mastra/mcp adds proxy helpers (MCPClientServerProxy, toMCPServerProxies()) and stamps serverId into tool metadata to support multi-server UI resolution.

Observability upgrades: nested-run querying + bounded high-cardinality metrics + Datadog bridge

New listBranches/getBranch (plus getSpans and getStructure) let you find and fetch runs even when an entity only appears as a nested span (not root traces), with corresponding HTTP endpoints and store implementations (ClickHouse/DuckDB). Metrics now support count_distinct plus server-side TopK (limit, ordering) for fast dashboards on high-cardinality fields; ClickHouse also adds skip indexes to speed drilldowns, and a new DatadogBridge keeps auto-instrumented spans correctly nested under Mastra spans.

Breaking Changes
  • Schedules storage schema changed (mastra_schedules, mastra_schedule_triggers): adds ownerType/ownerId, renames ScheduleTrigger.statusoutcome, adds trigger id PK plus triggerKind/parentTriggerId/metadata; GET /api/schedules/:id/triggers now returns outcome instead of status (no compat shim; scheduled workflows are alpha).
  • @mastra/client-js Vector return types changed to match runtime:
    • vector.getIndexes()string[] (was { indexes: string[] })
    • vector.upsert(){ ids: string[] } (was string[])
    • vector.query()QueryResult[] (was { results: QueryResult[] })

Changelog

@mastra/core@1.32.0
Minor Changes
  • Added Fine-Grained Authorization (FGA) support for relationship-based, resource-level access control. FGA answers "can this user perform this action on this specific resource?" — enabling multi-tenant isolation and per-resource permissions. (#15410)

    New interfaces: IFGAProvider (read-only checks) and IFGAManager (read + write operations) with types for access checks, resources, and role assignments.

    Enforcement at all execution points: FGA checks are automatically enforced before agent execution (generate(), stream()), tool execution, workflow execution, and memory thread access. When no FGA provider is configured, all checks are skipped (backward compatible).

    New utility: checkFGA() provides centralized FGA enforcement with FGADeniedError for denied checks. MastraMemory.checkThreadFGA() adds thread-level access control.

    Request-aware authorization: Resource ID resolvers receive request context so route-level FGA checks can derive tenant- or request-scoped resource IDs.

    Typed permission constants: Strongly-typed permission identifiers (e.g. 'agents:execute', 'workflows:execute', 'memory:threads:read') for use in authorization config and permissionMapping.

    const mastra = new Mastra({
      server: {
        fga: new MastraFGAWorkos({ apiKey, clientId }),
      },
    });
  • Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to mastra_schedules and mastra_schedule_triggers; scheduled workflows are still in alpha so no compat shim is provided. (#16166)

    • Schedule gains optional ownerType / ownerId so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset.
    • ScheduleTrigger.status is renamed to outcome and the type is widened to ScheduleTriggerOutcome so future outcome values can be added without another rename.
    • ScheduleTrigger gains a stable id primary key and new triggerKind, parentTriggerId, and metadata fields. triggerKind distinguishes schedule-fire rows from later queue-drain rows (used by upcoming heartbeat work); parentTriggerId links related rows; metadata carries outcome-specific context.
    • The libsql, pg, and mongodb adapters all add the new columns/indexes. Their @mastra/core peer dependency is tightened to >=1.32.0-0 <2.0.0-0 so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field.
    • Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The triggers response on GET /api/schedules/:id/triggers now returns outcome instead of status.
    • The bundled Studio (Mastra CLI) is updated to read outcome so the schedule detail page keeps polling and rendering publish-failure rows correctly.
  • Added listResources() and readResource() abstract methods to MCPServerBase, enabling MCP servers to expose app resources. These resources power interactive UI rendering (MCP Apps) in Studio and other consumers. (#16004)

  • Added count_distinct aggregation and server-side TopK to the metrics storage API so dashboards built on high-cardinality fields (like threadId or resourceId) stay fast and bounded. (#16137)

    New aggregation

    getMetricAggregate, getMetricBreakdown, and getMetricTimeSeries accept aggregation: 'count_distinct' with a distinctColumn. Backends pick the most efficient native implementation — uniq on ClickHouse, approx_count_distinct on DuckDB.

    distinctColumn is restricted to a low/medium-cardinality categorical allowlist (entityType, entityName, parentEntityType, parentEntityName, rootEntityType, rootEntityName, name, provider, model, environment, executionSource, serviceName). ID columns are not allowed — distinct counts over near-unique values converge to the row count and are rarely useful.

    await store.getMetricAggregate({
      name: ['mastra_llm_tokens_total'],
      aggregation: 'count_distinct',
      distinctColumn: 'model',
      filters: { timestamp: { start, end } },
    });

    Server-side TopK

    getMetricBreakdown accepts limit and orderDirection, so breakdowns never return the full cardinality of a column from the database. Ordering is always by the aggregated value; orderDirection flips between top-N (DESC, default) and bottom-N (ASC).

    await store.getMetricBreakdown({
      name: ['mastra_agent_duration_ms'],
      aggregation: 'sum',
      groupBy: ['threadId'],
      limit: 20,
      orderDirection: 'DESC',
    });
  • Added RegexFilterProcessor — a zero-cost regex-based content filter for blocking, redacting, or warning on pattern matches in agent messages. Includes built-in presets for PII, secrets, URLs, and prompt injection patterns. Supports input, output, and streaming phases. (#16058)

  • Added scheduled workflows. Declare a schedule on createWorkflow and Mastra fires the workflow on cron with no extra wiring. (#15830)

    import { createWorkflow } from '@mastra/core/workflows';
    
    const dailyReport = createWorkflow({
      id: 'daily-report',
      inputSchema: z.object({ date: z.string() }),
      outputSchema: z.object({ summary: z.string() }),
      schedule: {
        cron: '0 9 * * *',
        timezone: 'America/Los_Angeles',
        inputData: { date: 'today' },
      },
    })
      .then(/* steps */)
      .commit();

    A workflow with a schedule is auto-promoted to the evented engine, so scheduled fires share the same execution path as manual start() calls. inputData, initialState, and requestContext on the schedule are type-checked against the workflow's schemas at definition time. Pass an array of schedules with stable ids to fire one workflow on multiple crons.

    Mastra auto-instantiates a WorkflowScheduler when any registered workflow declares a schedule. The scheduler claims due schedules via compare-and-swap, so multiple instances polling the same storage cannot double-fire. Projects with no scheduled workflows pay zero cost. Configure with new Mastra({ scheduler: { tickIntervalMs, batchSize, enabled, onError } }).

    Requires a storage adapter that implements the new schedules domain (@mastra/libsql and @mastra/pg ship adapters; InMemorySchedulesStorage is included for tests). Adds a croner dependency.

  • Added MCP Apps extension support (SEP-1865). MCPServer now accepts an appResources config to register interactive ui:// HTML resources. MCPClient preserves full tool _meta (including ui.resourceUri) when converting MCP tools to Mastra tools. Both advertise the io.modelcontextprotocol/ui extension capability. (#16004)

    Example — MCPServer with app resources:

    const server = new MCPServer({
      name: 'my-server',
      tools: { myTool },
      appResources: {
        dashboard: {
          name: 'Dashboard',
          description: 'Interactive dashboard UI',
          html: '<html>...</html>',
        },
      },
    });
  • Added CostGuardProcessor, a built-in processor for enforcing monetary cost limits across agent runs. Supports run, resource, and thread scopes with configurable time windows (default 7 days), blocking or warning when limits are reached. Also added onViolation callback to the base Processor interface for generalized violation handling across all processors. (#16057)

    import { Agent } from '@mastra/core/agent';
    import { CostGuardProcessor } from '@mastra/core/processors';
    
    const costGuard = new CostGuardProcessor({
      maxCost: 5.0,
      scope: 'resource',
      window: '24h',
      strategy: 'block',
    });
    
    costGuard.onViolation = ({ processorId, message, detail }) => {
      console.log(`[${processorId}] ${message}`, detail);
    };
    
    const agent = new Agent({
      name: 'my-agent',
      model: 'openai/gpt-5-nano',
      inputProcessors: [costGuard],
    });
  • Added listBranches and getBranch for querying named-entity invocations across traces, including nested ones. listTraces only returns root-rooted traces, so an entity that always runs as a child (e.g., an Observer agent inside a workflow) wasn't queryable before. (#16154)

    // Before: nested-only entities returned nothing
    await store.listTraces({ filters: { entityName: 'Observer' } }); // []
    
    // After: one row per AGENT_RUN, WORKFLOW_RUN, PROCESSOR_RUN, SCORER_RUN,
    // RAG_INGESTION, TOOL_CALL, or MCP_TOOL_CALL span
    await store.listBranches({ filters: { entityName: 'Observer' } });
    
    // Plus: fetch the subtree at any span, with optional depth
    const branch = await store.getBranch({ traceId, spanId, depth: 1 });

    Added getStructure({ traceId }) (canonical name for the lightweight trace skeleton; getTraceLight retained as a deprecated alias) and getSpans({ traceId, spanIds }) (batch-fetch spans by id, used internally by getBranch to avoid pulling whole traces).

Patch Changes
  • Update provider registry and model documentation with latest models and providers (6dcd65f)

  • Fixed Harness token usage so provider-reported totals, reasoning tokens, and cache token fields are preserved. Fixes https://github.com/mastra-ai/mastra/issues/16055 (#16072)

  • Fixed supervisor output processors so they can filter streamed chunks from delegated sub-agents. (#16071)

  • Fixed Observational Memory model resolution for user-defined gateways. Models such as cloudflare/google/gemini-2.5-flash-lite now resolve through registered gateways instead of failing with provider-config errors. Closes #13841. (#16083)

  • Fixed model step traces to show the final prompt sent to the model, including memory-injected system messages. (#16029)

  • Fixed tool results dropping provider metadata from the original tool call. (#16078)

  • Fixed workflow request context serialization to skip values that cannot be safely stored as JSON. Fixes #16043. (#16061)

  • Fix buildResumedBlockResult returning suspended instead of failed when a step throws after resume in a parallel or conditional block (#14410)

  • Fixed serializeRequestContext to handle plain Map instances passed as requestContext, restoring backward compatibility broken in #16061 (#16081)

  • Added direct score lookup support to observability storage so score records can be fetched by scoreId without scanning paginated score lists, including DuckDB and ClickHouse vNext observability stores. (#16162)

  • fix(harness): use type 'image' and mimeType for image parts in convertToHarnessMessage to fix Gemini image recognition (#13917)

  • Fixed TokenLimiterProcessor failing silently when no input messages fit the token budget. (#16063)

@mastra/auth-workos@1.2.0
Minor Changes
  • Added MastraFGAWorkos provider for Fine-Grained Authorization using the WorkOS Authorization API. Implements IFGAManager interface with support for: (#15410)

    • Authorization checks (check(), require(), filterAccessible())
    • Resource management (createResource(), getResource(), listResources(), updateResource(), deleteResource())
    • Role assignments (assignRole(), removeRole(), listRoleAssignments())
    • resourceMapping and permissionMapping for translating Mastra resource types and permissions to WorkOS resource type slugs and permission slugs
    • Organization scoping that denies access when the user is not a member of the configured organization
    • Bearer-token / verified JWT support that carries service-token FGA context such as organization membership IDs, while ignoring JWT-derived memberships unless organization claims are trusted
    • Membership caching and batched accessible-resource discovery for lower per-request latency
    • Tenant inference and parent-resource filtering for scoped access checks
    • Paginated organization membership lookup and limited concurrent FGA checks when filtering accessible resources
    • Typed permission constants accepted in permissionMapping
    import { MastraFGAWorkos } from '@mastra/auth-workos';
    
    const fga = new MastraFGAWorkos({
      organizationId: 'org_abc123',
      resourceMapping: {
        agent: { fgaResourceType: 'team', deriveId: ctx => ctx.user.teamId },
      },
      permissionMapping: {
        'agents:execute': 'manage-workflows',
      },
    });
    
    // Check whether a user can execute an agent
    const allowed = await fga.check(user, {
      resource: { type: 'agent', id: 'my-agent' },
      permission: 'agents:execute',
    });
Patch Changes
@mastra/clickhouse@1.7.0
Minor Changes
  • Improved metric drilldown performance with skip indexes on the high-cardinality ID columns of metric_events. Dashboard queries that filter metrics by traceId, threadId, resourceId, userId, organizationId, experimentId, runId, sessionId, or requestId skip data chunks that don't contain the filtered value instead of scanning the full time range. (#16138)

    Equality (=) and IN filters benefit automatically. Aggregations and GROUP BY queries without a filter on these columns are unaffected.

    Migration

    Existing deployments pick up the indexes on next start. The migration is metadata-only and instant — no table lock, no rewrite, no downtime. Insert overhead is negligible and index storage is well under 1% of table size. Existing data is indexed lazily as parts merge under normal retention; no operator action is required.

  • Added count_distinct aggregation and server-side TopK to the metrics storage API so dashboards built on high-cardinality fields (like threadId or resourceId) stay fast and bounded. (#16137)

    New aggregation

    getMetricAggregate, getMetricBreakdown, and getMetricTimeSeries accept aggregation: 'count_distinct' with a distinctColumn. Backends pick the most efficient native implementation — uniq on ClickHouse, approx_count_distinct on DuckDB.

    distinctColumn is restricted to a low/medium-cardinality categorical allowlist (entityType, entityName, parentEntityType, parentEntityName, rootEntityType, rootEntityName, name, provider, model, environment, executionSource, serviceName). ID columns are not allowed — distinct counts over near-unique values converge to the row count and are rarely useful.

    await store.getMetricAggregate({
      name: ['mastra_llm_tokens_total'],
      aggregation: 'count_distinct',
      distinctColumn: 'model',
      filters: { timestamp: { start, end } },
    });

    Server-side TopK

    getMetricBreakdown accepts limit and orderDirection, so breakdowns never return the full cardinality of a column from the database. Ordering is always by the aggregated value; orderDirection flips between top-N (DESC, default) and bottom-N (ASC).

    await store.getMetricBreakdown({
      name: ['mastra_agent_duration_ms'],
      aggregation: 'sum',
      groupBy: ['threadId'],
      limit: 20,
      orderDirection: 'DESC',
    });
    • Added listBranches and getSpans implementations. (#16154)
    • Only spans recorded after this version is deployed are queryable via listBranches; historical traces remain accessible through the existing listTraces / getTrace APIs.
Patch Changes
  • Added direct score lookup support to observability storage so score records can be fetched by scoreId without scanning paginated score lists, including DuckDB and ClickHouse vNext observability stores. (#16162)
@mastra/client-js@1.17.0
Minor Changes
  • Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to mastra_schedules and mastra_schedule_triggers; scheduled workflows are still in alpha so no compat shim is provided. (#16166)

    • Schedule gains optional ownerType / ownerId so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset.
    • ScheduleTrigger.status is renamed to outcome and the type is widened to ScheduleTriggerOutcome so future outcome values can be added without another rename.
    • ScheduleTrigger gains a stable id primary key and new triggerKind, parentTriggerId, and metadata fields. triggerKind distinguishes schedule-fire rows from later queue-drain rows (used by upcoming heartbeat work); parentTriggerId links related rows; metadata carries outcome-specific context.
    • The libsql, pg, and mongodb adapters all add the new columns/indexes. Their @mastra/core peer dependency is tightened to >=1.32.0-0 <2.0.0-0 so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field.
    • Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The triggers response on GET /api/schedules/:id/triggers now returns outcome instead of status.
    • The bundled Studio (Mastra CLI) is updated to read outcome so the schedule detail page keeps polling and rendering publish-failure rows correctly.
  • Added getMcpServerResources() and readMcpServerResource() methods to MastraClient for listing and reading MCP server resources from the client SDK. These methods enable frontend applications to fetch app resource HTML for interactive MCP Apps rendering. (#16004)

    const client = new MastraClient();
    
    // List resources on an MCP server
    const resources = await client.getMcpServerResources('my-server');
    
    // Read a specific app resource
    const resource = await client.readMcpServerResource('my-server', 'ui://calculator/app');
  • Added schedule methods to the client for the new scheduled workflows feature. (#15830)

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
    
    const schedules = await client.listSchedules({ workflowId: 'daily-report' });
    const schedule = await client.getSchedule('wf_daily-report');
    const triggers = await client.listScheduleTriggers('wf_daily-report', { limit: 50 });
    
    await client.pauseSchedule('wf_daily-report');
    await client.resumeSchedule('wf_daily-report');

    Pause is durable across redeploys. Resume recomputes the next fire time from now so a long-paused schedule does not fire a backlog.

  • Added listBranches and getBranch endpoints. Use these to find specific runs of an agent, workflow, tool, or processor — even when they are nested inside another trace — and to fetch the subtree of spans rooted at any single span. (#16177)

    GET /observability/branches?spanType=agent_run&entityName=Observer
    GET /observability/traces/:traceId/branches/:spanId?depth=1

    Each row in listBranches is a single anchor span (one of AGENT_RUN, WORKFLOW_RUN, PROCESSOR_RUN, SCORER_RUN, RAG_INGESTION, TOOL_CALL, MCP_TOOL_CALL), so entities that always run as a child (e.g., an Observer agent inside a workflow) — previously not listable through listTraces — are now queryable via the HTTP API. getBranch accepts an optional depth (0 = anchor only; omitted = full subtree).

    Follow-up to https://github.com/mastra-ai/mastra/pull/16154 which added the underlying @mastra/core storage APIs.

  • Fixed Vector resource return types so they match what the server actually returns. Previously the types declared shapes that did not exist at runtime, leading to runtime failures with no TypeScript errors. (#16036)

    What changed

    • vector.getIndexes() now returns string[] (was { indexes: string[] })
    • vector.upsert() now returns { ids: string[] } (was string[])
    • vector.query() now returns QueryResult[] (was { results: QueryResult[] })

    Before

    const response = await client.getVector('docs').getIndexes();
    console.log(response.indexes); // undefined at runtime

    After

    const indexes = await client.getVector('docs').getIndexes();
    console.log(indexes[0]); // 'docs-index'

    Closes #15089.

Patch Changes
  • Fixed client stream handling for step completion and finish chunks that omit step result details. (#9146)

  • Added MCP Apps extension support (SEP-1865). MCPServer now accepts an appResources config to register interactive ui:// HTML resources. MCPClient preserves full tool _meta (including ui.resourceUri) when converting MCP tools to Mastra tools. Both advertise the io.modelcontextprotocol/ui extension capability. (#16004)

    Example — MCPServer with app resources:

    const server = new MCPServer({
      name: 'my-server',
      tools: { myTool },
      appResources: {
        dashboard: {
          name: 'Dashboard',
          description: 'Interactive dashboard UI',
          html: '<html>...</html>',
        },
      },
    });
  • Added server-generated route contract types for the JavaScript client SDK and updated the SDK to use those generated request and response types. (#15519)

@mastra/convex@1.0.10
Patch Changes
  • Fixed @mastra/convex workflow snapshot persistence when snapshots contain $-prefixed JSON Schema keys (for example $schema and $ref). (#16169) Snapshots are now stored safely, preventing Convex validation failures during workflow runs. Fixes #16110.

  • Improved Convex bulk insert and delete throughput. (#16149)

@mastra/datadog@1.1.0
Minor Changes
  • Added a new DatadogBridge integration for Mastra tracing so Datadog can keep auto-instrumented HTTP, database, and framework spans nested under the agent, workflow, model, and tool spans that triggered them. (#15716)

    import tracer from 'dd-trace';
    
    tracer.init({
      service: process.env.DD_SERVICE || 'my-mastra-app',
      env: process.env.DD_ENV || 'production',
    });
    
    import { Mastra } from '@mastra/core';
    import { Observability } from '@mastra/observability';
    import { DatadogBridge } from '@mastra/datadog';
    
    const mastra = new Mastra({
      observability: new Observability({
        configs: {
          default: {
            serviceName: 'my-mastra-app',
            bridge: new DatadogBridge({
              mlApp: process.env.DD_LLMOBS_ML_APP!,
            }),
          },
        },
      }),
    });
Patch Changes
@mastra/duckdb@1.3.0
Minor Changes
    • Added listBranches and getSpans implementations. (#16154)
    • Historical span data is queryable immediately; no migration required.
  • Added count_distinct aggregation and server-side TopK to the metrics storage API so dashboards built on high-cardinality fields (like threadId or resourceId) stay fast and bounded. (#16137)

    New aggregation

    getMetricAggregate, getMetricBreakdown, and getMetricTimeSeries accept aggregation: 'count_distinct' with a distinctColumn. Backends pick the most efficient native implementation — uniq on ClickHouse, approx_count_distinct on DuckDB.

    distinctColumn is restricted to a low/medium-cardinality categorical allowlist (entityType, entityName, parentEntityType, parentEntityName, rootEntityType, rootEntityName, name, provider, model, environment, executionSource, serviceName). ID columns are not allowed — distinct counts over near-unique values converge to the row count and are rarely useful.

    await store.getMetricAggregate({
      name: ['mastra_llm_tokens_total'],
      aggregation: 'count_distinct',
      distinctColumn: 'model',
      filters: { timestamp: { start, end } },
    });

    Server-side TopK

    getMetricBreakdown accepts limit and orderDirection, so breakdowns never return the full cardinality of a column from the database. Ordering is always by the aggregated value; orderDirection flips between top-N (DESC, default) and bottom-N (ASC).

    await store.getMetricBreakdown({
      name: ['mastra_agent_duration_ms'],
      aggregation: 'sum',
      groupBy: ['threadId'],
      limit: 20,
      orderDirection: 'DESC',
    });
Patch Changes
  • Improved performance of listTraces and listBranches on DuckDB. The Traces and Branches lists in the observability UI now load noticeably faster, especially on large span tables, because filtering and pagination happen up front and the store only assembles full span data for the rows on the page being viewed. (#16165)

    No API or behavior changes — return shapes and filter semantics are unchanged, and no migration is required.

  • Added direct score lookup support to observability storage so score records can be fetched by scoreId without scanning paginated score lists, including DuckDB and ClickHouse vNext observability stores. (#16162)

@mastra/editor@0.7.23
Patch Changes
  • Added MCP Apps extension support (SEP-1865). MCPServer now accepts an appResources config to register interactive ui:// HTML resources. MCPClient preserves full tool _meta (including ui.resourceUri) when converting MCP tools to Mastra tools. Both advertise the io.modelcontextprotocol/ui extension capability. (#16004)

    Example — MCPServer with app resources:

    const server = new MCPServer({
      name: 'my-server',
      tools: { myTool },
      appResources: {
        dashboard: {
          name: 'Dashboard',
          description: 'Interactive dashboard UI',
          html: '<html>...</html>',
        },
      },
    });
@mastra/express@1.3.17
Patch Changes
  • Added FGA enforcement to server adapter middleware, ensuring authorization checks are applied consistently across all built-in adapters. (#15410)
@mastra/fastify@1.3.17
Patch Changes
  • Added FGA enforcement to server adapter middleware, ensuring authorization checks are applied consistently across all built-in adapters. (#15410)
@mastra/hono@1.4.12
Patch Changes
  • Added FGA enforcement to server adapter middleware, ensuring authorization checks are applied consistently across all built-in adapters. (#15410)
@mastra/koa@1.5.0
Minor Changes
  • Improved the Koa adapter to make request routing more efficient as route counts grow. (#16050)

    Requests now move through a leaner routing path with lower middleware overhead, which helps Koa-based Mastra servers stay faster and produce cleaner request traces without changing the public API.

Patch Changes
  • Added FGA enforcement to server adapter middleware, ensuring authorization checks are applied consistently across all built-in adapters. (#15410)
@mastra/libsql@1.10.0
Minor Changes
  • Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to mastra_schedules and mastra_schedule_triggers; scheduled workflows are still in alpha so no compat shim is provided. (#16166)

    • Schedule gains optional ownerType / ownerId so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset.
    • ScheduleTrigger.status is renamed to outcome and the type is widened to ScheduleTriggerOutcome so future outcome values can be added without another rename.
    • ScheduleTrigger gains a stable id primary key and new triggerKind, parentTriggerId, and metadata fields. triggerKind distinguishes schedule-fire rows from later queue-drain rows (used by upcoming heartbeat work); parentTriggerId links related rows; metadata carries outcome-specific context.
    • The libsql, pg, and mongodb adapters all add the new columns/indexes. Their @mastra/core peer dependency is tightened to >=1.32.0-0 <2.0.0-0 so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field.
    • Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The triggers response on GET /api/schedules/:id/triggers now returns outcome instead of status.
    • The bundled Studio (Mastra CLI) is updated to read outcome so the schedule detail page keeps polling and rendering publish-failure rows correctly.
  • Added the schedules storage domain so LibSQL-backed Mastra apps can use scheduled workflows. Creates mastra_schedules and mastra_schedule_triggers tables on init. (#15830)

Patch Changes
@mastra/mcp@1.7.0
Minor Changes
  • Added MCP Apps support for interactive UI rendering over MCP. (#16004)

    MCPClientServerProxy — a lightweight proxy that delegates resource and tool operations to remote MCP servers via MCPClient, enabling Studio to fetch app resources from any connected server.

    toMCPServerProxies() — new convenience method on MCPClient that creates proxy objects for all configured servers, ready for Mastra-level registration.

    Automatic serverId stamping — tools returned by listTools() now carry _meta.ui.serverId, allowing consumers to resolve ui:// app resources from the correct MCP server in multi-server environments.

    const mcp = new MCPClient({
      servers: {
        myApps: { url: new URL('https://my-mcp-server.example.com/mcp') },
      },
    });
    
    const mastra = new Mastra({
      agents: { myAgent },
      mcpServers: { ...mcp.toMCPServerProxies() },
    });
  • Added MCP Apps extension support (SEP-1865). MCPServer now accepts an appResources config to register interactive ui:// HTML resources. MCPClient preserves full tool _meta (including ui.resourceUri) when converting MCP tools to Mastra tools. Both advertise the io.modelcontextprotocol/ui extension capability. (#16004)

    Example — MCPServer with app resources:

    const server = new MCPServer({
      name: 'my-server',
      tools: { myTool },
      appResources: {
        dashboard: {
          name: 'Dashboard',
          description: 'Interactive dashboard UI',
          html: '<html>...</html>',
        },
      },
    });
Patch Changes
  • Added Fine-Grained Authorization (FGA) enforcement to MCP tool execution. Both transport-driven calls and direct executeTool() calls now run the same authorization checks when a request user is present, and typed FGA permission constants are accepted in MCP server authorization config. (#15410)

  • Fixed trace parenting for long-lived MCP Stream connections. (#15716)

@mastra/memory@1.17.5
Patch Changes
  • Fixed Observational Memory model resolution for user-defined gateways. Models such as cloudflare/google/gemini-2.5-flash-lite now resolve through registered gateways instead of failing with provider-config errors. Closes #13841. (#16083)

  • Fixed async reflection buffering incorrectly triggering during idle timeout and provider-change activations when observation tokens are below the reflection activation threshold (#16076)

@mastra/mongodb@1.8.0
Minor Changes
  • Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to mastra_schedules and mastra_schedule_triggers; scheduled workflows are still in alpha so no compat shim is provided. (#16166)

    • Schedule gains optional ownerType / ownerId so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset.
    • ScheduleTrigger.status is renamed to outcome and the type is widened to ScheduleTriggerOutcome so future outcome values can be added without another rename.
    • ScheduleTrigger gains a stable id primary key and new triggerKind, parentTriggerId, and metadata fields. triggerKind distinguishes schedule-fire rows from later queue-drain rows (used by upcoming heartbeat work); parentTriggerId links related rows; metadata carries outcome-specific context.
    • The libsql, pg, and mongodb adapters all add the new columns/indexes. Their @mastra/core peer dependency is tightened to >=1.32.0-0 <2.0.0-0 so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field.
    • Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The triggers response on GET /api/schedules/:id/triggers now returns outcome instead of status.
    • The bundled Studio (Mastra CLI) is updated to read outcome so the schedule detail page keeps polling and rendering publish-failure rows correctly.
  • Added the schedules storage domain so MongoDB-backed Mastra apps can use scheduled workflows. Creates mastra_schedules and mastra_schedule_triggers collections on init, with default indexes on (status, next_fire_at) for due-schedule polling and (schedule_id, actual_fire_at) for trigger-history queries. (#15830)

Patch Changes
@mastra/observability@1.11.1
Patch Changes
  • Fixed model step traces to show the final prompt sent to the model, including memory-injected system messages. (#16029)

  • Added a new DatadogBridge integration for Mastra tracing so Datadog can keep auto-instrumented HTTP, database, and framework spans nested under the agent, workflow, model, and tool spans that triggered them. (#15716)

    import tracer from 'dd-trace';
    
    tracer.init({
      service: process.env.DD_SERVICE || 'my-mastra-app',
      env: process.env.DD_ENV || 'production',
    });
    
    import { Mastra } from '@mastra/core';
    import { Observability } from '@mastra/observability';
    import { DatadogBridge } from '@mastra/datadog';
    
    const mastra = new Mastra({
      observability: new Observability({
        configs: {
          default: {
            serviceName: 'my-mastra-app',
            bridge: new DatadogBridge({
              mlApp: process.env.DD_LLMOBS_ML_APP!,
            }),
          },
        },
      }),
    });
  • Reduced startup noise: CloudExporter missing-token message is now logged at debug level instead of warn, since being disabled is the expected state for local development (#16070)

@mastra/pg@1.10.0
Minor Changes
  • Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to mastra_schedules and mastra_schedule_triggers; scheduled workflows are still in alpha so no compat shim is provided. (#16166)

    • Schedule gains optional ownerType / ownerId so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset.
    • ScheduleTrigger.status is renamed to outcome and the type is widened to ScheduleTriggerOutcome so future outcome values can be added without another rename.
    • ScheduleTrigger gains a stable id primary key and new triggerKind, parentTriggerId, and metadata fields. triggerKind distinguishes schedule-fire rows from later queue-drain rows (used by upcoming heartbeat work); parentTriggerId links related rows; metadata carries outcome-specific context.
    • The libsql, pg, and mongodb adapters all add the new columns/indexes. Their @mastra/core peer dependency is tightened to >=1.32.0-0 <2.0.0-0 so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field.
    • Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The triggers response on GET /api/schedules/:id/triggers now returns outcome instead of status.
    • The bundled Studio (Mastra CLI) is updated to read outcome so the schedule detail page keeps polling and rendering publish-failure rows correctly.
  • Added the schedules storage domain so Postgres-backed Mastra apps can use scheduled workflows. Creates mastra_schedules and mastra_schedule_triggers tables on init, with default indexes on (status, next_fire_at) for due-schedule polling and (schedule_id, actual_fire_at) for trigger-history queries. (#15830)

Patch Changes
@mastra/playground-ui@26.0.0
Minor Changes
  • Improved the NoLogsInfo empty state. It now accepts optional datePreset, dateFrom, and dateTo props to show why no logs match the active range, suggests lowering the logging level, and links to the docs. Calling <NoLogsInfo /> without props keeps the original copy. (#16139)

  • Removed the deprecated Notification component. Use Notice for inline persistent context (errors, empty states) and toast (from @mastra/playground-ui's sonner wrapper) for transient feedback (success messages, confirmations). (#16033)

    // Before
    <Notification isVisible={true} type="error">Failed to load.</Notification>
    
    // After — inline persistent context
    <Notice variant="destructive">Failed to load.</Notice>
    
    // Before
    <Notification isVisible={true}>Saved successfully!</Notification>
    
    // After — transient feedback
    toast.info('Saved successfully');
  • Added Studio UI for scheduled workflows. (#15830)

    • /workflows/schedules lists every schedule across the project with the most recent run's status. Append ?workflowId=<id> to filter to a single workflow.
    • /workflows/schedules/:scheduleId shows the schedule's metadata, Pause/Resume controls, and paginated trigger history. Each trigger is deep-linked to its workflow run graph. The view polls every five seconds while any fired run is still active.
    • A workflow's detail header shows a Schedules action when it has at least one schedule.
  • Added SectionCard component to design system. Provides card primitive with tinted header strip (title, description, optional action slot), transparent body, and default/danger variants. Composes CardHeading for typography. Suitable for settings pages, dashboard sections, and grouped form layouts. (#16168)

    import { SectionCard } from '@mastra/playground-ui';
    
    <SectionCard title="Theme" description="Customize the appearance.">
      <ThemeSelector />
    </SectionCard>;
  • Redesigned the span token usage panel to show input vs output split with proportional bar and per-side detail breakdowns. DataKeysAndValues gained an optional density='dense' prop. (#16143)

  • Removed the CombinedButtons component. Use ButtonsGroup with spacing="close" for the same segmented-style cluster of toggle buttons. (#16035)

    // Before
    <CombinedButtons>
      <Button>Agent</Button>
      <Button>Model</Button>
    </CombinedButtons>
    
    // After
    <ButtonsGroup spacing="close">
      <Button>Agent</Button>
      <Button>Model</Button>
    </ButtonsGroup>
Patch Changes
  • Added support for icon-and-description layout in Notice by making title optional. When omitted, the notice renders as a single row with icon and description, useful for inline contextual messages. (#16033)

    // Before — title required
    <Notice variant="info" title="Heads up">Some message.</Notice>
    
    // After — title optional, single-row layout
    <Notice variant="info">Some message.</Notice>
  • Improved trace timeline span controls. Added tooltips with row counts to the expand-children, expand-all-descendants, and expand-at-this-level buttons. The expand-children button now collapses only the direct children rather than the entire subtree, and the descendants column gained a matching collapse-all-descendants action. Root spans show a single Expand all / Collapse all button using outward/inward double-chevrons. (#16173)

  • Refreshed toast styling so it aligns with the Notice component and lets sonner own the layout. (#16144)

    What changed for users:

    • Variant toasts (success / error / warning / info) now render with the same notice color tokens as the <Notice> component, including bg, border and text color in both light and dark mode.
    • Sonner's native layout is back in charge — the loader on toast.promise, the close button position, the icon placement and the mobile width all work as documented instead of fighting custom overrides.
    • The native close button has its own polished hover: it blends with the toast at rest and lifts with a tinted bg + stronger border on hover, in every variant and theme.
    • Sticky toasts can be made truly non-dismissible by passing both dismissible: false and closeButton: false.
    • toast.success / error / warning / info now return sonner's toast id (or an array of ids when called with an array of messages) so callers can keep dismissing or updating the toast they created.
  • Added a Scorer span type style on the trace timeline and a colored type dot before each span name so spans are visually flagged in both the name and timing columns. (#16160)

  • Migrated Files/Skills tabs and agent page tabs (Chat/Editor/Evaluate/Review/Traces) to the design-system Tabs component for consistent styling and accessibility (Radix tablist, arrow-key navigation). Also added a cursor-pointer on the Tab trigger and a disabled prop on the DS Tab. (#16148)

  • Added MCP Apps extension support (SEP-1865). MCPServer now accepts an appResources config to register interactive ui:// HTML resources. MCPClient preserves full tool _meta (including ui.resourceUri) when converting MCP tools to Mastra tools. Both advertise the io.modelcontextprotocol/ui extension capability. (#16004)

    Example — MCPServer with app resources:

    const server = new MCPServer({
      name: 'my-server',
      tools: { myTool },
      appResources: {
        dashboard: {
          name: 'Dashboard',
          description: 'Interactive dashboard UI',
          html: '<html>...</html>',
        },
      },
    });
  • Added an informational notice on the trace data panel pointing users to Mastra Studio (local or deployed) when "Evaluate Trace" and "Save as Dataset Item" actions are not available in the current view. (#16157)

  • Fixed pointer cursor on interactive form controls (Button, SelectTrigger, SelectItem) for better affordance. (#16140)

@mastra/react@0.2.34
Patch Changes
  • Fixed custom data stream parts so stable ids are preserved in React UI messages. (#16067)
@mastra/server@1.32.0
Minor Changes
  • Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to mastra_schedules and mastra_schedule_triggers; scheduled workflows are still in alpha so no compat shim is provided. (#16166)

    • Schedule gains optional ownerType / ownerId so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset.
    • ScheduleTrigger.status is renamed to outcome and the type is widened to ScheduleTriggerOutcome so future outcome values can be added without another rename.
    • ScheduleTrigger gains a stable id primary key and new triggerKind, parentTriggerId, and metadata fields. triggerKind distinguishes schedule-fire rows from later queue-drain rows (used by upcoming heartbeat work); parentTriggerId links related rows; metadata carries outcome-specific context.
    • The libsql, pg, and mongodb adapters all add the new columns/indexes. Their @mastra/core peer dependency is tightened to >=1.32.0-0 <2.0.0-0 so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field.
    • Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The triggers response on GET /api/schedules/:id/triggers now returns outcome instead of status.
    • The bundled Studio (Mastra CLI) is updated to read outcome so the schedule detail page keeps polling and rendering publish-failure rows correctly.
  • Added API endpoints for MCP server resources, enabling clients to list and read app resources for interactive UI rendering. (#16004)

    • GET /api/mcp/:serverId/resources — lists available resources on an MCP server
    • POST /api/mcp/:serverId/resources/read — reads a specific resource by URI (e.g. ui://calculator/app)
  • Added HTTP routes for scheduled workflows. (#15830)

    • GET /api/schedules — list schedules across the project, optionally filtered by workflowId.
    • GET /api/schedules/:scheduleId — fetch a schedule with its most recent run summary.
    • GET /api/schedules/:scheduleId/triggers — paginated trigger history joined to the corresponding workflow run.
    • POST /api/schedules/:scheduleId/pause and POST /api/schedules/:scheduleId/resume — durable pause/resume. Both require schedules:write and are idempotent. Resume recomputes nextFireAt from now so a long-paused schedule does not fire a backlog.
  • Added listBranches and getBranch endpoints. Use these to find specific runs of an agent, workflow, tool, or processor — even when they are nested inside another trace — and to fetch the subtree of spans rooted at any single span. (#16177)

    GET /observability/branches?spanType=agent_run&entityName=Observer
    GET /observability/traces/:traceId/branches/:spanId?depth=1

    Each row in listBranches is a single anchor span (one of AGENT_RUN, WORKFLOW_RUN, PROCESSOR_RUN, SCORER_RUN, RAG_INGESTION, TOOL_CALL, MCP_TOOL_CALL), so entities that always run as a child (e.g., an Observer agent inside a workflow) — previously not listable through listTraces — are now queryable via the HTTP API. getBranch accepts an optional depth (0 = anchor only; omitted = full subtree).

    Follow-up to https://github.com/mastra-ai/mastra/pull/16154 which added the underlying @mastra/core storage APIs.

  • Added Fine-Grained Authorization (FGA) enforcement across server handlers and memory APIs: (#15410)

    • Route-level checks on detail endpoints, custom routes (including request-aware resource ID resolvers and path parameters), and resource-scoped search
    • Thread-level checks on reads, writes, creation, cloning, message saving, and listing — with unviewable threads hidden from totals and pagination
    • Message deletion now denies access when the message's thread cannot be verified
    • Authenticated user context preserved through thread authorization, and the thread's owning resourceId forwarded into the FGA context so providers can derive composite tenant-scoped resource IDs
    • Route permission derivation and memory clone checks now use the correct resource context
    • Typed FGA permission constants accepted in route and thread authorization config
Patch Changes
  • Fixed A2A task resubscribe to return the current task snapshot and continue streaming live artifact and status updates for in-progress tasks. (#15987)

  • Added an observability score lookup endpoint at GET /observability/scores/:scoreId backed by observability storage. (#16162)

  • Added MCP Apps extension support (SEP-1865). MCPServer now accepts an appResources config to register interactive ui:// HTML resources. MCPClient preserves full tool _meta (including ui.resourceUri) when converting MCP tools to Mastra tools. Both advertise the io.modelcontextprotocol/ui extension capability. (#16004)

    Example — MCPServer with app resources:

    const server = new MCPServer({
      name: 'my-server',
      tools: { myTool },
      appResources: {
        dashboard: {
          name: 'Dashboard',
          description: 'Interactive dashboard UI',
          html: '<html>...</html>',
        },
      },
    });
  • Added server-generated route contract types for the JavaScript client SDK and updated the SDK to use those generated request and response types. (#15519)

@mastra/slack@1.1.1
Patch Changes
  • Fixed Slack app creation failing when agent description exceeds 139 characters. The manifest description is now automatically truncated to prevent the Slack API from rejecting the request. (#16093)
@mastra/temporal@0.1.0
Minor Changes
  • Added the new @mastra/temporal package for running Mastra workflows on Temporal. (#15789)

    What changed

    • Added init() to create Temporal-backed Mastra workflows and steps.
    • Added MastraPlugin to bundle workflow code for Temporal workers and load generated activities.
    • Added debug: true support to write transformed workflow modules and emitted bundles to .mastra/temporal.

    Example

    import { init } from '@mastra/temporal';
    import { MastraPlugin } from '@mastra/temporal/worker';
    import { Client, Connection } from '@temporalio/client';
    import { Worker } from '@temporalio/worker';
    
    const connection = await Connection.connect();
    const client = new Client({ connection });
    const { createWorkflow, createStep } = init({ client, taskQueue: 'mastra' });
    
    const step = createStep({ id: 'hello', execute: async () => 'world' });
    export const helloWorkflow = createWorkflow({ id: 'hello-workflow' }).then(step);
    
    await Worker.create({
      connection,
      taskQueue: 'mastra',
      plugins: [new MastraPlugin({ src: import.meta.resolve('./mastra/index.ts') })],
    });
Patch Changes
Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.31.0

Highlights

Platform Channels Framework (Core + Server + Client)

A new channels architecture adds ChannelProvider, a dedicated ChannelsStorage domain, and ChannelConnectResult connection flows (OAuth, deep link, immediate), with Mastra-level connect/disconnect/list APIs and matching MastraClient.channels methods.

Slack Channel Integration (@mastra/slack)

New Slack provider connects agents to Slack workspaces with OAuth-based app provisioning, manifest drift detection, encrypted credential storage, slash commands, and threaded conversation support.

NestJS Server Adapter (@mastra/nestjs)

New @mastra/nestjs adapter runs Mastra inside NestJS (Express) apps with native module registration, DI-friendly service injection, rate limiting, graceful shutdown, streaming, and MCP transport support; MastraServerBase is now exported to support adapters that manage routing.

Google Drive Workspace Filesystem (@mastra/google-drive)

New Google Drive WorkspaceFilesystem mounts a single Drive folder as an agent workspace, supporting OAuth tokens (with refresh callbacks) and service accounts, and implementing the full read/write/list/move/stat interface with optimistic concurrency via expectedMtime.

Real-time Voice Provider for AWS Nova Sonic (@mastra/voice-aws-nova-sonic)

Adds a bidirectional streaming voice provider for Bedrock Nova 2 Sonic with live mic streaming and playback, streaming transcription (speculative/final), barge-in detection, multi-voice selection, and tool calling with per-session RequestContext.

Breaking Changes
  • @mastra/playground-ui: IconButton export removed (use Button with size="icon-*") and <Alert> removed in favor of <Notice>; variant="light"/"inputLike" replaced by variant="default".

Changelog

@mastra/core@1.31.0
Minor Changes
  • Enhanced load_tool to accept an array of tool names, enabling bulk tool loading in a single call. Returns 'loaded', 'notFound', and 'alreadyLoaded' arrays for clearer response shape. (#15472)

  • Added Microsoft Entra ID authentication support for Azure OpenAI gateways, so Azure deployments can call models without API keys when using Azure SDK credentials. (#15983)

  • Added platform channels framework with ChannelProvider interface, ChannelsStorage domain, and ChannelConnectResult discriminated union supporting OAuth, deep link, and immediate connection flows. Channels can be registered on the Mastra instance and expose connect/disconnect/list APIs for platform integrations. (#15876)

  • Added top-level environment config on Mastra to tag observability signals with the deployment environment. (#15956)

    Set it once on the Mastra instance and it will be attached to all observability signals automatically. Falls back to process.env.NODE_ENV when unset; per-call tracingOptions.metadata.environment still takes precedence.

    Before

    await agent.generate('hello', {
      tracingOptions: { metadata: { environment: process.env.NODE_ENV } },
    });

    After

    new Mastra({
      environment: 'production',
      observability: new Observability({ ... }),
    })

    mastra.getEnvironment() returns the resolved value.

  • Fixed trajectory scorers in dataset.startExperiment receiving raw agent messages instead of a Trajectory object, which caused a crash when accessing run.output.steps. Trajectory scorers now receive the same pre-extracted Trajectory that runEvals provides. (#15693)

    The scorers option now also accepts the same categorised shape as runEvals (AgentScorerConfig / WorkflowScorerConfig), so you no longer need to rewrite your scorer config when moving from runEvals to dataset.startExperiment.

    Before (trajectory scorer crashed at runtime):

    await dataset.startExperiment({ scorers: [orderScorer] }) // run.output.steps was undefined

    After (works correctly, both flat and categorised forms accepted):

    await dataset.startExperiment({ scorers: [orderScorer] }) await dataset.startExperiment({ scorers: { agent: [accuracyScorer], trajectory: [orderScorer] } })

    Per-step scorers are now also supported for workflow targets, matching runEvals. Pass scorers: { workflow: [...], steps: { stepId: [...] }, trajectory: [...] } to score individual workflow steps with their own scorers; results carry the originating stepId and keep targetScope: 'span' (with targetEntityType: WORKFLOW_STEP on the underlying scorer run), matching how runEvals encodes step identity.

  • Workspace search now supports batch-capable embedders. Pass an embedder branded with batch: true (and an optional maxBatchSize) to embed all pending chunks for a flush in a single provider call instead of one call per chunk. This dramatically reduces index-rebuild time on large workspaces when using providers that support batch embedding (e.g. OpenAI's embedMany). Existing single-text embedders continue to work unchanged. (#14735)

    import { embedMany } from 'ai';
    import { openai } from '@ai-sdk/openai';
    
    const model = openai.embedding('text-embedding-3-small');
    
    const workspace = new Workspace({
      // ...
      embedder: Object.assign(
        async (texts: string[]) => {
          const { embeddings } = await embedMany({ model, values: texts });
          return embeddings;
        },
        { batch: true as const, maxBatchSize: 2048 },
      ),
    });
Patch Changes
  • Update provider registry and model documentation with latest models and providers (1723e09)

  • Fixed workflow runs not being cancellable when steps or conditions ignored the abort signal. Cancelling a run now correctly stops dountil, dowhile, and foreach loops at every cancellation boundary — between iterations, after a step returns, after the loop condition is evaluated, and (for foreach) between concurrency chunks and after the final chunk. Previously, long-running loops (e.g. a dountil with a setTimeout inside the step) would keep running and eventually emit success even after the run was cancelled. Closes #15990. (#15994)

  • Fixed type inference on workflow loop helpers (foreach, dowhile, dountil) so a step's requestContextSchema correctly aligns with the workflow's requestContextSchema. Previously these methods dropped the workflow's TRequestContext from the step parameter, causing TypeScript to reject typed-context steps even when the workflow declared a matching schema. Steps without a requestContextSchema are still accepted; steps whose schema does not match the workflow's now produce a type error. Fixes #15989. (#15995)

  • Fixed sub-agent delegation so nested tool results stay out of the parent model context by default while remaining available to application code. Set delegation.includeSubAgentToolResultsInModelContext to include the full subagent result in the parent model context. (#15832)

  • Added a coalesced display state subscription API for Harness. (#15974)

    This helps UI clients render fewer updates while still receiving the latest state. The example below renders the initial state, then subscribes to coalesced updates with the default windowMs and maxWaitMs timing options.

    render(harness.getDisplayState());
    
    const unsubscribe = harness.subscribeDisplayState(render, {
      windowMs: 250,
      maxWaitMs: 500,
    });
  • Fixed BatchPartsProcessor using a hardcoded id in batched text-delta chunks. The real message id and runId are now preserved from the original chunks, preventing AI SDK UIMessage stream from dropping batched deltas. (#14974)

  • Add filterAfterToolSteps to ToolCallFilter so tool calls can be filtered during agentic loops after they are no longer recent. By default, ToolCallFilter keeps its previous behavior and only filters the initial input. (#15795)

  • Workspace search no longer throws when requesting hybrid or vector mode if the configuration does not support it. The search tool now gracefully falls back to the best available mode instead of throwing an error. (#14533)

  • Workspace file tools no longer use misleading absolute-path examples (e.g. /data/output.txt) that caused weaker LLMs to attempt writes at the actual filesystem root. The example paths in read_file and write_file are now relative. (#14544)

    Additionally, when a contained workspace rejects an absolute path that escapes its boundary, the resulting PermissionError now guides the agent toward a relative path so it can self-correct on the next turn. When the path's first segment names a real directory in the workspace (e.g. /src/app.ts with an existing src/), the error suggests the exact relative form. Otherwise it falls back to a generic hint instead of inventing a misleading suggestion for genuinely out-of-workspace paths like /etc/passwd.

    Fixes #14542

  • Fixed SkillSearchProcessor so agents use it as the on-demand skill discovery path without also adding eager skill context. (#15916)

    When SkillSearchProcessor is configured, agents no longer auto-add the eager SkillsProcessor, and they hide the overlapping skill and skill_search tools while keeping skill_read available for supporting skill files. Workspace file tools can still read SKILL.md files during explicit file inspection or editing workflows.

  • Fixed tool calls to run in parallel when active tools exclude approval or suspending tools. (#15978)

    • SearchEngine: indexMany uses p-map with a default concurrency of 8 when vector embedding runs, with optional concurrency and stopOnError (same semantics as p-map). Lazy vector indexing flushes pending documents at the same concurrency, drains the queue before awaiting so concurrent index calls are not dropped, loops until the queue is empty before search, dedupes by document id (last wins), and re-queues the batch if a flush throws. (#14735)
    • Workspace: Search auto-indexing reads files in parallel with a bounded concurrency, skips unreadable paths, awaits batch indexing, and falls back to per-file indexing when the batch path throws. Successful single-file indexing returns the path so callers can track what was indexed.
  • Fix semantic recall indexing to honor read-only memory mode. (#15949)

  • Fixed Linux bubblewrap failing when Workspace mounts use symlinks under LocalSandbox by resolving mount paths to real directories for isolation allowlists. (#15498)

@mastra/arize@1.0.22
Patch Changes
  • Renamed emitted OTel GenAI cache usage attributes to match the OpenTelemetry semantic conventions: (#15966)

    • gen_ai.usage.cached_input_tokensgen_ai.usage.cache_read.input_tokens
    • gen_ai.usage.cache_write_tokensgen_ai.usage.cache_creation.input_tokens

    gen_ai.usage.input_tokens is unchanged and remains the total prompt-token count. Cache attributes are emitted separately as subsets of that total.

    Updated Arize, Arthur, and Sentry mappings so cache values continue to flow through those exporters.

    Direct consumers should update any dashboards, alerts, or queries that reference the old attribute names.

@mastra/arthur@0.2.8
Patch Changes
  • Renamed emitted OTel GenAI cache usage attributes to match the OpenTelemetry semantic conventions: (#15966)

    • gen_ai.usage.cached_input_tokensgen_ai.usage.cache_read.input_tokens
    • gen_ai.usage.cache_write_tokensgen_ai.usage.cache_creation.input_tokens

    gen_ai.usage.input_tokens is unchanged and remains the total prompt-token count. Cache attributes are emitted separately as subsets of that total.

    Updated Arize, Arthur, and Sentry mappings so cache values continue to flow through those exporters.

    Direct consumers should update any dashboards, alerts, or queries that reference the old attribute names.

@mastra/clickhouse@1.6.0
Minor Changes
  • Added ClickhouseStoreVNext, a ClickHouse storage adapter that uses the vNext observability domain by default. Equivalent to constructing a ClickhouseStore and overriding the observability domain manually, but exposed as a single class for new projects. (#15984)

    import { Mastra } from '@mastra/core';
    import { ClickhouseStoreVNext } from '@mastra/clickhouse';
    
    export const mastra = new Mastra({
      storage: new ClickhouseStoreVNext({
        id: 'clickhouse-storage',
        url: process.env.CLICKHOUSE_URL!,
        username: process.env.CLICKHOUSE_USERNAME!,
        password: process.env.CLICKHOUSE_PASSWORD!,
      }),
    });

    ClickhouseStoreVNext accepts the same configuration as ClickhouseStore and reuses the same ClickHouse client across every domain. ClickhouseStore continues to work for projects on the legacy observability schema.

Patch Changes
@mastra/client-js@1.16.0
Minor Changes
  • Added Channels resource to MastraClient with listPlatforms, listInstallations, connect, and disconnect methods for managing platform channel integrations. (#15876)
Patch Changes
@mastra/convex@1.0.9
Patch Changes
  • Fixed Convex workflow snapshot loads to use the workflow/run index. (#15971)
@mastra/dynamodb@1.0.5
Patch Changes
  • Fixed an issue where automatic thread title generation was skipped when using the DynamoDB storage adapter. The adapter was overwriting empty thread titles with a Thread <id> placeholder on save, which prevented the title-generation step (gated on an empty title) from running. Empty titles now round-trip correctly so generated titles work the same as with other storage adapters. Resolves #15998. (#16003)
@mastra/fastify@1.3.16
Patch Changes
  • Fix multipart file handling in Fastify adapter by aligning return type with other adapters and preventing stream hang on file size limit. (#15796)

  • Fix multipart upload tests to register the multipart content-type parser. The tests were manually adding the preHandler hook but skipping registerContextMiddleware(), which meant Fastify rejected multipart/form-data requests with 415 Unsupported Media Type. (#16002)

@mastra/google-drive@0.1.0
Minor Changes
  • Add @mastra/google-drive, a new Google Drive WorkspaceFilesystem provider that mounts a single Drive folder as an agent workspace. Supports OAuth access tokens, async refresh callbacks, and service account (JWT) authentication. Implements the full WorkspaceFilesystem interface — read, write, list, copy, move, mkdir, rmdir, stat, exists — plus expectedMtime optimistic concurrency. (#15756)

    import { Agent } from '@mastra/core/agent';
    import { Workspace } from '@mastra/core/workspace';
    import { GoogleDriveFilesystem } from '@mastra/google-drive';
    
    const workspace = new Workspace({
      filesystem: new GoogleDriveFilesystem({
        folderId: process.env.GOOGLE_DRIVE_FOLDER_ID!,
        accessToken: process.env.GOOGLE_DRIVE_ACCESS_TOKEN!,
      }),
    });
    
    const agent = new Agent({
      id: 'drive-agent',
      name: 'Drive Agent',
      model: 'openai/gpt-4o-mini',
      workspace,
    });

    A matching googleDriveFilesystemProvider descriptor is also exported for MastraEditor.

Patch Changes
  • GoogleDriveFilesystem tweaks: mkdir defaults to recursive, appendFile uses optimistic concurrency, rmdir skips redundant child listing, JSON body requests include Content-Type header, readFile uses consistent searchParams, and concurrent token refreshes are deduplicated. (#16010)
@mastra/libsql@1.9.1
Patch Changes
  • Added platform channels framework with ChannelProvider interface, ChannelsStorage domain, and ChannelConnectResult discriminated union supporting OAuth, deep link, and immediate connection flows. Channels can be registered on the Mastra instance and expose connect/disconnect/list APIs for platform integrations. (#15876)
@mastra/mongodb@1.7.4
Patch Changes
  • Removed unsupported minScore query option from MongoDB vector store docs and README. Exported MongoDBQueryVectorParams so callers can type documentFilter for MongoDBVector.query(). (#15936)

    Fixes #15715

@mastra/nestjs@0.1.0
Minor Changes
  • Add NestJS server adapter (@mastra/nestjs) for running Mastra with NestJS Express applications. Provides native module registration, DI-based service injection, rate limiting, graceful shutdown, streaming, and MCP transport support. (#12751)

    import { Module } from '@nestjs/common';
    import { MastraModule } from '@mastra/nestjs';
    import { mastra } from './mastra';
    
    @Module({
      imports: [MastraModule.register({ mastra })],
    })
    export class AppModule {}
Patch Changes
@mastra/observability@1.11.0
Minor Changes
  • Auto-attach the Mastra-level environment to all observability signals. (#15956)
Patch Changes
@mastra/otel-exporter@1.0.21
Patch Changes
  • Renamed emitted OTel GenAI cache usage attributes to match the OpenTelemetry semantic conventions: (#15966)

    • gen_ai.usage.cached_input_tokensgen_ai.usage.cache_read.input_tokens
    • gen_ai.usage.cache_write_tokensgen_ai.usage.cache_creation.input_tokens

    gen_ai.usage.input_tokens is unchanged and remains the total prompt-token count. Cache attributes are emitted separately as subsets of that total.

    Updated Arize, Arthur, and Sentry mappings so cache values continue to flow through those exporters.

    Direct consumers should update any dashboards, alerts, or queries that reference the old attribute names.

@mastra/perplexity@0.1.0
Minor Changes
  • Added new @mastra/perplexity integration with the Perplexity Search tool for agents. (#15939)

    import { createPerplexityTools } from '@mastra/perplexity';
    
    const { perplexitySearch } = createPerplexityTools({ apiKey: process.env.PERPLEXITY_API_KEY });
Patch Changes
@mastra/pg@1.9.4
Patch Changes
  • Fixed workflow snapshot sanitization in @mastra/pg for strings containing escaped surrogate patterns like [^\ud800-\udfff]. This prevents invalid JSON escape sequences that caused PostgreSQL jsonb writes to fail with error 22P02. (#15923)

    Fixes #15920

  • Added platform channels framework with ChannelProvider interface, ChannelsStorage domain, and ChannelConnectResult discriminated union supporting OAuth, deep link, and immediate connection flows. Channels can be registered on the Mastra instance and expose connect/disconnect/list APIs for platform integrations. (#15876)

@mastra/playground-ui@25.0.0
Minor Changes
  • Refactored Button component to use a single cva (class-variance-authority) variant config instead of nested manual maps. Consolidated IconButton into Button via size="icon-sm|icon-md|icon-lg" and removed the IconButton export. Replaced variant="light" and variant="inputLike" with variant="default" (no behavior change for default styling). Added cta and outline variants and unified active/hover styles between text- and icon-mode buttons. (#15985)

    Why: A single source of truth for variants means consistent visuals, fewer drift bugs, simpler maintenance, and a more predictable surface for AI agents — single-variant cva is the dominant shadcn pattern across DS components in this repo (Card, Input, Label, Textarea, StatusBadge).

    Migration:

    // Before
    import { IconButton } from '@mastra/playground-ui';
    <IconButton><Settings /></IconButton>
    <Button variant="light">…</Button>
    <Combobox variant="inputLike" />
    
    // After
    import { Button } from '@mastra/playground-ui';
    <Button size="icon-md"><Settings /></Button>
    <Button variant="default">…</Button>
    <Combobox variant="default" />
  • Removed <Alert> in favor of <Notice>. The two components had significant visual and behavioral overlap; <Notice> is now the single banner primitive and supports every previous <Alert> use case. (#15791)

    <Notice> is also redesigned with a flatter API: title and icon are now props, each variant ships a default icon, an optional action prop renders a button aligned to the title, and a new note variant has been added alongside warning, destructive, info, and success. Theme tokens (notice-warning, notice-destructive, notice-info, notice-success, notice-note) replace the previous hardcoded colors.

    Migration

    // Before
    <Alert variant="warning">
      <AlertTitle>Provider not connected</AlertTitle>
      <AlertDescription as="p">Set the API key environment variable.</AlertDescription>
    </Alert>
    
    // After
    <Notice variant="warning" title="Provider not connected">
      <Notice.Message>Set the API key environment variable.</Notice.Message>
    </Notice>
Patch Changes
  • Removed the "Avg Score" KPI card from the Metrics dashboard and the avg-score summary from the Scores card. (#15967)

  • Fixed row click behavior in the dataset experiments compare view. Clicking a row while selection mode is active now toggles the row's selection instead of navigating to the experiment. Clicking directly on the checkbox no longer also triggers the row click handler. (#15492)

  • Aligned AlertDialog visual styling with Dialog component for design system consistency. AlertDialog now uses the same surface tokens, border radius, shadow, animation curves, and typography scale as Dialog. The accessibility primitive remains separate (preserves role="alertdialog" and explicit Action/Cancel semantics) — only the visual shell was synced. Also added AlertDialog.Body for parity with Dialog. (#15988)

@mastra/react@0.2.33
Patch Changes
  • Fixed suspended tool run IDs not being preserved after page refresh. (#15107)
@mastra/sentry@1.0.21
Patch Changes
  • Renamed emitted OTel GenAI cache usage attributes to match the OpenTelemetry semantic conventions: (#15966)

    • gen_ai.usage.cached_input_tokensgen_ai.usage.cache_read.input_tokens
    • gen_ai.usage.cache_write_tokensgen_ai.usage.cache_creation.input_tokens

    gen_ai.usage.input_tokens is unchanged and remains the total prompt-token count. Cache attributes are emitted separately as subsets of that total.

    Updated Arize, Arthur, and Sentry mappings so cache values continue to flow through those exporters.

    Direct consumers should update any dashboards, alerts, or queries that reference the old attribute names.

@mastra/server@1.31.0
Patch Changes
  • Fix GET /tools/:toolId and POST /tools/:toolId/execute to find dynamically-resolved agent tools (provided via toolsResolver / function-based tools) when they are not in the static tool registry. Errors thrown by an individual agent's listTools() during the lookup are now logged as warnings instead of being silently swallowed. (#13989)

  • Fixed memory query validation when optional JSON query params are omitted with newer Zod versions. (#15969)

  • Export MastraServerBase from @mastra/core/server so framework adapters that manage routing independently can share the same server base class. (#12751)

  • Added platform channels framework with ChannelProvider interface, ChannelsStorage domain, and ChannelConnectResult discriminated union supporting OAuth, deep link, and immediate connection flows. Channels can be registered on the Mastra instance and expose connect/disconnect/list APIs for platform integrations. (#15876)

@mastra/slack@1.1.0
Minor Changes
  • Added @mastra/slack channel integration for connecting AI agents to Slack workspaces. Provides automatic Slack app provisioning via OAuth, manifest management with drift detection, encrypted credential storage, slash command support, and threaded conversation handling. Usage: (#15876)

    import { SlackProvider } from '@mastra/slack';
    
    const mastra = new Mastra({
      channels: {
        slack: new SlackProvider({
          refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN!,
        }),
      },
    });
    
    // Connect an agent to Slack
    const result = await mastra.channels.slack.connect('my-agent');
    // result.type === 'oauth' → redirect user to result.authorizationUrl
Patch Changes
@mastra/voice-aws-nova-sonic@0.1.0
Minor Changes
  • Add new @mastra/voice-aws-nova-sonic voice provider for AWS Bedrock Nova 2 Sonic. (#13232)

    The provider exposes a real-time bidirectional voice interface backed by the InvokeModelWithBidirectionalStreamCommand API on AWS Bedrock, including:

    • Live microphone streaming (send / listen) and assistant audio playback via speaking events
    • Live transcription via writing events with SPECULATIVE / FINAL generation stages
    • Barge-in / interrupt detection
    • Speaker selection across all 18 Nova Sonic voices and configurable endpointing sensitivity
    • Tool calling with per-session RequestContext
    • Configurable AWS region, model id, credentials (or default credential provider chain), and inference / turn-detection parameters
Patch Changes
Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.30.0

Highlights

Durable Agents + Resumable Streams (crash/disconnect resilient execution)

New DurableAgent support lets agent streams resume after client disconnects and continue through server crashes/restarts by caching stream events and enabling reconnection via observe(runId, { offset }).

Workflow-backed “Durable Execution” (Evented + Inngest strategies)

Agents can now run outside the HTTP request using workflow execution (createEventedAgent for built-in evented engine, createInngestAgent for Inngest), enabling reliable long-running tool loops while clients subscribe to progress.

Pluggable PubSub + Cache infrastructure (Redis/Upstash-ready)

Durable streaming is backed by a PubSub + ServerCache layer (defaults: EventEmitterPubSub + InMemoryServerCache), with recommended production configs using Redis-backed implementations so any instance can serve reconnect/replay.

Improved A2A streaming artifacts in @mastra/server

A2A streaming now emits incremental artifact updates during the full agent stream while still preserving final structured output artifacts.

Observability noise/volume reduction by default

Cloud observability uploads now filter model chunk spans by default and raise the default observability log level to warn, reducing data volume and chatter.

Breaking Changes
  • None called out in this changelog.

Changelog

@mastra/core@1.30.0
Minor Changes
  • Add durable agents with resumable streams (#12557)

    Durable agents make agent execution resilient to disconnections, crashes, and long-running operations.

    The Problem

    Standard agent streaming has two fragility points:

    1. Connection drops - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed.
    2. Long-running operations - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost.
    The Solution

    Resumable streams solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive.

    Durable execution solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress.

    Usage

    Wrap any existing Agent with durability using factory functions:

    import { Agent } from '@mastra/core/agent';
    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const agent = new Agent({
      id: 'my-agent',
      model: openai('gpt-4'),
      instructions: 'You are helpful',
    });
    
    const durableAgent = createDurableAgent({ agent });

    Factory functions for different execution strategies:

    FactoryExecutionUse Case
    createDurableAgent({ agent })Local, synchronousDevelopment, simple deployments
    createEventedAgent({ agent })Fire-and-forget via workflow engineLong-running operations
    createInngestAgent({ agent, inngest })Inngest-poweredProduction, distributed systems
    Resumable Streams
    // Start streaming
    const { runId, output } = await durableAgent.stream('Analyze this data...');
    
    // Client disconnects at event 5...
    
    // Reconnect and resume from where we left off
    const { output: resumed } = await durableAgent.observe(runId, { offset: 6 });
    // Receives events 6, 7, 8... from cache, then continues with live events
    PubSub and Cache

    Durable agents use two infrastructure components:

    ComponentPurposeDefault
    PubSubReal-time event delivery during streamingEventEmitterPubSub
    CacheStores events for replay on reconnectionInMemoryServerCache

    When stream() is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When observe() is called, missed events replay from cache before continuing with live events.

    Configure via Mastra instance (recommended):

    const mastra = new Mastra({
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
      agents: {
        // Inherits cache and pubsub from Mastra
        myAgent: createDurableAgent({ agent }),
      },
    });

    Configure per-agent (overrides Mastra):

    const durableAgent = createDurableAgent({
      agent,
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
    });

    Disable caching (streams won't be resumable):

    const durableAgent = createDurableAgent({ agent, cache: false });

    For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests.

    Class Hierarchy
    • DurableAgent extends Agent - base class with resumable streams
    • EventedAgent extends DurableAgent - fire-and-forget execution
    • InngestAgent extends DurableAgent - Inngest-powered execution
Patch Changes
  • Fixed a regression in 1.29.0 where configuring an agent with channel adapters (e.g. channels.adapters.slack) caused server startup to crash with a "Custom API route ... must not start with /api" error. The custom-route prefix validation now skips framework-generated webhook routes. (#15952)

  • Update provider registry and model documentation with latest models and providers (d587199)

  • Fix MCP client support in the agent editor: (#15945)

    • MCP client form dirty state: Save button now enables after adding/removing MCP clients
    • MCP tool name matching: Both bare and namespaced tool names are matched correctly
    • Auth token forwarding: Token from cookie or header is forwarded to auth-protected MCP servers
    • String interpolation: Request context variables in system prompts now resolve correctly
@mastra/client-js@1.15.2
Patch Changes
  • Remove incorrect deprecation markers from getTask() and cancelTask() in the Mastra A2A client. (#15941)

  • Add durable agents with resumable streams (#12557)

    Durable agents make agent execution resilient to disconnections, crashes, and long-running operations.

    The Problem

    Standard agent streaming has two fragility points:

    1. Connection drops - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed.
    2. Long-running operations - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost.
    The Solution

    Resumable streams solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive.

    Durable execution solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress.

    Usage

    Wrap any existing Agent with durability using factory functions:

    import { Agent } from '@mastra/core/agent';
    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const agent = new Agent({
      id: 'my-agent',
      model: openai('gpt-4'),
      instructions: 'You are helpful',
    });
    
    const durableAgent = createDurableAgent({ agent });

    Factory functions for different execution strategies:

    FactoryExecutionUse Case
    createDurableAgent({ agent })Local, synchronousDevelopment, simple deployments
    createEventedAgent({ agent })Fire-and-forget via workflow engineLong-running operations
    createInngestAgent({ agent, inngest })Inngest-poweredProduction, distributed systems
    Resumable Streams
    // Start streaming
    const { runId, output } = await durableAgent.stream('Analyze this data...');
    
    // Client disconnects at event 5...
    
    // Reconnect and resume from where we left off
    const { output: resumed } = await durableAgent.observe(runId, { offset: 6 });
    // Receives events 6, 7, 8... from cache, then continues with live events
    PubSub and Cache

    Durable agents use two infrastructure components:

    ComponentPurposeDefault
    PubSubReal-time event delivery during streamingEventEmitterPubSub
    CacheStores events for replay on reconnectionInMemoryServerCache

    When stream() is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When observe() is called, missed events replay from cache before continuing with live events.

    Configure via Mastra instance (recommended):

    const mastra = new Mastra({
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
      agents: {
        // Inherits cache and pubsub from Mastra
        myAgent: createDurableAgent({ agent }),
      },
    });

    Configure per-agent (overrides Mastra):

    const durableAgent = createDurableAgent({
      agent,
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
    });

    Disable caching (streams won't be resumable):

    const durableAgent = createDurableAgent({ agent, cache: false });

    For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests.

    Class Hierarchy
    • DurableAgent extends Agent - base class with resumable streams
    • EventedAgent extends DurableAgent - fire-and-forget execution
    • InngestAgent extends DurableAgent - Inngest-powered execution
@mastra/editor@0.7.22
Patch Changes
  • Fix MCP client support in the agent editor: (#15945)
    • MCP client form dirty state: Save button now enables after adding/removing MCP clients
    • MCP tool name matching: Both bare and namespaced tool names are matched correctly
    • Auth token forwarding: Token from cookie or header is forwarded to auth-protected MCP servers
    • String interpolation: Request context variables in system prompts now resolve correctly
@mastra/hono@1.4.10
Patch Changes
  • Fixed a regression in 1.29.0 where configuring an agent with channel adapters (e.g. channels.adapters.slack) caused server startup to crash with a "Custom API route ... must not start with /api" error. The custom-route prefix validation now skips framework-generated webhook routes. (#15952)
@mastra/inngest@1.3.0
Minor Changes
  • Add durable agents with resumable streams (#12557)

    Durable agents make agent execution resilient to disconnections, crashes, and long-running operations.

    The Problem

    Standard agent streaming has two fragility points:

    1. Connection drops - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed.
    2. Long-running operations - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost.
    The Solution

    Resumable streams solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive.

    Durable execution solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress.

    Usage

    Wrap any existing Agent with durability using factory functions:

    import { Agent } from '@mastra/core/agent';
    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const agent = new Agent({
      id: 'my-agent',
      model: openai('gpt-4'),
      instructions: 'You are helpful',
    });
    
    const durableAgent = createDurableAgent({ agent });

    Factory functions for different execution strategies:

    FactoryExecutionUse Case
    createDurableAgent({ agent })Local, synchronousDevelopment, simple deployments
    createEventedAgent({ agent })Fire-and-forget via workflow engineLong-running operations
    createInngestAgent({ agent, inngest })Inngest-poweredProduction, distributed systems
    Resumable Streams
    // Start streaming
    const { runId, output } = await durableAgent.stream('Analyze this data...');
    
    // Client disconnects at event 5...
    
    // Reconnect and resume from where we left off
    const { output: resumed } = await durableAgent.observe(runId, { offset: 6 });
    // Receives events 6, 7, 8... from cache, then continues with live events
    PubSub and Cache

    Durable agents use two infrastructure components:

    ComponentPurposeDefault
    PubSubReal-time event delivery during streamingEventEmitterPubSub
    CacheStores events for replay on reconnectionInMemoryServerCache

    When stream() is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When observe() is called, missed events replay from cache before continuing with live events.

    Configure via Mastra instance (recommended):

    const mastra = new Mastra({
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
      agents: {
        // Inherits cache and pubsub from Mastra
        myAgent: createDurableAgent({ agent }),
      },
    });

    Configure per-agent (overrides Mastra):

    const durableAgent = createDurableAgent({
      agent,
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
    });

    Disable caching (streams won't be resumable):

    const durableAgent = createDurableAgent({ agent, cache: false });

    For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests.

    Class Hierarchy
    • DurableAgent extends Agent - base class with resumable streams
    • EventedAgent extends DurableAgent - fire-and-forget execution
    • InngestAgent extends DurableAgent - Inngest-powered execution
  • Update peer dependencies to match core package version bump (1.0.5) (#12557)

Patch Changes
@mastra/memory@1.17.4
Patch Changes
  • Fixed idle timeout and provider-change observation activations blocking on in-progress reflection buffering. These triggers now return immediately, letting the background reflection complete asynchronously. (#15937)
@mastra/observability@1.10.3
Patch Changes
  • Add durable agents with resumable streams (#12557)

    Durable agents make agent execution resilient to disconnections, crashes, and long-running operations.

    The Problem

    Standard agent streaming has two fragility points:

    1. Connection drops - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed.
    2. Long-running operations - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost.
    The Solution

    Resumable streams solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive.

    Durable execution solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress.

    Usage

    Wrap any existing Agent with durability using factory functions:

    import { Agent } from '@mastra/core/agent';
    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const agent = new Agent({
      id: 'my-agent',
      model: openai('gpt-4'),
      instructions: 'You are helpful',
    });
    
    const durableAgent = createDurableAgent({ agent });

    Factory functions for different execution strategies:

    FactoryExecutionUse Case
    createDurableAgent({ agent })Local, synchronousDevelopment, simple deployments
    createEventedAgent({ agent })Fire-and-forget via workflow engineLong-running operations
    createInngestAgent({ agent, inngest })Inngest-poweredProduction, distributed systems
    Resumable Streams
    // Start streaming
    const { runId, output } = await durableAgent.stream('Analyze this data...');
    
    // Client disconnects at event 5...
    
    // Reconnect and resume from where we left off
    const { output: resumed } = await durableAgent.observe(runId, { offset: 6 });
    // Receives events 6, 7, 8... from cache, then continues with live events
    PubSub and Cache

    Durable agents use two infrastructure components:

    ComponentPurposeDefault
    PubSubReal-time event delivery during streamingEventEmitterPubSub
    CacheStores events for replay on reconnectionInMemoryServerCache

    When stream() is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When observe() is called, missed events replay from cache before continuing with live events.

    Configure via Mastra instance (recommended):

    const mastra = new Mastra({
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
      agents: {
        // Inherits cache and pubsub from Mastra
        myAgent: createDurableAgent({ agent }),
      },
    });

    Configure per-agent (overrides Mastra):

    const durableAgent = createDurableAgent({
      agent,
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
    });

    Disable caching (streams won't be resumable):

    const durableAgent = createDurableAgent({ agent, cache: false });

    For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests.

    Class Hierarchy
    • DurableAgent extends Agent - base class with resumable streams
    • EventedAgent extends DurableAgent - fire-and-forget execution
    • InngestAgent extends DurableAgent - Inngest-powered execution
  • Reduced default cloud observability volume by filtering model chunk spans from CloudExporter uploads by default and raising the default observability log level to warn. (#15815)

@mastra/playground-ui@24.0.2
Patch Changes
  • Updated the look and motion of Dialog. The surface is now lighter and translucent with a subtle backdrop blur, the typography is tighter, and the open/close animation feels snappier. SideDialog and AlertDialog pick up the refined ambient shadow as well, since they share the same shadow style. (#15958)

  • Polished DataList visuals: removed the trailing "No more data to load" message and dropped the bottom border on the last row for a cleaner end-of-list appearance. (#15959)

  • Refined the DataPanel loading state with a smaller spinner and tightened layout for a less prominent appearance. (#15965)

@mastra/redis@1.1.0
Minor Changes
  • Update peer dependencies to match core package version bump (1.0.5) (#12557)
Patch Changes
  • Add durable agents with resumable streams (#12557)

    Durable agents make agent execution resilient to disconnections, crashes, and long-running operations.

    The Problem

    Standard agent streaming has two fragility points:

    1. Connection drops - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed.
    2. Long-running operations - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost.
    The Solution

    Resumable streams solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive.

    Durable execution solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress.

    Usage

    Wrap any existing Agent with durability using factory functions:

    import { Agent } from '@mastra/core/agent';
    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const agent = new Agent({
      id: 'my-agent',
      model: openai('gpt-4'),
      instructions: 'You are helpful',
    });
    
    const durableAgent = createDurableAgent({ agent });

    Factory functions for different execution strategies:

    FactoryExecutionUse Case
    createDurableAgent({ agent })Local, synchronousDevelopment, simple deployments
    createEventedAgent({ agent })Fire-and-forget via workflow engineLong-running operations
    createInngestAgent({ agent, inngest })Inngest-poweredProduction, distributed systems
    Resumable Streams
    // Start streaming
    const { runId, output } = await durableAgent.stream('Analyze this data...');
    
    // Client disconnects at event 5...
    
    // Reconnect and resume from where we left off
    const { output: resumed } = await durableAgent.observe(runId, { offset: 6 });
    // Receives events 6, 7, 8... from cache, then continues with live events
    PubSub and Cache

    Durable agents use two infrastructure components:

    ComponentPurposeDefault
    PubSubReal-time event delivery during streamingEventEmitterPubSub
    CacheStores events for replay on reconnectionInMemoryServerCache

    When stream() is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When observe() is called, missed events replay from cache before continuing with live events.

    Configure via Mastra instance (recommended):

    const mastra = new Mastra({
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
      agents: {
        // Inherits cache and pubsub from Mastra
        myAgent: createDurableAgent({ agent }),
      },
    });

    Configure per-agent (overrides Mastra):

    const durableAgent = createDurableAgent({
      agent,
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
    });

    Disable caching (streams won't be resumable):

    const durableAgent = createDurableAgent({ agent, cache: false });

    For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests.

    Class Hierarchy
    • DurableAgent extends Agent - base class with resumable streams
    • EventedAgent extends DurableAgent - fire-and-forget execution
    • InngestAgent extends DurableAgent - Inngest-powered execution
@mastra/server@1.30.0
Minor Changes
  • Update peer dependencies to match core package version bump (1.0.5) (#12557)
Patch Changes
  • Fixed a regression in 1.29.0 where configuring an agent with channel adapters (e.g. channels.adapters.slack) caused server startup to crash with a "Custom API route ... must not start with /api" error. The custom-route prefix validation now skips framework-generated webhook routes. (#15952)

  • Fix MCP client support in the agent editor: (#15945)

    • MCP client form dirty state: Save button now enables after adding/removing MCP clients
    • MCP tool name matching: Both bare and namespaced tool names are matched correctly
    • Auth token forwarding: Token from cookie or header is forwarded to auth-protected MCP servers
    • String interpolation: Request context variables in system prompts now resolve correctly
  • Add durable agents with resumable streams (#12557)

    Durable agents make agent execution resilient to disconnections, crashes, and long-running operations.

    The Problem

    Standard agent streaming has two fragility points:

    1. Connection drops - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed.
    2. Long-running operations - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost.
    The Solution

    Resumable streams solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive.

    Durable execution solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress.

    Usage

    Wrap any existing Agent with durability using factory functions:

    import { Agent } from '@mastra/core/agent';
    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const agent = new Agent({
      id: 'my-agent',
      model: openai('gpt-4'),
      instructions: 'You are helpful',
    });
    
    const durableAgent = createDurableAgent({ agent });

    Factory functions for different execution strategies:

    FactoryExecutionUse Case
    createDurableAgent({ agent })Local, synchronousDevelopment, simple deployments
    createEventedAgent({ agent })Fire-and-forget via workflow engineLong-running operations
    createInngestAgent({ agent, inngest })Inngest-poweredProduction, distributed systems
    Resumable Streams
    // Start streaming
    const { runId, output } = await durableAgent.stream('Analyze this data...');
    
    // Client disconnects at event 5...
    
    // Reconnect and resume from where we left off
    const { output: resumed } = await durableAgent.observe(runId, { offset: 6 });
    // Receives events 6, 7, 8... from cache, then continues with live events
    PubSub and Cache

    Durable agents use two infrastructure components:

    ComponentPurposeDefault
    PubSubReal-time event delivery during streamingEventEmitterPubSub
    CacheStores events for replay on reconnectionInMemoryServerCache

    When stream() is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When observe() is called, missed events replay from cache before continuing with live events.

    Configure via Mastra instance (recommended):

    const mastra = new Mastra({
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
      agents: {
        // Inherits cache and pubsub from Mastra
        myAgent: createDurableAgent({ agent }),
      },
    });

    Configure per-agent (overrides Mastra):

    const durableAgent = createDurableAgent({
      agent,
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
    });

    Disable caching (streams won't be resumable):

    const durableAgent = createDurableAgent({ agent, cache: false });

    For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests.

    Class Hierarchy
    • DurableAgent extends Agent - base class with resumable streams
    • EventedAgent extends DurableAgent - fire-and-forget execution
    • InngestAgent extends DurableAgent - Inngest-powered execution
  • Fix A2A streaming to emit incremental artifact updates from the agent full stream while preserving final structured output artifacts. (#15941)

@mastra/upstash@1.1.0
Minor Changes
  • Update peer dependencies to match core package version bump (1.0.5) (#12557)
Patch Changes
  • Add durable agents with resumable streams (#12557)

    Durable agents make agent execution resilient to disconnections, crashes, and long-running operations.

    The Problem

    Standard agent streaming has two fragility points:

    1. Connection drops - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed.
    2. Long-running operations - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost.
    The Solution

    Resumable streams solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive.

    Durable execution solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress.

    Usage

    Wrap any existing Agent with durability using factory functions:

    import { Agent } from '@mastra/core/agent';
    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const agent = new Agent({
      id: 'my-agent',
      model: openai('gpt-4'),
      instructions: 'You are helpful',
    });
    
    const durableAgent = createDurableAgent({ agent });

    Factory functions for different execution strategies:

    FactoryExecutionUse Case
    createDurableAgent({ agent })Local, synchronousDevelopment, simple deployments
    createEventedAgent({ agent })Fire-and-forget via workflow engineLong-running operations
    createInngestAgent({ agent, inngest })Inngest-poweredProduction, distributed systems
    Resumable Streams
    // Start streaming
    const { runId, output } = await durableAgent.stream('Analyze this data...');
    
    // Client disconnects at event 5...
    
    // Reconnect and resume from where we left off
    const { output: resumed } = await durableAgent.observe(runId, { offset: 6 });
    // Receives events 6, 7, 8... from cache, then continues with live events
    PubSub and Cache

    Durable agents use two infrastructure components:

    ComponentPurposeDefault
    PubSubReal-time event delivery during streamingEventEmitterPubSub
    CacheStores events for replay on reconnectionInMemoryServerCache

    When stream() is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When observe() is called, missed events replay from cache before continuing with live events.

    Configure via Mastra instance (recommended):

    const mastra = new Mastra({
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
      agents: {
        // Inherits cache and pubsub from Mastra
        myAgent: createDurableAgent({ agent }),
      },
    });

    Configure per-agent (overrides Mastra):

    const durableAgent = createDurableAgent({
      agent,
      cache: new RedisServerCache({ url: 'redis://...' }),
      pubsub: new RedisPubSub({ url: 'redis://...' }),
    });

    Disable caching (streams won't be resumable):

    const durableAgent = createDurableAgent({ agent, cache: false });

    For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests.

    Class Hierarchy
    • DurableAgent extends Agent - base class with resumable streams
    • EventedAgent extends DurableAgent - fire-and-forget execution
    • InngestAgent extends DurableAgent - Inngest-powered execution
Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.27.0

Highlights

CLI-Driven Browser Automation + Screencasts

@mastra/core and the new @mastra/browser-viewer package add end-to-end browser automation for CLI-based agent workflows. BrowserViewer launches Chrome via Playwright with remote debugging, and a new BrowserCliHandler automatically detects browser CLIs (agent-browser, browser-use, browse) and injects the CDP URL into commands — no manual wiring needed. Browser sessions are thread-isolated with automatic lifecycle management, and live screencasts stream directly to Studio. External CDP endpoints (e.g. browser-use cloud) are also supported: the system detects them, skips injection, and connects for screencast.

S3 “Prefix Mounts” Across Workspace Providers

Workspace packages (@mastra/s3, @mastra/daytona, @mastra/e2b, and @mastra/blaxel) now support mounting an S3 subdirectory via a prefix option, so sandboxes can expose only a folder within a bucket instead of the entire bucket.

More Robust Workflow Resumes for Parallel foreach

Fixes a workflow snapshot/resume bug where parallel foreach iterations could lose their suspendPayload when a sibling iteration resumed—important for HITL/tool-approval flows that rely on preserved per-iteration stream state.

Observational Memory Improvements (Temporal Markers + Correctness Fixes)

Adds opt-in temporal-gap markers (observationalMemory.temporalMarkers: true) to inject persisted <system-reminder type="temporal-gap"> when users return after 10+ minutes, and fixes duplication/conflicts by force-disabling savePerStep when observational memory is enabled.

New Package: @mastra/tavily

New integration package wrapping @tavily/core as first-class Mastra tools — createTavilySearchTool, createTavilyExtractTool, createTavilyCrawlTool, and createTavilyMapTool — with full Zod input/output schemas, lazy client initialization, and a convenience createTavilyTools() that returns all four with shared config. API key resolves from config or TAVILY_API_KEY env var.

Breaking Changes
  • None noted in this changelog.

Changelog

@mastra/core@1.27.0
Minor Changes
  • Added support for CLI-driven browser automation with screencast support in @mastra/core, including automatic CDP injection for browser CLIs. (#15415)

    Fixed local process spawning so workspace-relative cwd values no longer get duplicated.

Patch Changes
  • Update provider registry and model documentation with latest models and providers (f112db1)

  • Fixed foreach parallel iterations losing their suspendPayload when a sibling iteration was resumed. Previously, every result entry written back to the workflow snapshot had its suspendPayload cleared, so iterations that were still suspended (e.g. parallel tool-call approvals each carrying an agent's __streamState) lost the context they needed to resume correctly. Suspended iterations now retain their suspendPayload across resume cycles; completed iterations still have it cleared to keep snapshots small. (#15551)

    const approvalWorkflow = createWorkflow({ id: "approve" }).foreach(approveToolStep, { concurrency: 5 }).commit();
    
    // Before: resuming the first approval wiped streamState on the others,
    //         so subsequent resumes lost conversation context.
    // After:  each suspended iteration keeps its suspendPayload (including
    //         streamState) until it is individually resumed.
  • 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. (#15652)

  • Added opt-in temporal-gap markers for observational memory. When enabled via observationalMemory.temporalMarkers: true, the agent receives a <system-reminder type="temporal-gap"> before any user message that arrives more than 10 minutes after the previous one, so it can anchor responses in real elapsed time. Markers are persisted, surfaced to the observer, and rendered by the MastraCode TUI on reload. (#15605)

@mastra/agent-builder@1.0.28
Patch Changes
  • Hide internal log during mastra dev startup (that was previously already hidden but got exposed again by a recent change) (#15616)
@mastra/blaxel@0.3.0
Minor Changes
  • Added S3 prefix (subdirectory) mount support. You can now mount a specific folder within an S3 bucket instead of the entire bucket by setting the prefix option on your S3 filesystem. (#15171)

    Example:

    const fs = new S3Filesystem({
      bucket: "my-bucket",
      region: "us-east-1",
      prefix: "workspace/data",
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
    });

    When mounted in a sandbox, only the contents under workspace/data/ in the bucket will be visible at the mount path. This uses the s3fs bucket:/path syntax under the hood.

    Closes #15147.

Patch Changes
@mastra/browser-viewer@0.1.0
Minor Changes
  • Initial release of @mastra/browser-viewer (#15415)

    Playwright-based browser viewer for CLI providers that enables screencast visualization in Studio. Supports thread-isolated browser sessions and automatic CDP connection management.

    import { BrowserViewer } from "@mastra/browser-viewer";
    
    const workspace = new Workspace({
      sandbox: new LocalSandbox({ cwd: "./workspace" }),
      browser: new BrowserViewer({
        cli: "agent-browser",
        headless: false
      })
    });
Patch Changes
@mastra/clickhouse@1.5.1
Patch Changes
  • Fixed mastra dev repeatedly reporting MIGRATION REQUIRED on ClickHouse Cloud after mastra migrate had already run successfully. The observability migration check now recognizes the engine-name variants that ClickHouse Cloud and replicated clusters use in place of ReplacingMergeTree. (#15623)

  • Improved ClickHouse v-next observability initialization errors to include the underlying ClickHouse message in the standard error text. This makes init failures actionable in loggers that only print error.message. (#15588)

@mastra/daytona@0.3.0
Minor Changes
  • Added S3 prefix (subdirectory) mount support. You can now mount a specific folder within an S3 bucket instead of the entire bucket by setting the prefix option on your S3 filesystem. (#15171)

    Example:

    const fs = new S3Filesystem({
      bucket: "my-bucket",
      region: "us-east-1",
      prefix: "workspace/data",
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
    });

    When mounted in a sandbox, only the contents under workspace/data/ in the bucket will be visible at the mount path. This uses the s3fs bucket:/path syntax under the hood.

    Closes #15147.

Patch Changes
@mastra/e2b@0.2.0
Minor Changes
  • Added S3 prefix (subdirectory) mount support. You can now mount a specific folder within an S3 bucket instead of the entire bucket by setting the prefix option on your S3 filesystem. (#15171)

    Example:

    const fs = new S3Filesystem({
      bucket: "my-bucket",
      region: "us-east-1",
      prefix: "workspace/data",
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
    });

    When mounted in a sandbox, only the contents under workspace/data/ in the bucket will be visible at the mount path. This uses the s3fs bucket:/path syntax under the hood.

    Closes #15147.

Patch Changes
@mastra/fastify@1.3.11
Patch Changes
  • Fix custom route handlers on the Fastify adapter silently overwriting request-body fields named tools (e.g. POST /stored/agents, POST /stored/workspaces). The adapter now exposes registered tools as registeredTools in handler params, matching the Express and Hono adapters and the @mastra/server handler contract. (#15635)
@mastra/memory@1.17.0
Minor Changes
  • Added opt-in temporal-gap markers for observational memory. When enabled via observationalMemory.temporalMarkers: true, the agent receives a <system-reminder type="temporal-gap"> before any user message that arrives more than 10 minutes after the previous one, so it can anchor responses in real elapsed time. Markers are persisted, surfaced to the observer, and rendered by the MastraCode TUI on reload. (#15605)
Patch Changes
  • Fixed observer agent truncation that could cut UTF-16 surrogate pairs in half when formatting messages, tool results, or observation lines with emoji or other astral-plane characters. This produced lone surrogates that strict JSON parsers (including Anthropic's) reject with errors like no low surrogate in string, causing observer runs to fail. (#15634)
@mastra/s3@0.4.0
Minor Changes
  • Added S3 prefix (subdirectory) mount support. You can now mount a specific folder within an S3 bucket instead of the entire bucket by setting the prefix option on your S3 filesystem. (#15171)

    Example:

    const fs = new S3Filesystem({
      bucket: "my-bucket",
      region: "us-east-1",
      prefix: "workspace/data",
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
    });

    When mounted in a sandbox, only the contents under workspace/data/ in the bucket will be visible at the mount path. This uses the s3fs bucket:/path syntax under the hood.

    Closes #15147.

Patch Changes
@mastra/server@1.27.0
Patch Changes
  • Forward requestContext from the /approve-tool-call, /decline-tool-call, /approve-tool-call-generate and /decline-tool-call-generate REST handlers to agent.approveToolCall(...) / declineToolCall(...) / approveToolCallGenerate(...) / declineToolCallGenerate(...). (#15620)

    Previously requestContext was destructured from the handler arguments but never passed through. On resume, dynamicInstructions ran with requestContext: undefined, so any value placed on the per-request RequestContext by upstream middleware (or by body.requestContext auto-merge) was lost for the rest of the turn. Agents whose prompt assembly depends on request-scoped data (e.g. read-only state from the frontend) produced blank or placeholder responses after the user approved a HITL tool call. Other agent entry points (stream, generate) already forwarded requestContext correctly; this brings the approval routes in line.

  • Fixed screencast panel staying "Live" after browser closes due to an error. The ViewerRegistry now broadcasts browser_closed status when a screencast stream emits an error, not just when it stops cleanly. (#15415)

@mastra/tavily@1.0.1
Patch Changes
  • Fixed runtime ERR_MODULE_NOT_FOUND for @tavily/core by making it a direct dependency. Consumers no longer need to install @tavily/core manually. (#15628)
Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.24.0

Highlights

End-to-end RAG Tracing + New RAG/Graph Span Types

RAG ingestion and query operations are now visible in Mastra tracing with new span types (e.g., RAG_INGESTION, RAG_EMBEDDING, RAG_VECTOR_OPERATION, RAG_ACTION, GRAPH_ACTION) plus helpers like startRagIngestion() / withRagIngestion(). Instrumentation is opt-in via an observabilityContext, and @mastra/rag automatically threads context from agent TOOL_CALL spans into vector and graph tools.

CloudExporter Now Ships All Observability Signals (Not Just Traces)

@mastra/observability CloudExporter can now batch and upload logs, metrics, scores, and feedback in addition to tracing spans, enabling a single exporter path to Mastra Cloud for all signals. This also changes the endpoint configuration to use a base collector URL and derive publish paths automatically.

Span Filtering to Reduce Observability Noise and Cost

excludeSpanTypes and spanFilter were added to ObservabilityInstanceConfig in both @mastra/core and @mastra/observability, allowing you to drop entire span categories (e.g., MODEL_CHUNK) or apply predicate-based filtering before export—useful for pay-per-span backends.

AI SDK v6 Message Support + Message Interop Helpers

@mastra/core MessageList can now accept AI SDK v6 UI/model messages and project stored messages via messageList.get.all.aiV6.ui(), supporting v6 approval request/response flows. @mastra/ai-sdk adds toAISdkMessages() to load stored Mastra messages into AI SDK v5 or v6 chat UIs.

Better Reliability and Debuggability Across Logs + MCP + Memory

Observability log correlation is fixed so logs inside agent runs carry the active span correlation fields (restoring trace↔log linking), and deepClean() now applies to all signals and better preserves Map/Set/Error detail. MCP tool discovery now retries after reconnectable errors and the MCP server returns spec-correct 404s for stale sessions; memory recall gains more precise browsing (partType, toolName, threadId: "current", anchor paging), and message parts now include createdAt timestamps for accurate part-level timing.

Breaking Changes
  • CloudExporter endpoint format changed: configure a base endpoint URL (publisher paths are derived automatically) when using CloudExporter for Mastra Cloud uploads.

Changelog

@mastra/core@1.24.0
Minor Changes
  • Added excludeSpanTypes and spanFilter options to ObservabilityInstanceConfig for selectively filtering spans before export. Use excludeSpanTypes to drop entire categories of spans by type (e.g., MODEL_CHUNK, MODEL_STEP) or spanFilter for fine-grained predicate-based filtering by attributes, metadata, entity, or any combination. Both options help reduce noise and costs in observability platforms that charge per-span. (#15131)

    excludeSpanTypes example:

    excludeSpanTypes: [SpanType.MODEL_CHUNK, SpanType.MODEL_STEP, SpanType.WORKFLOW_SLEEP];

    spanFilter example:

    spanFilter: span => {
      if (span.type === SpanType.MODEL_CHUNK) return false;
      if (span.type === SpanType.TOOL_CALL && span.attributes?.success) return false;
      return true;
    };

    Resolves https://github.com/mastra-ai/mastra/issues/12710

  • Add RAG observability (#10898) (#15137)

    Surfaces RAG ingestion and query operations in Mastra's AI tracing.

    New span types in @mastra/core/observability:

    • RAG_INGESTION (root) — wraps an ingestion pipeline run
    • RAG_EMBEDDING — embedding call (used by ingestion and query)
    • RAG_VECTOR_OPERATION — vector store I/O (query/upsert/delete/fetch)
    • RAG_ACTIONchunk / extract_metadata / rerank
    • GRAPH_ACTION — non-RAG graph build / traverse / update / prune

    New helpers exported from @mastra/core/observability:

    • startRagIngestion(opts) — manual: returns { span, observabilityContext }
    • withRagIngestion(opts, fn) — scoped: runs fn(observabilityContext), attaches the return value as the span's output, routes thrown errors to span.error(...)

    Wired in @mastra/rag:

    • vectorQuerySearch emits RAG_EMBEDDING (mode: query) and RAG_VECTOR_OPERATION (operation: query)
    • rerank / rerankWithScorer emit RAG_ACTION (action: rerank)
    • MDocument.chunk emits RAG_ACTION (action: chunk) and RAG_ACTION (action: extract_metadata)
    • createGraphRAGTool emits GRAPH_ACTION (action: build / traverse)
    • createVectorQueryTool and createGraphRAGTool thread observabilityContext from the agent's TOOL_CALL span automatically

    All new instrumentation is opt-in: functions accept an optional observabilityContext and no-op when absent, so existing callers are unaffected.

Patch Changes
  • Update provider registry and model documentation with latest models and providers (8db7663)

  • Added AI SDK v6 UI message support to MessageList in @mastra/core. (#14592)

    MessageList can now accept AI SDK v6 UI and model messages in add(...), and project stored messages with messageList.get.all.aiV6.ui(). This adds first-class handling for v6 approval request and response message flows.

  • Fix observability log correlation: logs emitted from inside an agent run were being persisted with entityId, runId, traceId, and the other correlation fields set to null, breaking trace ↔ log linking in Mastra Studio and downstream observability tools. Logs now correctly carry the active span's correlation context end to end. (#15148)

  • Added createdAt timestamps to message parts in message history. (#15121)

    Message parts now keep their own creation timestamps so downstream code can preserve part-level timing instead of relying only on the parent message timestamp.

    After:

    { type: 'text', text: 'hello', createdAt: 1712534400000 }
@mastra/ai-sdk@1.3.3
Patch Changes
  • Added toAISdkMessages() for loading stored Mastra messages into AI SDK v5 or v6 chat UIs. (#14592)

    Use the default v5 behavior or pass { version: 'v6' } when your app is typed against AI SDK v6 useChat() message types.

@mastra/client-js@1.13.2
Patch Changes
  • Fixed the client JS Responses types to allow omitting the model override. (#15140)
@mastra/datadog@1.0.15
Patch Changes
  • Fix Datadog LLM Observability span kinds for model spans so traces match Datadog's expected shape. (#15149)
    • Each call to a model now shows up as an llm span in Datadog (previously the per-call spans were reported as task, so Datadog's "Model Calls" count was wrong and per-call inputs/outputs were not rendered as messages).
    • The wrapper around a generation is now reported as a workflow span instead of llm, so it no longer looks like an extra LLM call.
    • Token usage and cost are reported only on the per-call llm spans, so Datadog no longer double-counts tokens against the wrapper.
    • Per-call llm spans inherit modelName and modelProvider from their parent generation, so the model is still attached in the Datadog UI.
@mastra/loggers@1.1.1
Patch Changes
  • Removed 'component' field from pino-pretty log output to reduce noise in CLI logs (#15146)
@mastra/mcp@1.4.2
Patch Changes
  • Improved MCP tool discovery to retry once after reconnectable connection errors like Connection closed during tools/list. (#15141)

    MCPClient.listToolsets(), listToolsetsWithErrors(), and listTools() now attempt a reconnect before treating transient discovery failures as missing tools.

  • Fixed MCP server to return HTTP 404 (instead of 400) when a client sends a stale or unknown session ID. Per the MCP spec, this tells clients to re-initialize with a new session, which fixes broken tool calls after server redeploys. (#15160)

@mastra/memory@1.15.0
Minor Changes
  • Updated the recall tool to support more precise message browsing for agents. (#15116)

    Agents using recall can now pass partType and toolName to narrow message results to specific parts, such as tool calls or tool results for one tool. This change also adds threadId: "current" support across recall modes and anchor: "start" | "end" for no-cursor message paging, making it easier to inspect recent thread activity and past tool usage.

Patch Changes
  • Fixed reflection threshold not respecting per-record overrides set via the PATCH API. Previously, lowering the reflection threshold for a specific record had no effect on the actual reflection trigger — only the default 40k threshold was used. Now per-record overrides are correctly applied in both sync and async reflection paths. (#15170)

  • Improved observational memory formatting to use part timestamps when rendering dates and times. (#15121)

    Observer history now follows part-level timing more closely, so the rendered memory context is more accurate when messages contain parts created at different times.

  • Fixed message history doubling when using Observational Memory with the Mastra gateway. The local ObservationalMemoryProcessor now detects when the agent's model is routed through the Mastra gateway and skips its input/output processing, since the gateway handles OM server-side. (#15161)

@mastra/observability@1.8.0
Minor Changes
  • Added CloudExporter support for Mastra Observability logs, metrics, scores, and feedback. (#15124)

    CloudExporter now batches and uploads all Mastra Observability signals to Mastra Cloud, not just tracing spans.

    This includes a breaking change to the CloudExporter endpoint format. We now pass a base endpoint URL and let let the exporter derive the standard publish paths automatically.

    import { CloudExporter, Observability } from '@mastra/observability';
    
    const observability = new Observability({
      configs: {
        default: {
          serviceName: 'my-app',
          exporters: [
            new CloudExporter({
              endpoint: 'https://collector.example.com',
            }),
          ],
        },
      },
    });
    
    // Traces, logs, metrics, scores, and feedback now all publish through CloudExporter.

    After updating the exporter endpoint config, the exporter will continue to work for traces, and the same exporter will now also publish structured logs, auto-extracted metrics, scores, and feedback records.

  • Added excludeSpanTypes and spanFilter options to ObservabilityInstanceConfig for selectively filtering spans before export. Use excludeSpanTypes to drop entire categories of spans by type (e.g., MODEL_CHUNK, MODEL_STEP) or spanFilter for fine-grained predicate-based filtering by attributes, metadata, entity, or any combination. Both options help reduce noise and costs in observability platforms that charge per-span. (#15131)

    excludeSpanTypes example:

    excludeSpanTypes: [SpanType.MODEL_CHUNK, SpanType.MODEL_STEP, SpanType.WORKFLOW_SLEEP];

    spanFilter example:

    spanFilter: span => {
      if (span.type === SpanType.MODEL_CHUNK) return false;
      if (span.type === SpanType.TOOL_CALL && span.attributes?.success) return false;
      return true;
    };

    Resolves https://github.com/mastra-ai/mastra/issues/12710

Patch Changes
  • ObservabilityBus now honors per-instance serializationOptions (maxStringLength, maxDepth, maxArrayLength, maxObjectKeys) when deep-cleaning log/metric/score/feedback payloads, matching the behavior of tracing spans. Previously these signals always used the built-in defaults regardless of user configuration. (#15138)

  • Apply deepClean() to all observability signals (logs, metrics, scores, feedback) before fanning out to exporters and bridges. Previously only tracing spans were deep-cleaned at construction time, leaving free-form payload fields on other signals (e.g. log.data, log.metadata, metric.metadata, metric.costContext.costMetadata, score.metadata, feedback.metadata) susceptible to circular references, oversized strings, and other non-serializable values. Sanitization now happens centrally in ObservabilityBus.emit() so every signal leaving the bus is bounded and JSON-safe. (#15135)

  • deepClean() now preserves data for Map, Set, and richer Error objects. Previously Maps and Sets were serialized as empty {} (entries silently dropped) and Errors only kept name/message. Maps are now converted to plain objects of entries, Sets to arrays (both respecting maxObjectKeys/maxArrayLength and cycle detection), and Errors additionally preserve stack and recursively cleaned cause. (#15136)

@mastra/playground-ui@22.1.0
Minor Changes
  • Search input can now be collapsed into a compact icon button with tooltip and auto-focuses when expanded (#15130)

  • Added DataKeysAndValues component — a compound component for displaying key-value pairs in a grid layout with support for single or two-column modes and section headers (#15126)

  • Added DateTimeRangePicker component — a date range selector with preset options (last 24h, 7d, 30d, etc.) and a custom range mode with dual calendar and time pickers (#15128)

  • Added DataCodeSection component — a read-only code viewer with JSON syntax highlighting, search, multiline toggle, and an expandable fullscreen dialog (#15125)

  • Added DataPanel compound component — a container for detail panels with header, navigation, close button, loading, and empty states (#15127)

  • New inline Traces page replacing the old dialog-based Observability page. Trace, span, and score details now open in stacked side panels instead of full-screen dialogs. URL deep-linking supports traceId, spanId, tab, and scoreId params. Includes new TracesDataList, DataList.Pagination, DataList.Subheader components, and Evaluate Trace / Save as Dataset Item actions. (#15139)

Patch Changes
  • Fixed publishing older agent versions (#15154)

    Fixed agent editor to allow publishing older read-only versions. Previously, the Publish button was disabled when viewing a previous version. Now a "Publish This Version" button appears, enabling users to set any older version as the published version.

    Fixed Publish button being clickable without a saved draft

    The Publish button is now disabled until a draft version is saved. Previously, making edits would enable the Publish button even without a saved draft, which caused an error when clicked.

    Eliminated spurious 404 error logs for code-only agents

    The agent versions endpoint now checks both code-registered and stored agents before returning 404, and the frontend conditionally fetches stored agent details only when versions exist. This prevents noisy error logs when navigating to the editor for agents that haven't been published yet.

    Changed editor sections to be collapsed by default

    The System Prompt, Tools, and Variables sections in the agent editor are now collapsed by default when navigating to the editor page.

@mastra/rag@2.2.0
Minor Changes
  • Add RAG observability (#10898) (#15137)

    Surfaces RAG ingestion and query operations in Mastra's AI tracing.

    New span types in @mastra/core/observability:

    • RAG_INGESTION (root) — wraps an ingestion pipeline run
    • RAG_EMBEDDING — embedding call (used by ingestion and query)
    • RAG_VECTOR_OPERATION — vector store I/O (query/upsert/delete/fetch)
    • RAG_ACTIONchunk / extract_metadata / rerank
    • GRAPH_ACTION — non-RAG graph build / traverse / update / prune

    New helpers exported from @mastra/core/observability:

    • startRagIngestion(opts) — manual: returns { span, observabilityContext }
    • withRagIngestion(opts, fn) — scoped: runs fn(observabilityContext), attaches the return value as the span's output, routes thrown errors to span.error(...)

    Wired in @mastra/rag:

    • vectorQuerySearch emits RAG_EMBEDDING (mode: query) and RAG_VECTOR_OPERATION (operation: query)
    • rerank / rerankWithScorer emit RAG_ACTION (action: rerank)
    • MDocument.chunk emits RAG_ACTION (action: chunk) and RAG_ACTION (action: extract_metadata)
    • createGraphRAGTool emits GRAPH_ACTION (action: build / traverse)
    • createVectorQueryTool and createGraphRAGTool thread observabilityContext from the agent's TOOL_CALL span automatically

    All new instrumentation is opt-in: functions accept an optional observabilityContext and no-op when absent, so existing callers are unaffected.

Patch Changes
@mastra/server@1.24.0
Patch Changes
  • Fixed publishing older agent versions (#15154)

    Fixed agent editor to allow publishing older read-only versions. Previously, the Publish button was disabled when viewing a previous version. Now a "Publish This Version" button appears, enabling users to set any older version as the published version.

    Fixed Publish button being clickable without a saved draft

    The Publish button is now disabled until a draft version is saved. Previously, making edits would enable the Publish button even without a saved draft, which caused an error when clicked.

    Eliminated spurious 404 error logs for code-only agents

    The agent versions endpoint now checks both code-registered and stored agents before returning 404, and the frontend conditionally fetches stored agent details only when versions exist. This prevents noisy error logs when navigating to the editor for agents that haven't been published yet.

    Changed editor sections to be collapsed by default

    The System Prompt, Tools, and Variables sections in the agent editor are now collapsed by default when navigating to the editor page.

  • Fixed the Responses API to use the agent default model when create requests omit model. (#15140)

Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.16.0

Highlights

Smarter Model Selection for Observational Memory

@mastra/memory now lets you route observer and reflector calls to different models based on input size using ModelByInputTokens. Short inputs can go to a fast, cheap model while longer ones get sent to a more capable one -- all configured declaratively with token thresholds. Tracing shows which model was selected and why.

MongoDB Support for Datasets and Experiments

@mastra/mongodb now stores versioned datasets with full item history and time-travel queries, plus experiment results and CRUD. If you're already using MongoDBStore, this works automatically with no extra setup.

Okta Auth and RBAC

New @mastra/auth-okta package brings SSO authentication and role-based access control via Okta. Map Okta groups to Mastra permissions, verify JWTs against Okta's JWKS endpoint, and manage sessions -- or pair Okta RBAC with a different auth provider like Auth0 or Clerk.

Breaking Changes
  • None called out in this changelog.

Changelog

@mastra/core@1.16.0
Minor Changes
  • Added dataset-agent association and experiment status tracking for the Evaluate workflow. (#14470)

    • Dataset targeting: Added targetType and targetIds fields to datasets, enabling association with agents, scorers, or workflows. Datasets can now be linked to multiple entities.
    • Experiment status: Added status field to experiment results ('needs-review', 'reviewed', 'complete') for review queue workflow.
    • Dataset experiment routes: Added API endpoints for triggering experiments from a dataset with configurable target type and target ID.
    • LLM data generation: Added endpoint for generating dataset items using an LLM with configurable count and prompt.
    • Failure analysis: Added endpoint for clustering experiment failures and proposing tags using LLM analysis.
  • Added agent version support for experiments. When triggering an experiment, you can now pass an agentVersion parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. (#14562)

    const client = new MastraClient();
    
    await client.triggerDatasetExperiment({
      datasetId: "my-dataset",
      targetType: "agent",
      targetId: "my-agent",
      version: 3, // pin to dataset version 3
      agentVersion: "ver_abc123" // pin to a specific agent version
    });
  • Added tool suspension handling to the Harness. (#14611)

    When a tool calls suspend() during execution, the harness now emits a tool_suspended event, reports agent_end with reason 'suspended', and exposes respondToToolSuspension() to resume execution with user-provided data.

    harness.subscribe((event) => {
      if (event.type === "tool_suspended") {
        // event.toolName, event.suspendPayload, event.resumeSchema
      }
    });
    
    // Resume after collecting user input
    await harness.respondToToolSuspension({ resumeData: { confirmed: true } });
  • Added agentId to the agent tool execution context. Tools executed by an agent can now access context.agent.agentId to identify which agent is calling them. This enables tools to look up agent metadata, share workspace configuration with sub-agents, or customize behavior per agent. (#14502)

  • Improved observability metrics and logs storage support. (#14607)

    • Added typed observability storage fields for shared correlation context and cost data.
    • Added storage-layer metric listing and richer metric aggregations that can return estimated cost alongside values.
    • Improved observability filter parity across log and metric storage APIs.
  • Add optional ?path= query param to workspace skill routes for disambiguating same-named skills. (#14430)

    Skill routes continue to use :skillName in the URL path (no breaking change). When two skills share the same name (e.g. from different directories), pass the optional ?path= query parameter to select the exact skill:

    GET /workspaces/:workspaceId/skills/:skillName?path=skills/brand-guidelines

    SkillMetadata now includes a path field, and the list() method returns all same-named skills for disambiguation. The client SDK's getSkill() accepts an optional skillPath parameter for disambiguation.

  • Added ModelByInputTokens in @mastra/memory for token-threshold-based model selection in Observational Memory. (#14614)

    When configured, OM automatically selects different observer or reflector models based on the actual input token count at the time the OM call runs.

    Example usage:

    import { Memory, ModelByInputTokens } from "@mastra/memory";
    
    const memory = new Memory({
      options: {
        observationalMemory: {
          model: new ModelByInputTokens({
            upTo: {
              10_000: "google/gemini-2.5-flash",
              40_000: "openai/gpt-4o",
              1_000_000: "openai/gpt-4.5"
            }
          })
        }
      }
    });

    The upTo keys are inclusive upper bounds. OM resolves the matching tier directly at the observer or reflector call site. If the input exceeds the largest configured threshold, OM throws an error.

    Improved Observational Memory tracing so traces show the observer and reflector spans and make it easier to see which resolved model was used at runtime.

Patch Changes
  • Update provider registry and model documentation with latest models and providers (68ed4e9)

  • Fixed Harness.destroy() to properly clean up heartbeats and workspace on teardown. (#14568)

  • Fixed null detection in tool input validation to check actual values at failing paths instead of relying on error message string matching. This ensures null values from LLMs are correctly handled even when validators produce error messages that don't contain the word "null" (e.g., "must be string"). Fixes #14476. (#14496)

  • Fixed missing tool lists in agent traces for streaming runs. Exporters like Datadog LLM Observability now receive the tools available to the agent. (#14550)

  • Fix consecutive tool-only loop iterations being merged into a single assistant message block. When the agentic loop runs multiple iterations that each produce only tool calls, the LLM would misinterpret them as parallel calls from a single turn. A step-start boundary is now inserted between iterations to ensure they are treated as sequential steps. (#14652)

  • Improved custom OpenAI-compatible model configuration guidance in the models docs. (#14594)

  • Added client/server body schemas for feedback and scores that omit the timestamp field, allowing it to be set server-side (#14470)

  • Workspace skills now surface all same-named skills for disambiguation. (#14430)

    When multiple skills share the same name (e.g., a local brand-guidelines skill and one from node_modules), list() now returns all of them instead of only the tie-break winner. This lets agents and UIs see every available skill, along with its path and source type.

    Tie-breaking behavior:

    • get(name) still returns a single skill using source-type priority: local > managed > external
    • If two skills share the same name and source type, get(name) throws an error — rename one or move it to a different source type
    • get(path) bypasses tie-breaking entirely and returns the exact skill

    Agents and UIs now receive all same-named skills with their paths, which improves disambiguation in prompts and tool calls.

    const skills = await workspace.skills.list();
    // Returns both local and external "brand-guidelines" skills
    
    const exact = await workspace.skills.get("node_modules/@myorg/skills/brand-guidelines");
    // Fetches the external copy directly by path
  • Fixed Anthropic 'tool_use ids were found without tool_result blocks immediately after' error. When client tools (e.g. execute_command) and provider tools (e.g. web_search) are called in parallel, the tool ordering in message history could cause Anthropic to reject subsequent requests, making the thread unrecoverable. Tool blocks are now correctly split to satisfy Anthropic's ordering requirements. (#14648)

  • Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. (#14464)

    Mastra agent and client APIs accept schemas from either zod/v3 or zod/v4, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions.

@mastra/ai-sdk@1.2.1
Patch Changes
  • Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. (#14464)

    Mastra agent and client APIs accept schemas from either zod/v3 or zod/v4, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions.

@mastra/auth-okta@0.0.2
Patch Changes
  • fix(auth-okta): harden security defaults and address code review feedback (#14553)
    • Fix cache poisoning: errors in fetchGroupsFromOkta now propagate so the outer .catch evicts the entry and retries on next request
    • Reduce cookie size: only store user claims, id_token (for logout), and expiry — access/refresh tokens are no longer stored, keeping cookies under the 4KB browser limit
    • Add id_token_hint to logout URL (required by Okta)
    • Add console.warn for auto-generated cookie password and in-memory state store in production
    • Document missing env vars (OKTA_CLIENT_SECRET, OKTA_REDIRECT_URI, OKTA_COOKIE_PASSWORD) in README and examples
    • Expand MastraAuthOktaOptions docs to include all fields (session config, scopes, etc.)
    • Fix test to actually exercise getUserId cross-provider lookup path
@mastra/client-js@1.10.0
Minor Changes
  • Added new observability API endpoints and client methods for logs, scores, feedback, metrics (aggregate, breakdown, time series, percentiles), and discovery (metric names, label keys/values, entity types/names, service names, environments, tags) (#14470)
Patch Changes
  • Added client SDK methods for dataset experiments and item generation. (#14470)

    • Added triggerExperiment() method to dataset resources for running experiments with configurable target type and ID
    • Added generateItems() method for LLM-powered test data generation
    • Added clusterFailures() method for analyzing experiment failures
    • Added TypeScript types for new dataset and experiment API payloads
  • Added agent version support for experiments. When triggering an experiment, you can now pass an agentVersion parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. (#14562)

    const client = new MastraClient();
    
    await client.triggerDatasetExperiment({
      datasetId: "my-dataset",
      targetType: "agent",
      targetId: "my-agent",
      version: 3, // pin to dataset version 3
      agentVersion: "ver_abc123" // pin to a specific agent version
    });
  • Add optional ?path= query param to workspace skill routes for disambiguating same-named skills. (#14430)

    Skill routes continue to use :skillName in the URL path (no breaking change). When two skills share the same name (e.g. from different directories), pass the optional ?path= query parameter to select the exact skill:

    GET /workspaces/:workspaceId/skills/:skillName?path=skills/brand-guidelines

    SkillMetadata now includes a path field, and the list() method returns all same-named skills for disambiguation. The client SDK's getSkill() accepts an optional skillPath parameter for disambiguation.

  • Updated skill search result types and query parameters to use skillName/skillNames instead of skillPath/skillPaths for consistency with the name-based public API. (#14430)

  • Added storage type detection to the Metrics Dashboard. The /system/packages endpoint now returns observabilityStorageType, identifying the observability storage backend. The dashboard shows an empty state when the storage does not support metrics (e.g. PostgreSQL, LibSQL), and displays a warning when using in-memory storage since metrics are not persisted across server restarts. Also added a docs link button to the Metrics page header. (#14620)

    import { MastraClient } from "@mastra/client-js";
    
    const client = new MastraClient();
    const system = await client.getSystemPackages();
    
    // system.observabilityStorageType contains the class name of the observability store:
    // - 'ObservabilityInMemory' → metrics work but are not persisted across restarts
    // - 'ObservabilityPG', 'ObservabilityLibSQL', etc. → metrics not supported
    
    if (system.observabilityStorageType === "ObservabilityInMemory") {
      console.warn("Metrics are not persisted — data will be lost on server restart.");
    }
    
    const SUPPORTED = new Set(["ObservabilityInMemory"]);
    if (!SUPPORTED.has(system.observabilityStorageType ?? "")) {
      console.error("Metrics require in-memory observability storage.");
    }
  • Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. (#14464)

    Mastra agent and client APIs accept schemas from either zod/v3 or zod/v4, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions.

@mastra/datadog@1.0.9
Patch Changes
  • Fixed error info tags being recorded as [object Object] in Datadog. Error details (message, id, domain, category) are now stored as separate flattened tags (error.message, error.id, error.domain, error.category) instead of a nested object, making error information properly visible in Datadog LLM Observability. (#14570)
@mastra/deployer@1.16.0
Patch Changes
  • Inject MASTRA_EXPERIMENTAL_UI environment variable into the studio HTML shell during build and deploy. (#14547)
@mastra/deployer-vercel@1.1.8
Patch Changes
  • Inject MASTRA_EXPERIMENTAL_UI environment variable into the studio HTML shell during build and deploy. (#14547)
@mastra/express@1.3.0
Minor Changes
  • Added adapter auth middleware helpers for raw framework routes. (#14458)

    Use createAuthMiddleware({ mastra }) when you mount routes directly on a Hono, Express, Fastify, or Koa app and still want Mastra auth to run. Set requiresAuth: false when you need to reuse the same helper chain on a public route.

    app.get("/custom/protected", createAuthMiddleware({ mastra }), handler);
Patch Changes
@mastra/fastify@1.3.0
Minor Changes
  • Added adapter auth middleware helpers for raw framework routes. (#14458)

    Use createAuthMiddleware({ mastra }) when you mount routes directly on a Hono, Express, Fastify, or Koa app and still want Mastra auth to run. Set requiresAuth: false when you need to reuse the same helper chain on a public route.

    app.get("/custom/protected", createAuthMiddleware({ mastra }), handler);
Patch Changes
@mastra/hono@1.3.0
Minor Changes
  • Added adapter auth middleware helpers for raw framework routes. (#14458)

    Use createAuthMiddleware({ mastra }) when you mount routes directly on a Hono, Express, Fastify, or Koa app and still want Mastra auth to run. Set requiresAuth: false when you need to reuse the same helper chain on a public route.

    app.get("/custom/protected", createAuthMiddleware({ mastra }), handler);
Patch Changes
@mastra/koa@1.4.0
Minor Changes
  • Added adapter auth middleware helpers for raw framework routes. (#14458)

    Use createAuthMiddleware({ mastra }) when you mount routes directly on a Hono, Express, Fastify, or Koa app and still want Mastra auth to run. Set requiresAuth: false when you need to reuse the same helper chain on a public route.

    app.get("/custom/protected", createAuthMiddleware({ mastra }), handler);
Patch Changes
@mastra/libsql@1.7.2
Patch Changes
  • Added storage support for dataset targeting and experiment status fields. (#14470)

    • Added targetType (text) and targetIds (jsonb) columns to datasets table for entity association
    • Added tags (jsonb) column to datasets table for tag vocabulary
    • Added status column to experiment results for review workflow tracking
    • Added migration logic to add new columns to existing tables
  • Added agent version support for experiments. When triggering an experiment, you can now pass an agentVersion parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. (#14562)

    const client = new MastraClient();
    
    await client.triggerDatasetExperiment({
      datasetId: "my-dataset",
      targetType: "agent",
      targetId: "my-agent",
      version: 3, // pin to dataset version 3
      agentVersion: "ver_abc123" // pin to a specific agent version
    });
@mastra/memory@1.10.0
Minor Changes
  • Added ModelByInputTokens in @mastra/memory for token-threshold-based model selection in Observational Memory. (#14614)

    When configured, OM automatically selects different observer or reflector models based on the actual input token count at the time the OM call runs.

    Example usage:

    import { Memory, ModelByInputTokens } from "@mastra/memory";
    
    const memory = new Memory({
      options: {
        observationalMemory: {
          model: new ModelByInputTokens({
            upTo: {
              10_000: "google/gemini-2.5-flash",
              40_000: "openai/gpt-4o",
              1_000_000: "openai/gpt-4.5"
            }
          })
        }
      }
    });

    The upTo keys are inclusive upper bounds. OM resolves the matching tier directly at the observer or reflector call site. If the input exceeds the largest configured threshold, OM throws an error.

    Improved Observational Memory tracing so traces show the observer and reflector spans and make it easier to see which resolved model was used at runtime.

Patch Changes
  • Fixed observational memory reflection compression for google/gemini-2.5-flash by using stronger compression guidance and starting it at a higher compression level during reflection. google/gemini-2.5-flash is unusually good at generating long, faithful outputs. That made reflection retries more likely to preserve too much detail and miss the compression target, wasting tokens in the process. (#14612)
@mastra/mongodb@1.6.0
Minor Changes
  • Added datasets and experiments storage support to the MongoDB store. (#14556)

    Datasets — Full dataset management with versioned items. Create, update, and delete datasets and their items with automatic version tracking. Supports batch insert/delete operations, time-travel queries to retrieve items at any past version, and item history tracking.

    Experiments — Run and track experiments against datasets. Full CRUD for experiments and per-item experiment results, with pagination, filtering, and cascade deletion.

    Both domains are automatically available when using MongoDBStore — no additional configuration needed.

    const store = new MongoDBStore({ uri: "mongodb://localhost:27017", dbName: "my-app" });
    
    // Datasets
    const dataset = await store.getStorage("datasets").createDataset({ name: "my-dataset" });
    await store.getStorage("datasets").addItem({ datasetId: dataset.id, input: { prompt: "hello" } });
    
    // Experiments
    const experiment = await store.getStorage("experiments").createExperiment({ name: "run-1", datasetId: dataset.id });
Patch Changes
@mastra/pg@1.8.3
Patch Changes
  • Added storage support for dataset targeting and experiment status fields. (#14470)

    • Added targetType (text) and targetIds (jsonb) columns to datasets table for entity association
    • Added tags (jsonb) column to datasets table for tag vocabulary
    • Added status column to experiment results for review workflow tracking
    • Added migration logic to add new columns to existing tables
  • Added agent version support for experiments. When triggering an experiment, you can now pass an agentVersion parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. (#14562)

    const client = new MastraClient();
    
    await client.triggerDatasetExperiment({
      datasetId: "my-dataset",
      targetType: "agent",
      targetId: "my-agent",
      version: 3, // pin to dataset version 3
      agentVersion: "ver_abc123" // pin to a specific agent version
    });
@mastra/playground-ui@19.0.0
Minor Changes
  • Added Evaluate tab to the agent playground with full dataset management, scorer editing, experiment execution, and review workflow. (#14470)

    Evaluate tab — A new sidebar-driven tab for managing datasets, scorers, and experiments within the agent playground. Key features:

    • Dataset management: Create, attach/detach, and browse datasets. Inline item editing and deletion. Background LLM-powered test data generation with review-before-add flow.
    • Scorer editor: Create and edit scorers with test items, linked test datasets, configurable score ranges, and model selection. Run scorer experiments directly from the editor.
    • Experiment runner: Trigger experiments from dataset or scorer views with correct target routing (agent, scorer, or workflow). Past runs displayed with pass/fail counts and auto-polling for status updates.
    • Collapsible sidebar sections: Datasets, Scorers, and Experiments sections are collapsible with search-enabled attach dialogs.

    Review tab — A dedicated review workflow for experiment results:

    • Tag-based organization with dataset-level tag vocabulary and bulk tagging
    • LLM-powered analysis ("Analyze untagged/selected") that proposes tags with reasons, using existing tags when applicable
    • Thumbs up/down ratings and comments persisted via feedback API
    • Mark items as complete for audit trail
    • Completed items section with read-only display
  • Added dataset and agent version selectors to the experiment evaluate tab. You can now choose which dataset version and agent version to use when running an experiment. Version information is displayed in the experiment sidebar, results panel header, and Past Runs list. Added a copy button next to the agent version selector to easily copy version IDs. (#14562)

Patch Changes
  • Added EntityList.NoMatch component that displays a message when search filtering returns no results. Applied to all entity list pages: Agents, Workflows, Tools, Scorers, Processors, Prompts, Datasets, and MCP Servers. (#14621)

  • Added agent version support for experiments. When triggering an experiment, you can now pass an agentVersion parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. (#14562)

    const client = new MastraClient();
    
    await client.triggerDatasetExperiment({
      datasetId: "my-dataset",
      targetType: "agent",
      targetId: "my-agent",
      version: 3, // pin to dataset version 3
      agentVersion: "ver_abc123" // pin to a specific agent version
    });
  • Removed 'Create an Agent' button from agent list page and table empty state. Removed 'Create Scorer' button from top-level scorers page. Removed stored/code source icons (AgentSourceIcon) from agent headers, combobox, and table. Renamed 'Versions' tab to 'Editor' in agent page tabs. Added GaugeIcon to the 'Create Scorer' button in the review tab. (#14555)

  • Added metrics dashboard with KPI cards, trace volume, latency, model usage, and scores visualizations. Includes filtering by date range, agents, models, and providers. Added HorizontalBars, MetricsCard, MetricsKpiCard, MetricsLineChart, MetricsFlexGrid, and MetricsDataTable design system components. (#14491)

  • Internal cleanup and linting fixes (#14497)

  • Add optional ?path= query param to workspace skill routes for disambiguating same-named skills. (#14430)

    Skill routes continue to use :skillName in the URL path (no breaking change). When two skills share the same name (e.g. from different directories), pass the optional ?path= query parameter to select the exact skill:

    GET /workspaces/:workspaceId/skills/:skillName?path=skills/brand-guidelines

    SkillMetadata now includes a path field, and the list() method returns all same-named skills for disambiguation. The client SDK's getSkill() accepts an optional skillPath parameter for disambiguation.

  • Updated skill search result types and query parameters to use skillName/skillNames instead of skillPath/skillPaths for consistency with the name-based public API. (#14430)

  • Added experimental entity list components with skeleton loading states, error handling, and dedicated empty state components for all list pages. Gated behind MASTRA_EXPERIMENTAL_UI environment variable. (#14547)

  • Added storage type detection to the Metrics Dashboard. The /system/packages endpoint now returns observabilityStorageType, identifying the observability storage backend. The dashboard shows an empty state when the storage does not support metrics (e.g. PostgreSQL, LibSQL), and displays a warning when using in-memory storage since metrics are not persisted across server restarts. Also added a docs link button to the Metrics page header. (#14620)

    import { MastraClient } from "@mastra/client-js";
    
    const client = new MastraClient();
    const system = await client.getSystemPackages();
    
    // system.observabilityStorageType contains the class name of the observability store:
    // - 'ObservabilityInMemory' → metrics work but are not persisted across restarts
    // - 'ObservabilityPG', 'ObservabilityLibSQL', etc. → metrics not supported
    
    if (system.observabilityStorageType === "ObservabilityInMemory") {
      console.warn("Metrics are not persisted — data will be lost on server restart.");
    }
    
    const SUPPORTED = new Set(["ObservabilityInMemory"]);
    if (!SUPPORTED.has(system.observabilityStorageType ?? "")) {
      console.error("Metrics require in-memory observability storage.");
    }
  • Added ModelByInputTokens in @mastra/memory for token-threshold-based model selection in Observational Memory. (#14614)

    When configured, OM automatically selects different observer or reflector models based on the actual input token count at the time the OM call runs.

    Example usage:

    import { Memory, ModelByInputTokens } from "@mastra/memory";
    
    const memory = new Memory({
      options: {
        observationalMemory: {
          model: new ModelByInputTokens({
            upTo: {
              10_000: "google/gemini-2.5-flash",
              40_000: "openai/gpt-4o",
              1_000_000: "openai/gpt-4.5"
            }
          })
        }
      }
    });

    The upTo keys are inclusive upper bounds. OM resolves the matching tier directly at the observer or reflector call site. If the input exceeds the largest configured threshold, OM throws an error.

    Improved Observational Memory tracing so traces show the observer and reflector spans and make it easier to see which resolved model was used at runtime.

  • Redesigned the agent instruction blocks editor with a Notion-like document feel. Blocks no longer show line numbers or block numbers, have tighter spacing, and display a subtle hover highlight. Reference blocks now show a sync-block header with a popover for block details, "Open original", "De-reference block", and "Used by" agents. Inline blocks can be converted to saved prompt blocks via a new "Save as prompt block" action in the hover toolbar. The prompt block edit sidebar now shows a "Used by" section listing which agents reference the block. Added a lineNumbers prop to CodeEditor to optionally hide line numbers. (#14563)

    <CodeEditor language="markdown" lineNumbers={false} />
@mastra/react@0.2.17
Patch Changes
  • Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. (#14464)

    Mastra agent and client APIs accept schemas from either zod/v3 or zod/v4, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions.

@mastra/schema-compat@1.2.7
Patch Changes
  • Fixed schema-compat ESM imports for Zod JSON Schema helpers. (#14617)

    @mastra/schema-compat no longer uses createRequire in its Zod v4 adapter or runtime eval tests, which avoids createRequire-related ESM issues while preserving support for zod/v3 and zod/v4.

  • Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. (#14464)

    Mastra agent and client APIs accept schemas from either zod/v3 or zod/v4, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions.

@mastra/server@1.16.0
Minor Changes
  • Added dataset-agent association and experiment status tracking for the Evaluate workflow. (#14470)

    • Dataset targeting: Added targetType and targetIds fields to datasets, enabling association with agents, scorers, or workflows. Datasets can now be linked to multiple entities.
    • Experiment status: Added status field to experiment results ('needs-review', 'reviewed', 'complete') for review queue workflow.
    • Dataset experiment routes: Added API endpoints for triggering experiments from a dataset with configurable target type and target ID.
    • LLM data generation: Added endpoint for generating dataset items using an LLM with configurable count and prompt.
    • Failure analysis: Added endpoint for clustering experiment failures and proposing tags using LLM analysis.
  • Added getAuthenticatedUser() to @mastra/server/auth so server middleware can resolve the configured auth user without changing route auth behavior. (#14458)

    Example

    import { getAuthenticatedUser } from "@mastra/server/auth";
    
    const user = await getAuthenticatedUser({
      mastra,
      token,
      request: c.req.raw
    });
  • Added new observability API endpoints and client methods for logs, scores, feedback, metrics (aggregate, breakdown, time series, percentiles), and discovery (metric names, label keys/values, entity types/names, service names, environments, tags) (#14470)

Patch Changes
  • Added agent version support for experiments. When triggering an experiment, you can now pass an agentVersion parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. (#14562)

    const client = new MastraClient();
    
    await client.triggerDatasetExperiment({
      datasetId: "my-dataset",
      targetType: "agent",
      targetId: "my-agent",
      version: 3, // pin to dataset version 3
      agentVersion: "ver_abc123" // pin to a specific agent version
    });
  • Add optional ?path= query param to workspace skill routes for disambiguating same-named skills. (#14430)

    Skill routes continue to use :skillName in the URL path (no breaking change). When two skills share the same name (e.g. from different directories), pass the optional ?path= query parameter to select the exact skill:

    GET /workspaces/:workspaceId/skills/:skillName?path=skills/brand-guidelines

    SkillMetadata now includes a path field, and the list() method returns all same-named skills for disambiguation. The client SDK's getSkill() accepts an optional skillPath parameter for disambiguation.

  • Updated skill search result types and query parameters to use skillName/skillNames instead of skillPath/skillPaths for consistency with the name-based public API. (#14430)

  • Added storage type detection to the Metrics Dashboard. The /system/packages endpoint now returns observabilityStorageType, identifying the observability storage backend. The dashboard shows an empty state when the storage does not support metrics (e.g. PostgreSQL, LibSQL), and displays a warning when using in-memory storage since metrics are not persisted across server restarts. Also added a docs link button to the Metrics page header. (#14620)

    import { MastraClient } from "@mastra/client-js";
    
    const client = new MastraClient();
    const system = await client.getSystemPackages();
    
    // system.observabilityStorageType contains the class name of the observability store:
    // - 'ObservabilityInMemory' → metrics work but are not persisted across restarts
    // - 'ObservabilityPG', 'ObservabilityLibSQL', etc. → metrics not supported
    
    if (system.observabilityStorageType === "ObservabilityInMemory") {
      console.warn("Metrics are not persisted — data will be lost on server restart.");
    }
    
    const SUPPORTED = new Set(["ObservabilityInMemory"]);
    if (!SUPPORTED.has(system.observabilityStorageType ?? "")) {
      console.error("Metrics require in-memory observability storage.");
    }
  • Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. (#14464)

    Mastra agent and client APIs accept schemas from either zod/v3 or zod/v4, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions.

  • Added ModelByInputTokens in @mastra/memory for token-threshold-based model selection in Observational Memory. (#14614)

    When configured, OM automatically selects different observer or reflector models based on the actual input token count at the time the OM call runs.

    Example usage:

    import { Memory, ModelByInputTokens } from "@mastra/memory";
    
    const memory = new Memory({
      options: {
        observationalMemory: {
          model: new ModelByInputTokens({
            upTo: {
              10_000: "google/gemini-2.5-flash",
              40_000: "openai/gpt-4o",
              1_000_000: "openai/gpt-4.5"
            }
          })
        }
      }
    });

    The upTo keys are inclusive upper bounds. OM resolves the matching tier directly at the observer or reflector call site. If the input exceeds the largest configured threshold, OM throws an error.

    Improved Observational Memory tracing so traces show the observer and reflector spans and make it easier to see which resolved model was used at runtime.

Other updated packages

The following packages were updated with dependency changes only:

@mastra/core@1.14.0

Highlights

AI Gateway Tool Support in the Agentic Loop

@mastra/core now supports AI Gateway tools (e.g. gateway.tools.perplexitySearch()) as provider-executed tools: it infers providerExecuted, merges streamed provider results back into the originating tool calls, and skips local execution when the provider already returned a result.

More Reliable Observational Memory (Cache Stability + “As Of” Retrieval)

Observational memory persistence is more stable via dated message boundary delimiters and chunking, and @mastra/memory adds getObservationsAsOf() to retrieve the exact observation set active at a given message timestamp (useful for replay/debugging and consistent prompting).

MCP Client Diagnostics & Per-Server Control

@mastra/mcp adds per-server operational tooling—reconnectServer(serverName), listToolsetsWithErrors(), and getServerStderr(serverName)—to improve reliability and debugging of MCP stdio/server integrations.

Breaking Changes
  • None called out in this changelog.

Changelog

@mastra/core@1.14.0
Patch Changes
  • Update provider registry and model documentation with latest models and providers (51970b3)

  • Added dated message boundary delimiters when activating buffered observations for improved cache stability. (#14367)

  • Fixed provider-executed tool calls being saved out of order or without results in memory replay. (Fixes #13762) (#13860)

  • Fix generateEmptyFromSchema to accept both string and pre-parsed object JSON schema inputs, recursively initialize nested object properties, and respect default values. Updated WorkingMemoryTemplate type to a discriminated union supporting Record<string, unknown> content for JSON format templates. Removed duplicate private schema generator in the working-memory processor in favor of the shared utility. (#14310)

  • Fixed provider-executed tool calls (e.g. Anthropic web_search) being dropped or incorrectly persisted when deferred by the provider. Tool call parts are now persisted in stream order, and deferred tool results are correctly merged back into the originating message. (#14282)

  • Fixed replaceString utility to properly escape $ characters in replacement strings. Previously, patterns like $& in the replacement text would be interpreted as regex backreferences instead of literal text. (#14434)

  • Fixed tool invocation updates to preserve providerExecuted and providerMetadata from the original tool call when updating to result state. (#14431)

  • @mastra/core: patch (#14327)

    Added spanId alongside traceId across user-facing execution results that return tracing identifiers (including agent stream/generate and workflow run results) so integrations can query observability vendors by run root span ID

  • Add AI Gateway tool support in the agentic loop. (#14016)

    Gateway tools (e.g., gateway.tools.perplexitySearch()) are provider-executed but, unlike native provider tools (e.g., openai.tools.webSearch()), the LLM provider does not store their results server-side. The agentic loop now correctly infers providerExecuted for these tools, merges streamed provider results with their corresponding tool calls, and skips local execution when a provider result is already present.

    Fixes #13190

  • Fixed schema-based working memory typing so workingMemory.schema accepts supported schemas such as Zod and JSON Schema. (#14363)

  • Fixed workspace search being wiped when skills refresh. Previously, calling skills.refresh() or triggering a skills re-discovery via maybeRefresh() would clear the entire BM25 search index, including auto-indexed workspace content. Now only skill entries are removed from the index during refresh, preserving workspace search results. (#14287)

  • Added client/server body schemas for feedback and scores that omit the timestamp field, allowing it to be set server-side (#14270)

  • Fixed processor state not persisting between processOutputStream and processOutputResult when processors are wrapped in workflows. State set during stream processing is now correctly accessible in processOutputResult. (#14279)

  • Fixed type inference for requestContext schemas when using Zod v3 and v4. Agent and tool configurations now correctly infer RequestContext types from Zod schemas and other StandardSchema-compatible schemas. (#14363)

@mastra/ai-sdk@1.1.4
Patch Changes
  • Clarified that chatRoute() aborts generation on client disconnect and documented a custom route pattern that continues server-side generation with consumeStream(). (#14333)
@mastra/auth@1.0.2
Patch Changes
  • Fixed Studio showing unauthenticated state when using MastraJwtAuth with custom headers. MastraJwtAuth now implements the IUserProvider interface (getCurrentUser/getUser), so the Studio capabilities endpoint can resolve the authenticated user from the JWT Bearer token. (#14411)

    Also added an optional mapUser option to customize how JWT claims are mapped to user fields:

    new MastraJwtAuth({
      secret: process.env.JWT_SECRET,
      mapUser: payload => ({
        id: payload.userId,
        name: payload.displayName,
        email: payload.mail,
      }),
    });

    Closes #14350

@mastra/auth-studio@1.2.0
Minor Changes
  • Add configurable cookie domain support (#14285)
    • Add cookieDomain option to MastraAuthStudioOptions for explicit configuration
    • Support MASTRA_COOKIE_DOMAIN environment variable as fallback
    • Use hostname-based detection for auto-detecting .mastra.ai domain (prevents false positives from malicious URLs)
    • Maintain backward compatibility with existing .mastra.ai auto-detection
Patch Changes
@mastra/client-js@1.9.0
Minor Changes
  • Added new observability API endpoints and client methods for logs, scores, feedback, metrics (aggregate, breakdown, time series, percentiles), and discovery (metric names, label keys/values, entity types/names, service names, environments, tags) (#14270)
Patch Changes
@mastra/deployer@1.14.0
Patch Changes
  • Added MASTRA_HOST environment variable support for configuring the server bind address. Previously, the host could only be set via server.host in the Mastra config. Now it follows the same pattern as PORT: config value takes precedence, then env var, then defaults to localhost. (#14313)

  • Added a new MASTRA_TEMPLATES Studio runtime flag to control whether the Templates section appears in the sidebar. (#14309)

    • MASTRA_TEMPLATES=true now enables Templates navigation in Studio.
    • By default (false or unset), Templates is hidden.
    • Studio HTML injection now propagates this value in both CLI-hosted and deployer-hosted Studio builds.
    • Added tests covering environment variable injection for both paths.
  • Fixed tsconfig path aliases during build when imports use .js-style module specifiers. (#13998)

  • Fixed apiPrefix server option not being applied to the underlying Hono server instance. Routes, welcome page, Swagger UI, and studio HTML handler now all respect the configured apiPrefix instead of hardcoding /api. (#14325)

@mastra/deployer-cloudflare@1.1.12
Patch Changes
  • Stop writing .env variables to wrangler.jsonc to prevent secrets from leaking into source control. (#14302)
    • Environment variables from .env are no longer merged into the vars field of the generated wrangler config.
    • User-provided vars from the CloudflareDeployer constructor are still written as before.
    • A warning is logged during build with instructions to upload secrets via npx wrangler secret bulk .env.
@mastra/elasticsearch@1.2.0
Minor Changes
  • Added support for constructing ElasticSearchVector with a pre-configured Elasticsearch client. You can now pass either a client instance or connection parameters (url and optional auth), giving you full control over client configuration when needed. (#12802)

    Using connection parameters:

    const vectorDB = new ElasticSearchVector({
      id: 'my-store',
      url: 'http://localhost:9200',
      auth: { apiKey: 'my-key' },
    });

    Using a pre-configured client:

    import { Client } from '@elastic/elasticsearch';
    
    const client = new Client({ node: 'http://localhost:9200' });
    const vectorDB = new ElasticSearchVector({ id: 'my-store', client });
Patch Changes
@mastra/libsql@1.7.1
Patch Changes
  • Added dated message boundary delimiters when activating buffered observations for improved cache stability. (#14367)
@mastra/loggers@1.0.3
Patch Changes
  • Fixed: PinoLogger now supports JSON output for log aggregators (#14306)

    Previously, PinoLogger always used pino-pretty which produced multiline colored output, breaking log aggregators like Datadog, Loki, and CloudWatch. A new prettyPrint option allows switching to single-line JSON output.

@mastra/mcp@1.3.0
Minor Changes
  • Added new MCP client APIs for per-server control and diagnostics. (#14377)

    • Added reconnectServer(serverName) to reconnect a single MCP server without restarting all servers.
    • Added listToolsetsWithErrors() to return both toolsets and per-server errors.
    • Added getServerStderr(serverName) to inspect piped stderr for stdio servers.

    Example

    const { toolsets, errors } = await mcpClient.listToolsetsWithErrors();
    await mcpClient.reconnectServer('slack');
    const stderr = mcpClient.getServerStderr('slack');
Patch Changes
  • Improved (#14260)

    • Updated @modelcontextprotocol/sdk from ^1.17.5 to ^1.27.1.

    Deprecated

    • Deprecated prompt version usage in @mastra/mcp.
    • Prompt versions are not part of MCP protocol behavior and will be removed.

    Migration

    • Use unique prompt names instead of prompt versions.
    • Before: client.prompts.get({ name: 'explain-code', version: 'v1', args })
    • After: client.prompts.get({ name: 'explain-code-v1', args })
    • MastraPrompt is available for migration and is also deprecated.
@mastra/memory@1.8.3
Patch Changes
  • Fixed observational memory triggering observation while provider-executed tool calls are still pending, which could split messages and cause errors on follow-up turns. (#14282)

  • Fixed working memory tool description to accurately reflect merge behavior. The previous description incorrectly stated "Set a field to null to remove it" but null values are stripped by validation before reaching the merge logic. The updated description clarifies: omit fields to preserve existing data, and pass complete arrays or omit them since arrays are replaced entirely. (#14424)

  • Limit oversized observational-memory tool results before they reach the observer. (#14344)

    This strips large encryptedContent blobs and truncates remaining tool result payloads to keep observer prompts and token estimates aligned with what the model actually sees.

  • Improved observational memory cache stability by splitting persisted observations into separate prompt chunks using dated message boundary delimiters. (#14367)

    Added getObservationsAsOf() utility to retrieve the observations that were active at a specific point in time. This enables filtering observation history by message creation date.

    import { getObservationsAsOf } from '@mastra/memory';
    
    // Get observations that existed when a specific message was created
    const observations = getObservationsAsOf(record.activeObservations, message.createdAt);
@mastra/mongodb@1.5.6
Patch Changes
  • Added dated message boundary delimiters when activating buffered observations for improved cache stability. (#14367)
@mastra/pg@1.8.1
Patch Changes
  • Added dated message boundary delimiters when activating buffered observations for improved cache stability. (#14367)
@mastra/playground-ui@17.0.0
Minor Changes
  • Added dedicated session page for agents at /agents/<agentId>/session. This minimal view shows only the chat interface without the sidebar or information pane, making it ideal for quick internal testing or sharing with non-technical team members. If request context presets are configured, a preset dropdown appears in the header. (#13754)

    Added hideModelSwitcher prop to AgentChat and Thread components to allow hiding the model picker in the composer.

Patch Changes
  • Fixed crash during template installation by ensuring error values from stream events are converted to strings before being passed to the UI. This prevents the 'e?.includes is not a function' TypeError when the server returns non-string error payloads. (#14267)

  • Fixed crash when template installation errors are non-string values (e.g. objects). The error is now safely converted to a string before calling .includes(), preventing the 'e?.includes is not a function' TypeError in the studio. (#14267)

@mastra/schema-compat@1.2.5
Patch Changes
  • Added ZodIntersection support so that MCP tools using allOf in their JSON Schema no longer throw 'does not support zod type: ZodIntersection'. Intersection types are flattened and merged into a single object schema across all provider compatibility layers (Anthropic, Google, OpenAI, OpenAI Reasoning, DeepSeek, Meta). (#14255)
@mastra/server@1.14.0
Minor Changes
  • Added new observability API endpoints and client methods for logs, scores, feedback, metrics (aggregate, breakdown, time series, percentiles), and discovery (metric names, label keys/values, entity types/names, service names, environments, tags) (#14270)
Patch Changes
  • Fixed server tool serialization for plain JSON Schema objects to prevent tools from disappearing in Studio. (#13920)
Other updated packages

The following packages were updated with dependency changes only:

Last Checked
4h ago
Domain
mastra.ai
Tracking since Apr 8, 2025