github mastra-ai/mastra @mastra/core@1.33.0
May 13, 2026

latest releases: mastracode@0.18.1, mastra@1.9.1, create-mastra@1.9.1...
7 hours ago

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 #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 #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 #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

Don't miss a new mastra release

NewReleases is sending notifications on new releases.