Skip to content

June 26, 2026

Choose a tag to compare

@PaulieScanlon PaulieScanlon released this 01 Jul 08:45

Highlights

AI SDK v7 (LanguageModelV4) support + upgraded model router

Agents can now accept AI SDK v7 provider models directly (LanguageModelV4), while still supporting v4/v5/v6; Mastra auto-detects model spec versions so you can mix providers across agents. The model router was upgraded to resolve V4 models natively, and modelSettings adds a new reasoning effort control for V4-capable providers.

DurableAgent API parity (stream/resume/generate) + better cancellation & tracing

DurableAgent now mirrors Agent across stream/resume/generate, including full AgentExecutionOptions support (model settings, stop conditions, transforms, per-call instructions/system, tracing options, actor, etc.). New abort surfaces (abortSignal, result.abort(), onAbort) and forwarded abort to delegated subagents improve control, and durable runs now emit richer AGENT_RUN span metadata for observability.

Gateway-first model discovery & auth via new GatewayManager (Harness hooks removed)

Harness now sources model catalog, auth status, mode agents, observational-memory models, and subagents entirely from registered gateways (no fallback to resolveModel, customModelCatalogProvider, or modelAuthChecker, which were removed). Shared gateway operations are centralized in a new GatewayManager exported from @mastra/core, and gateways can implement handlesModel to claim bare provider model IDs for auth resolution.

AgentController becomes the canonical “Harness” surface across core/server/client

Harness is renamed to AgentController with a new canonical import path @mastra/core/agent-controller, and server routes are now exclusively under /agent-controller/.... Client usage follows suit (MastraClient.getAgentController, listAgentControllers, AgentControllerSession), with enriched session state (OM progress, token usage, behavior settings) and thread scoping via session tags (e.g. per git worktree).

Evals quality gates + micro-scorer checks namespace

runEvals now supports pass/fail gates, per-scorer { scorer, threshold } (including { min, max }), and returns verdict, gateResults, and thresholdResults. @mastra/evals adds checks micro-scorers (text assertions and tool-use assertions) to make common eval criteria easy to compose.

Breaking Changes

  • Harness→AgentController migration: @mastra/server removed legacy /harness/... routes and harness:* permissions; clients must use /agent-controller/... and agent-controller:* permissions.
  • @mastra/client-js removed deprecated getHarness/listHarnesses and Harness/HarnessSession classes (breaking for recent client users).
  • Harness config removed resolveModel, customModelCatalogProvider, and modelAuthChecker; register model gateways via gateways instead.
  • Session identity is now required: id and ownerId are mandatory on harness.createSession() and Session/SessionIdentity constructors (no defaults).
  • Workspace/browser ownership moved to per-session: Session constructor now requires workspace: Workspace; HarnessConfig.workspace no longer accepts WorkspaceConfig shorthand (must provide Workspace instance or factory).

Changelog

@mastra/core@1.47.0

Minor Changes

  • Added support for AI SDK v7 models (LanguageModelV4). You can now pass any AI SDK v7 provider model directly to an agent, alongside the existing AI SDK v4, v5, and v6 support. (#18477)

    import { Agent } from '@mastra/core/agent';
    import { openai } from '@ai-sdk/openai'; // AI SDK v7
    
    const agent = new Agent({
      name: 'my-agent',
      instructions: 'You are a helpful assistant.',
      model: openai('gpt-5'),
    });

    Mastra detects the model's specification version automatically, so mixing models from different AI SDK versions across your agents continues to work without any extra configuration.

  • Finished closing the gap between DurableAgent and Agent. After this change the durable agent surface mirrors the in-process agent's stream, resume, and generate APIs. (#18508)

    What's new

    • abortSignal on stream() and resume(), plus result.abort() on stream(), resume(), and observe(). result.abort() on streamUntilIdle() fans out to every inner run.
    • untilIdle on resume() (it already existed on stream()), using the shared runWithIdleWrapper so both paths drive the same idle loop.
    • DurableAgent.generate() and DurableAgent.resumeGenerate() wrap stream() / resume() and resolve a FullOutput even when the run suspends mid-flight.
    • delegation callbacks (onDelegationStart, onDelegationComplete, messageFilter) are forwarded to convertTools at prepare time and baked into sub-agent tool wrappers.
    • Per-call clientTools and toolsets survive in-process resume via the run registry (cross-process resume still falls back to the agent's static tools).
    • Scorers configured on the wrapped agent or passed per call now actually execute under durable runs and emit ON_SCORER_RUN payloads matching the non-durable shape.
    • AGENT_RUN spans now carry conversationId, instructions, resolvedVersionId, entityVersionId, and the agent's tracingPolicy. Resume spans use 'agent run: <id> (resumed)' and include resumedFromSpanId.

    Example

    import { createDurableAgent } from '@mastra/core/agent/durable';
    
    const durable = createDurableAgent({ agent: myAgent });
    
    // 1. generate() — drains a durable run to a single FullOutput
    const out = await durable.generate('Plan a week in Lisbon', {
      abortSignal: controller.signal,
      modelSettings: { temperature: 0.2 },
    });
    
    // 2. stream() with result.abort() — cancel mid-run
    const result = await durable.stream('Long research task');
    setTimeout(() => result.abort(), 5_000);
    for await (const chunk of result.output.fullStream) {
      process.stdout.write(chunk.payload?.text ?? '');
    }
    
    // 3. Suspend → resumeGenerate() round-trip (e.g. tool approval)
    const first = await durable.generate('Run the dangerous tool', {
      requireToolApproval: true,
    });
    if (first.finishReason === 'suspended') {
      const final = await durable.resumeGenerate(first.runId!, { approved: true });
      console.log(final.text);
    }
    
    // 4. resume({ untilIdle }) — drive a resume through to a quiescent state
    await durable.resume(runId, resumeData, { untilIdle: true });
  • Brought DurableAgent.stream() to parity with Agent.stream() for the full AgentExecutionOptions surface, so the same call works on both. (#18461)

    What's new

    DurableAgent.stream() now honors these options that were previously dropped or silently coerced:

    • Full modelSettings (was: only temperature) — maxOutputTokens, topP, topK, presencePenalty, frequencyPenalty, stopSequences, seed, headers
    • stopWhen, prepareStep, isTaskComplete, transform
    • Per-call instructions and system
    • disableBackgroundTasks, tracingOptions, actor
    • Function-form requireToolApproval(toolName, args, …) (was: coerced to "approve all" — now evaluated per tool call)
    • New callbacks: onAbort and onIterationComplete (joining the existing onChunk / onStepFinish / onFinish / onError / onSuspended bridge)

    Example

    The same options that work on Agent.stream() now work on DurableAgent.stream():

    const durable = createDurableAgent({ agent, pubsub });
    
    await durable.stream('Plan the trip', {
      modelSettings: { temperature: 0.2, maxOutputTokens: 500, topP: 0.9 },
      stopWhen: ({ steps }) => steps.length >= 3,
      prepareStep: ({ stepNumber }) => ({
        activeTools: stepNumber === 0 ? ['search'] : ['book'],
      }),
      requireToolApproval: ({ toolName }) => toolName === 'book',
      onIterationComplete: ({ iteration }) => console.log('iter', iteration),
    });
  • Resolve all Harness models through gateways. The Harness now builds its available-models catalog, model auth status, mode agents, Observational Memory models, and subagents from the gateways you register, instead of the separate resolveModel, customModelCatalogProvider, and modelAuthChecker config hooks. Removed those three options from HarnessConfig (and the ModelAuthChecker type) — register a gateway via gateways instead. (#18382)

    listAvailableModels() and getCurrentModelAuthStatus() are now sourced entirely from gateways: model discovery comes from each gateway's fetchProviders() (a network call for gateways like models.dev and Netlify), and auth status is resolved through the same gateway chain the model router uses at run time (resolveAuth(), falling back to getApiKey()). There is no static provider-registry fallback — everything the model picker shows comes from a gateway.

    The gateway-chain operations shared by ModelRouterLanguageModel and the Harness (gateway merging, model→gateway selection, auth resolution, provider/model listing) are now centralized in a new GatewayManager class exported from @mastra/core. Both consumers delegate to it, eliminating duplicated logic. defaultGateways is still re-exported from the same paths for backward compatibility.

  • Added reasoning option to modelSettings for controlling model reasoning effort level. This option accepts standardized levels ('provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh') and is effective with LanguageModelV4 (AI SDK v7) providers that support reasoning. When used with older model providers (V2/V3), the option is a no-op. (#18500)

    Usage:

    const result = await agent.stream('Solve this problem', {
      modelSettings: { reasoning: 'high' },
    });

    Also upgraded the model router to support LanguageModelV4, enabling native V4 model resolution alongside existing V2 and V3 models without regressions.

  • Added an optional availableTools allowlist to HarnessModeBase so each mode can declare one unified list of visible tool names. When set, the harness resolves activeTools at LLM-call time and hides any tool not in the list — including workspace tools, which are matched by their exposed names. Per-tool and category deny permission rules still take precedence over the allowlist. undefined means no mode-level restriction (existing behavior). This moves mode-based tool visibility out of workspace construction and into a single, serializable contract. (#18463)

    Example

    Declare a mode that only exposes read and plan-writing tools:

    import type { HarnessMode } from '@mastra/core/harness';
    
    const planMode: HarnessMode = {
      id: 'plan',
      // Only these tools are visible to the model in plan mode.
      // Workspace tools are matched by their exposed (renamed) names.
      availableTools: ['view', 'find_files', 'search_content', 'write_file', 'submit_plan'],
      // ...other mode config
    };
    
    // Omitting availableTools (or setting undefined) leaves all tools visible.
    // Set to [] to hide every tool for this mode.
  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

  • Gates and verdict for runEvals (#18394)

    New optional gates field accepts scorers that must score 1.0 for the run to pass. Scorers can now use a { scorer, threshold } form to set pass/fail thresholds. threshold accepts a number (minimum) or { min, max } for range-based checks (e.g. hallucination where high = bad). The result includes verdict, gateResults, and thresholdResults. Fully backward compatible.

    import { runEvals } from '@mastra/core/evals';
    import { checks } from '@mastra/evals/checks';
    
    const result = await runEvals({
      data: [{ input: 'What is the weather?' }],
      target: weatherAgent,
      gates: [checks.calledTool('get_weather')],
      scorers: [
        { scorer: faithfulnessScorer, threshold: 0.7 },
        { scorer: hallucinationScorer, threshold: { max: 0.3 } },
      ],
    });
    
    result.verdict; // 'passed' | 'scored' | 'failed'
  • Added waitUntil support for channels so background agent runs survive serverless responses. (#17832)

    Without waitUntil, the runtime freezes the invocation as soon as the webhook response returns, killing the agent run mid-flight and leaving the user with no reply.

    Auto-detected (no config needed):

    • Cloudflare Workers — reads c.executionCtx.waitUntil
    • Netlify Functions — reads c.env.context.waitUntil

    Requires explicit config:

    Vercel needs a waitUntil function passed directly because it exposes waitUntil via AsyncLocalStorage, not the request context. AWS Lambda doesn't need waitUntil — it waits for the event loop to drain naturally.

    import { waitUntil } from '@vercel/functions';
    import { SlackProvider } from '@mastra/slack';
    
    new SlackProvider({ waitUntil });
    new Agent({
      channels: {
        adapters: { slack: createSlackAdapter({ ... }) },
        waitUntil,
      },
    });

    For runtimes where waitUntil lives on the request context but isn't covered by the default, pass resolveWaitUntil: (c) => fn | undefined instead.

    new Agent({
      channels: {
        adapters: { slack: createSlackAdapter({ ... }) },
        resolveWaitUntil: c => c.var.runtime?.waitUntil,
      },
    });

    Resolution order: waitUntilresolveWaitUntil(c) → core default.

  • Moved workspace and browser ownership from the Harness to individual sessions. createSession now accepts optional workspace and browser overrides that are passed directly to the Session constructor. When no override is provided, the Harness-level config is used as a fallback. (#18467)

    Breaking changes:

    • Session constructor now requires workspace: Workspace and accepts optional browser?: MastraBrowser
    • HarnessConfig.workspace no longer accepts a WorkspaceConfig object — pass a Workspace instance or a dynamic factory function
    • createSession overrides accept static Workspace / MastraBrowser instances only (not DynamicArgument)
    • Removed Harness.destroyWorkspace() — workspace lifecycle is now driven by per-session workspace_status_changed, workspace_ready, and workspace_error events emitted after workspace.init() completes
    • Harness.getWorkspace() returns the static workspace instance only (returns undefined for dynamic factory configs); use resolveWorkspace({ session }) to resolve and cache a factory outside the request flow
    • Harness.resolveWorkspace() now requires a session parameter to resolve dynamic factories against the session's request context
    • The SessionBus replays the last workspace lifecycle event group to subscribers that attach after session creation, so late listeners always learn the current workspace status

    Before:

    const harness = new Harness({
      name: 'my-harness',
      workspace: { name: 'my-workspace' }, // WorkspaceConfig shorthand
    });
    
    const session = await harness.createSession({
      resourceId: 'user-1',
      ownerId: 'owner-1',
      id: 'session-1',
    });
    
    // workspace resolved lazily by the harness
    const ws = harness.getWorkspace();

    After:

    import { Workspace } from '@mastra/core';
    
    const harness = new Harness({
      name: 'my-harness',
      workspace: new Workspace({ name: 'my-workspace' }),
    });
    
    // workspace can be overridden per session with a static instance
    const session = await harness.createSession({
      resourceId: 'user-1',
      ownerId: 'owner-1',
      id: 'session-1',
      workspace: new Workspace({ name: 'ws-1' }),
    });
    
    // for dynamic per-request workspaces, use the harness-level config
    const harness2 = new Harness({
      name: 'my-harness',
      workspace: ({ requestContext, mastra }) => new Workspace({ name: `ws-${requestContext.session.id}` }),
    });
  • Surface step providerMetadata to output-step processors (#18432)

    Output-step processors now receive the finishing step's provider-specific metadata through the new optional providerMetadata field on ProcessOutputStepArgs. This lets observability and guardrail processors attribute a model response to the provider data behind it — most notably an AWS Bedrock guardrail trace on a content-filter block, where the completed-steps array is empty and the assessment was previously unreachable.

    The field is present whenever the underlying model step produced provider metadata, in both streaming and non-streaming generation. Behaviour is unchanged for steps without provider metadata.

    class MyProcessor implements Processor {
      async processOutputStep({ finishReason, providerMetadata }: ProcessOutputStepArgs) {
        if (finishReason === 'content-filter') {
          const guardrail = providerMetadata?.bedrock?.trace?.guardrail;
          // attribute the block to the responsible policy/topic/filter
        }
      }
    }
  • Scope harness session creation with tags so sessions sharing a resourceId can (#18446)
    each resume their own thread.

    harness.createSession() now accepts an optional tags record. The tags are
    (a) seeded into the new session's state, (b) stamped onto every thread the
    session creates (so thread listings can be filtered back to the session's
    scope), and (c) used to filter initial thread selection: a thread is a resume
    candidate only when its metadata matches every provided tag. Previously, initial
    thread selection only consulted the
    harness-global initialState.projectPath; on a multi-session server (where one
    Harness serves many scopes) a session could resume the most recently updated
    thread from a different scope that shared the resourceId. Using a generic tag
    record (e.g. { projectPath }) keeps room for future scoping dimensions without
    further API changes.

    The @mastra/client-js HarnessSession.create() method accepts { tags }, and
    the POST /harness/:id/sessions route accepts a tags body field.

    // before: initial thread chosen by resourceId only
    const session = await harness.createSession({ resourceId, id, ownerId });
    
    // after: initial thread scoped to this worktree via a tag
    const session = await harness.createSession({
      resourceId,
      id,
      ownerId,
      tags: { projectPath: '/repo/worktree-a' },
    });

Patch Changes

  • Fixed channel rendering on serverless runtimes (Vercel, AWS Lambda, Cloudflare Workers, Netlify). (#17832)

    No more duplicate replies. On serverless, the same webhook event can be processed by multiple concurrent invocations. Previously each one rendered the agent reply independently, producing duplicate messages. Now only the invocation that wins the run lease renders — losers resolve cleanly without posting.

    Runs no longer get killed mid-flight. The channel handler now keeps the serverless invocation alive until the agent run finishes, instead of returning immediately and relying on a background subscription that gets frozen.

    Multi-step replies fully render. Agents that make multiple LLM calls per run (e.g. tool use → follow-up) now render every step's text instead of only the first.

    Tool approval buttons now appear on all rendering paths. Previously, tool approval prompts (approve/deny buttons) could fail to render when using output processors. Fixed by routing tool-call-approval chunks through the output processor pipeline.

    Tool approval and decline use the same rendering path as regular messages. Approval and decline actions now flow through the per-run output processor instead of a separate subscription-based path, making rendering consistent across all message types.

    User output processors now run before channel rendering. User-configured output processors (e.g. PII redaction, translation) now transform chunks before the channel renders them to the platform, instead of after.

    Concurrency strategy switched to concurrent. The chat-sdk queue lock could get stuck in frozen serverless invocations. Ordering is now handled by the signals/lease layer, removing the stale-lock failure mode.

  • feat(playground): render ask_user tool as interactive question UI in Studio (#18374)

    Adds an AskUserBadge component that renders suspended ask_user tool calls
    as interactive prompts with clickable option buttons (single/multi-select) or
    free-text input. The user's answer is sent as resumeData through the existing
    sendToolApproval flow to properly resume the tool.

    Plumbing changes:

    • sendToolApprovalBodySchema now accepts optional resumeData
    • agent.sendToolApproval() passes custom resumeData when provided
    • @mastra/client-js and @mastra/react expose the new parameter
  • Update provider registry and model documentation with latest models and providers (7c9dd77)

  • Forward the parent abortSignal to delegated subagents so that aborting a supervisor's stream() or generate() call cancels in-flight subagents instead of letting them run to completion. (#17561)

    Previously, calling AbortController.abort() on a supervisor only stopped the supervisor itself: each delegated subagent kept looping and calling its own tools and the LLM for several seconds after the abort, because the delegation tool dropped the parent's abortSignal. The signal is now propagated to every delegation path (stream, generate, resumeStream, resumeGenerate, and the legacy variants).

    const controller = new AbortController();
    
    const stream = await supervisor.stream('Research AI trends', {
      abortSignal: controller.signal,
    });
    
    // Now also cancels any in-flight subagents, not just the supervisor
    controller.abort();

    Fixes #14820.

  • Added DatasetItemPayload, a new exported type describing the user-supplied fields of a dataset item. (#18489)

    Dataset item inputs (AddDatasetItemInput, UpdateDatasetItemInput, and batch insert items) now share this type, so they stay consistent automatically. This is a type-only change with no runtime behavior change.

  • Added collectToolMocks export to @mastra/core/evals. The helper walks a TrajectoryStep[] and returns DatasetItemToolMock[], collecting top-level tool_call and mcp_tool_call steps in recorded order (sub-agent delegations are emitted as matchArgs: 'ignore'). Consumers can now derive item-level tool mocks from a hydrated trajectory directly from @mastra/core. (#18488)

    import { collectToolMocks } from '@mastra/core/evals';
    import type { Trajectory } from '@mastra/core/evals';
    
    const mocks = collectToolMocks(trajectory.steps);
  • Fixed Studio failing to boot in the browser with TypeError: os.tmpdir is not a function. The local sandbox resolved its mount marker directory at module-load time via os.tmpdir(), which crashed the client bundle where node:os has no tmpdir. The directory is now resolved lazily, so importing the Agent runtime in the browser no longer throws. (#18524)

  • Plan rejection now returns distinct tool results for inline-feedback vs. chat-based revision paths, enabling the TUI to stop the agent and wait for user chat messages. (#18323)

  • Added an optional handlesModel hook to the model gateway interface so a gateway can claim bare provider model IDs (like __GATEWAY_ANTHROPIC_MODEL_OPUS__) it is able to authenticate, before routing falls back to the default models.dev catalog. This lets gateways that hold credentials (for example OAuth-backed logins) be selected for auth resolution and availability checks instead of being bypassed. (#18503)

  • Preserved response message boundaries when processor message arrays are reapplied after output step processing. (#18337)

  • Reworked plan mode to use named plan files. The agent writes its plan to a markdown file under .mastracode/plans/ and calls submit_plan with the path to that file, so multiple plans stay on disk for review. The host validates the submitted path is a .md file inside .mastracode/plans/ before reading it, so the tool can't be pointed at arbitrary files. Plan mode can write any .md file inside .mastracode/plans/ (enforced by a tool guard) but nothing else, submitted plan snapshots are persisted for history replay, revision diffs are computed from a real line-ending-normalized LCS diff and only shown when the change is small relative to the plan, "Request Changes" stops the run immediately with no trailing model output, and diffs only compare revisions of the same plan file. (#18490)

  • Fixed InferToolInput, InferToolOutput, InferUITool, and InferUITools so tools created with createTool({ outputSchema: z.object(...) }) now produce a typed { input, output } instead of falling through to never. UIMessage parts (tool-<name>) and other consumers can now discriminate on the inferred input/output types without manual workaround shims. (#17039)

    const echo = createTool({
      id: 'echo',
      inputSchema: z.object({ x: z.string() }),
      outputSchema: z.object({ y: z.number() }),
      execute: async ({ x }) => ({ y: x.length }),
    });
    
    // before: { input: never; output: never }
    // after:  { input: { x: string }; output: { y: number } }
    type UI = InferUITool<typeof echo>;

    Closes the gap left by #7184.

  • Fixed the in-memory workflow store resetting a run's createdAt on every re-persist, so it now matches the persistent stores. Re-persisting an existing run preserves the original createdAt and only advances updatedAt. (#18004)

  • Fixed duplicate step ID warnings in Inngest workflows. When a workflow step ran, two persistence calls with the same ID caused AUTOMATIC_PARALLEL_INDEXING warnings in Inngest logs. Steps now use unique IDs for each persistence call, eliminating the warnings. (#17320)

  • Fixed thread auto-resume selecting the wrong thread in git worktrees by scoping startup selection to threads tagged with the current project path. When Mastra Code detects a matching project thread on a different resource after resourceId drift, it now prompts before cloning and resuming that thread under the current resource; accepting the prompt leaves the old thread untouched and loads the clone, while declining starts fresh and leaves the old resource untouched. (#18333)

  • Fixed duplicate stream events when attaching to an in-progress durable or evented agent run. (#18191)

    When you call agent.observe(runId, { offset }) (or reconnect to a stream with replay) while agent.stream() is still running, the buffered portion of output.textStream could be delivered twice before settling into normal single delivery. Late observers now receive each text-delta exactly once.

    Why: Two issues in the resumable-stream path could double events.

    • If you passed a caching pubsub to new Mastra({ pubsub }) (e.g. withCaching(...)), the agent adopted it as its inner transport and then wrapped it again in a second caching layer sharing the same cache. Every event was stored twice, so replay delivered the buffered prefix doubled. The agent now reuses the existing caching pubsub instead of double-wrapping it.
    // This setup no longer double-caches:
    const cache = new InMemoryServerCache();
    const mastra = new Mastra({ pubsub: withCaching(new EventEmitterPubSub(), cache), cache });
    • Replay/live deduplication keyed on event.id, but the underlying pubsub regenerates id on publish, so the cached copy and the live copy of the same event carried different ids and dedup never matched. Deduplication now keys on the event's stable sequential index, which is preserved across both paths.
  • Workspace skills now refresh on each skill-tool invocation, so edits to SKILL.md files (or new skills added to the workspace) are picked up between tool calls without restarting the server. Set checkSkillFileMtime: true on the workspace config to also detect content changes to existing SKILL.md files. Fixes #16640. (#16678)

  • Fixed createTool type error when passing jsonSchema() or a Schema object from @ai-sdk/provider-utils as inputSchema — no more cast needed (#17435)

  • Fixed reasoning text being lost when agent messages are persisted to memory and recalled on the next turn (#17803)

  • Preserve original tool-call arguments when a tool result is persisted without its matching tool-call in the same message. Previously the arguments were saved as empty {}, which poisoned the model's context and caused it to emit invalid empty-argument tool calls after a few cycles. The adapter now recovers the original arguments from prior persisted messages, covering the server resume path, AG-UI hosts replaying client-tool results, and @mastra/client-js streaming recursion. (#16981)

    Also execute every streamed client tool call emitted in the same tool-calls step before continuing, instead of only continuing with one tool result. Fixes #16017 and #15576.

  • Fixed images from tool results being dropped when using AI SDK v6 (@ai-sdk/* v3) providers. Tools that return image data via toModelOutput (for example screenshots) now reach the model correctly instead of arriving empty. (#18396)

  • Address post-merge review follow-ups from the evented-workflow serialization work: (#18385)

    • Mastra now logs a warning when the TTL sweep evicts a run-scoped workflow that was still registered, surfacing abandoned suspended runs instead of dropping them silently.
    • The RegExp codec's // lgtm[js/regex-injection] suppression now spells out all three structural gates (isEnvelope length cap + flag whitelist, local re-narrowing in decodeRegExpEnvelope, try/catch fallback) at the suppression site so future readers don't reintroduce escaping.
    • The evented-unix-pubsub scenario test now allocates its Unix socket under a short /tmp/aim-* directory instead of os.tmpdir() so it stays under the macOS 104-byte sun_path limit.
  • Added a browser-safe @mastra/core/utils/collect-tool-mocks export for collectToolMocks. The previous @mastra/core/evals barrel pulls in Node-only modules (node:crypto), which broke bundling in browser apps such as the Studio playground. Import the helper from the new subpath in browser code: (#18504)

    import { collectToolMocks } from '@mastra/core/utils/collect-tool-mocks';

    The @mastra/core/evals export still works for Node-side callers.

  • Fixed dynamic workspace resolution to pass the full request context to workspace factories, allowing them to access session state through getState(), and handled async stream aborts correctly so initial streamed output is preserved. (#18497)

  • Added stable session id and ownerId to Harness sessions. SessionIdentity now exposes getId() and getOwnerId(), and harness.createSession() accepts id and ownerId to set them. These identifiers are stable for the life of the session — they do not change when the resource ID is switched — and are surfaced in the harness request context session snapshot. (#18372)

    Breaking change: id and ownerId are now required on both harness.createSession() and the Session/SessionIdentity constructors. There are no defaults.

    // Before
    const session = new Session({ resourceId: 'my-resource' });
    const session = await harness.createSession();
    
    // After
    const session = new Session({
      resourceId: 'my-resource',
      id: 'my-session-id',
      ownerId: 'my-owner-id',
    });
    const session = await harness.createSession({
      id: 'my-session-id',
      ownerId: 'my-owner-id',
    });
    
    session.identity.getId(); // 'my-session-id'
    session.identity.getOwnerId(); // 'my-owner-id'
  • Fixed a thread becoming permanently unresponsive after a signal woke it without anyone reading the agent's response. (#18493)

    When a signal woke a thread in the background and nothing consumed the resulting run, the run never finished and the thread stayed busy forever. Any further signals sent to that thread were then absorbed by the stuck run instead of starting a new one, so the agent stopped responding on that thread. Threads where you read the agent's response normally were never affected. Background-woken threads now finish on their own and keep accepting new signals.

    Also fixed consumeStream() so that await consumeStream() always waits for the run to actually finish, even when consumption was already started elsewhere, and so that every caller's onError runs if the stream fails.

  • Fixed tool-level background config being silently ignored. The background option passed to createTool was accepted by the type system but never stored on the tool instance, so tools opted into background execution at the tool level always ran in the foreground. The option is now stored on the tool and dispatched as a background task. (#18454)

    const researchTool = createTool({
      id: 'research',
      description: 'Run a long research job',
      inputSchema: z.object({ topic: z.string() }),
      background: { enabled: true, timeoutMs: 600_000 },
      execute: async ({ topic }, context) => {
        // ...
      },
    });
  • Fixed semantic recall not injecting recalled messages into the prompt when chatting through Studio. User messages sent through the agent signal pipeline (used by the Studio playground) were stored as signal rows and skipped when extracting the recall query, so recalled context never reached the model even though retrieval worked and the same request succeeded via the HTTP API. Fixes #17797 (#17818)

  • Fixed durable agents (createDurableAgent) silently dropping stored working memory from the prompt. Working memory was saved correctly but never injected back, so per-request preferences had no effect on output. Durable preparation now establishes the memory context before resolving input processors so the working-memory injector is included. (#18411)

  • Fixed conditional workflows so that re-running or rehydrating a run (time travel) no longer leaves the wrong branch marked as active. When a paused or replayed run lands on a conditional, arms whose condition does not evaluate truthy are now correctly recorded as skipped instead of staying stuck in a running state. (#18228)

    The server workflow and schedule run-status response schemas now include the 'skipped' status so they stay in sync with core's WorkflowRunStatus.

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

  • Removed experimental flag from retrieval-mode observational memory. The retrieval API is now stable. (#18324)

@mastra/ai-sdk@1.6.0

Minor Changes

  • Added workflowSnapshotToStream utility to convert a WorkflowState (as returned by getWorkflowRunById) into an AI SDK-compatible stream. This lets you display historical workflow runs using the same useChat-powered UI components used for live workflow streams. (#18453)

    Example usage:

    import { workflowSnapshotToStream } from '@mastra/ai-sdk';
    import { createUIMessageStreamResponse } from 'ai';
    
    const workflowRun = await mastra.getWorkflow('myWorkflow').getWorkflowRunById(runId);
    const stream = workflowSnapshotToStream(workflowRun);
    return createUIMessageStreamResponse({ stream });

Patch Changes

@mastra/auth@1.1.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-auth0@1.2.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-better-auth@1.1.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-clerk@1.2.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-cloud@1.2.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-firebase@1.1.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-google@0.1.0

Minor Changes

  • Added native Google Workspace authentication and group-based RBAC. (#18160)

    • MastraAuthGoogle lets developers authenticate users with Google accounts and restrict access to trusted Google Workspace domains.
    • MastraRBACGoogle lets developers map Google Workspace groups to Mastra permissions for role-based access.

    Usage:

    import { MastraAuthGoogle, MastraRBACGoogle } from '@mastra/auth-google';
    
    const mastra = new Mastra({
      server: {
        auth: new MastraAuthGoogle({
          allowedDomains: ['example.com'],
        }),
        rbac: new MastraRBACGoogle({
          serviceAccount: {
            clientEmail: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL!,
            privateKey: process.env.GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY!,
            subject: process.env.GOOGLE_WORKSPACE_ADMIN_EMAIL!,
          },
          roleMapping: {
            'admins@example.com': ['*'],
            'engineering@example.com': ['agents:*', 'workflows:*'],
            _default: [],
          },
        }),
      },
    });

@mastra/auth-okta@0.1.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-studio@1.3.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-supabase@1.1.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/auth-workos@1.6.1

Patch Changes

  • Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. (#17142)

@mastra/client-js@1.28.0

Minor Changes

  • Enrich the agent-controller HTTP session surface so a client can render a status (#18446)
    line, read behavior settings, and scope threads per working directory.

    GET /agent-controller/:controllerId/sessions/:resourceId now also returns:

    • omProgress — the status-line slice of observational-memory progress
      (pending tokens vs. observation threshold, accumulated observations vs.
      reflection threshold, plus projected message removal / reflection savings)
    • tokenUsage — cumulative token usage for the current thread
    • settings — agent behavior settings (yolo, thinkingLevel,
      notifications, smartEditing)

    GET /agent-controller/:controllerId/sessions/:resourceId/threads accepts
    an optional tags query param (a JSON-encoded object). A single resourceId can
    be shared across git worktrees of the same repo (the id is derived from the git
    URL), so passing tags scopes the list to threads matching every tag (e.g.
    { projectPath } for the working directory). Each returned thread now also
    includes the scoping tags it was stamped with at creation.

    The session event stream route (.../stream) now enqueues raw event objects
    and lets the server adapter handle SSE framing, fixing a double-framing bug
    where events were wrapped twice (data: "data: {...}\n\n"\n\n) and could not be
    parsed by clients.

    @mastra/client-js gains the matching types and reads:

    • AgentControllerOMProgress and AgentControllerSessionSettings, surfaced on
      AgentControllerSessionState (omProgress, tokenUsage, settings)
    • a display_state_changed event in KnownAgentControllerEvent carrying the
      status-line figures
    • AgentControllerSession.listThreads() now accepts either a number
      (back-compat) or { limit?, tags? }

    Example:

    const session = client.getAgentController('code').session(resourceId);
    
    // Scope a worktree's thread list (and resumed session) to its own threads.
    await session.create({ tags: { projectPath: '/repo/worktree-a' } });
    const threads = await session.listThreads({ tags: { projectPath: '/repo/worktree-a' } });
    
    // Read the status-line figures and agent settings.
    const state = await session.state();
    state.omProgress; // observational-memory progress
    state.tokenUsage; // cumulative token usage
    state.settings; // { yolo, thinkingLevel, notifications, smartEditing }
  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

  • Scope harness session creation with tags so sessions sharing a resourceId can (#18446)
    each resume their own thread.

    harness.createSession() now accepts an optional tags record. The tags are
    (a) seeded into the new session's state, (b) stamped onto every thread the
    session creates (so thread listings can be filtered back to the session's
    scope), and (c) used to filter initial thread selection: a thread is a resume
    candidate only when its metadata matches every provided tag. Previously, initial
    thread selection only consulted the
    harness-global initialState.projectPath; on a multi-session server (where one
    Harness serves many scopes) a session could resume the most recently updated
    thread from a different scope that shared the resourceId. Using a generic tag
    record (e.g. { projectPath }) keeps room for future scoping dimensions without
    further API changes.

    The @mastra/client-js HarnessSession.create() method accepts { tags }, and
    the POST /harness/:id/sessions route accepts a tags body field.

    // before: initial thread chosen by resourceId only
    const session = await harness.createSession({ resourceId, id, ownerId });
    
    // after: initial thread scoped to this worktree via a tag
    const session = await harness.createSession({
      resourceId,
      id,
      ownerId,
      tags: { projectPath: '/repo/worktree-a' },
    });

Patch Changes

  • feat(playground): render ask_user tool as interactive question UI in Studio (#18374)

    Adds an AskUserBadge component that renders suspended ask_user tool calls
    as interactive prompts with clickable option buttons (single/multi-select) or
    free-text input. The user's answer is sent as resumeData through the existing
    sendToolApproval flow to properly resume the tool.

    Plumbing changes:

    • sendToolApprovalBodySchema now accepts optional resumeData
    • agent.sendToolApproval() passes custom resumeData when provided
    • @mastra/client-js and @mastra/react expose the new parameter
  • Preserve original tool-call arguments when a tool result is persisted without its matching tool-call in the same message. Previously the arguments were saved as empty {}, which poisoned the model's context and caused it to emit invalid empty-argument tool calls after a few cycles. The adapter now recovers the original arguments from prior persisted messages, covering the server resume path, AG-UI hosts replaying client-tool results, and @mastra/client-js streaming recursion. (#16981)

    Also execute every streamed client tool call emitted in the same tool-calls step before continuing, instead of only continuing with one tool result. Fixes #16017 and #15576.

@mastra/deployer@1.47.0

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

  • Add the experimental Signals observability experience to Studio. Introduces a route-driven enterprise topics/signals trace explorer (topic, subtopic, and trace panels) and a reusable scatter plot chart component in @mastra/playground-ui, with the reusable Signals UI and data placed behind the playground-ui EE export boundary. The Signals page is gated behind a server-injected MASTRA_SIGNALS_UI runtime flag: the Studio HTML exposes window.MASTRA_SIGNALS_UI, which the CLI and deployers populate from the MASTRA_SIGNALS_UI env var (default off). When disabled, the Signals sidebar item and /signals routes are not registered. (#18138)

@mastra/deployer-cloud@1.47.0

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/deployer-cloudflare@1.2.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/deployer-netlify@1.2.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/deployer-vercel@1.2.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/dynamodb@1.1.1

Patch Changes

  • Fixed workflow runs preserving their original creation time when re-persisted in DynamoDB storage, including concurrent saves. (#18004)

@mastra/e2b@0.4.1

Patch Changes

  • Make concurrent S3 and GCS mounts reliable in the same sandbox (#18512)

    Mounting several buckets at once, or restoring mounts after a pause/resume, could previously fail or pick up the wrong credentials because every mount shared one temporary credentials file and overwrote each other's. Each mount now gets its own credentials file, so they no longer interfere. (Azure already worked this way.)

@mastra/evals@1.5.0

Minor Changes

  • Added checks, a new namespace of micro-scorers for common eval assertions. (#18392)

    What changed

    • Added text checks: includes, excludes, equals, matches, and similarity.
    • Added tool checks: calledTool, didNotCall, toolOrder, maxToolCalls, usedNoTools, and noToolErrors.
    • You can now import checks from @mastra/evals/checks.

    Example

    import { checks } from '@mastra/evals/checks';
    
    const scorers = [
      checks.includes('sunny'),
      checks.calledTool('get_weather'),
      checks.toolOrder(['search', 'summarize']),
      checks.noToolErrors(),
    ];

Patch Changes

  • Gates and verdict for runEvals (#18394)

    New optional gates field accepts scorers that must score 1.0 for the run to pass. Scorers can now use a { scorer, threshold } form to set pass/fail thresholds. threshold accepts a number (minimum) or { min, max } for range-based checks (e.g. hallucination where high = bad). The result includes verdict, gateResults, and thresholdResults. Fully backward compatible.

    import { runEvals } from '@mastra/core/evals';
    import { checks } from '@mastra/evals/checks';
    
    const result = await runEvals({
      data: [{ input: 'What is the weather?' }],
      target: weatherAgent,
      gates: [checks.calledTool('get_weather')],
      scorers: [
        { scorer: faithfulnessScorer, threshold: 0.7 },
        { scorer: hallucinationScorer, threshold: { max: 0.3 } },
      ],
    });
    
    result.verdict; // 'passed' | 'scored' | 'failed'

@mastra/express@1.4.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/fastify@1.4.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/github-signals@0.2.2

Patch Changes

  • Avoided reapplying unchanged messages when GitHub signals only emit subscription hint side effects. (#18337)

@mastra/hono@1.5.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/koa@1.6.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/libsql@1.14.2

Patch Changes

  • Fixed persistWorkflowSnapshot resetting a workflow run's createdAt on every re-persist. The default execution engine re-persists a run's snapshot on every step, so createdAt drifted to the last activity time and jumped forward on suspend/resume. Re-persisting now preserves the original createdAt and only advances updatedAt, so listWorkflowRuns ordering, fromDate/toDate filters, and the creation time shown in Studio stay correct. (#18004)

@mastra/memory@1.21.2

Patch Changes

  • Fixed unhandled promise rejection when async buffered observation fails due to missing catch handler (#18026)

  • Removed experimental flag from retrieval-mode observational memory. The retrieval API is now stable. (#18324)

@mastra/mysql@0.3.1

Patch Changes

  • Fixed workflow snapshots and AI spans creating duplicate records instead of updating in place. Each workflow step previously inserted a new row, causing unbounded table growth and degraded read performance. (#18460)

  • Fixed workflow runs preserving their original creation time when re-persisted in MySQL storage, including concurrent saves. (#18004)

@mastra/nestjs@0.2.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/next@0.2.1

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/observability@1.15.2

Patch Changes

  • Fixed pricing lookup when a provider-reported response model does not match a known pricing entry but the configured model does. Cost estimation now falls back to the configured model before reporting no_matching_model. (#16585)

  • Tool telemetry is now more informative for tools that transform their output before sending to the model. Tool-result spans in traces show the actual value the model received instead of being empty, and step input previews display the tool result content instead of an opaque [tool-result] placeholder. This makes it easier to debug tool behavior in Langfuse, Datadog, and other observability providers. (#18417)

@mastra/otel-bridge@1.4.0

Minor Changes

  • Added tracerProvider and loggerProvider options to OtelBridgeConfig, allowing spans and logs to be routed to non-global OpenTelemetry providers. (#18185)

    import { OtelBridge } from '@mastra/otel-bridge';
    
    const bridge = new OtelBridge({
      tracerProvider: myLangfuseTracerProvider,
      loggerProvider: myCustomLoggerProvider,
    });

    Both fields are optional and default to the global provider when omitted — no breaking changes.

Patch Changes

@mastra/pg@1.14.2

Patch Changes

  • Fixed workflow runs advancing their update time when re-persisted in PostgreSQL storage. (#18004)

  • Fixed PgVector numeric range filters ($gt, $gte, $lt, $lte) so rows with non-numeric metadata values no longer fail the whole query. (#18430)

    A single document with a value like { price: 'N/A' } used to make the entire query error out, breaking all range-filtered vector queries (and semantic recall using semanticRecall.filter) on that index. Rows whose value isn't a number are now skipped for numeric range checks instead, matching the behavior of the other Mastra vector stores. Numeric rows still match as expected.

    await pgVector.upsert({
      indexName: 'products',
      vectors: [
        [1, 0],
        [0, 1],
      ],
      metadata: [{ price: 100 }, { price: 'N/A' }],
    });
    
    // Before: threw "invalid input syntax for type numeric: N/A"
    // After: returns only the row whose price is actually a number
    await pgVector.query({
      indexName: 'products',
      queryVector: [1, 0],
      topK: 10,
      filter: { price: { $gt: 50 } },
    });
  • Negated numeric range filters ($not with $gt, $gte, $lt, $lte) now correctly exclude rows where the filtered field is missing or non-numeric. Previously these rows were incorrectly included in results. (#18466)

@mastra/playground-ui@37.0.0

Minor Changes

  • Added a reusable environment variables editor with .env import, bulk paste support, and custom-row hook support. (#18371)

    import { EnvironmentVariablesEditor, useEnvironmentVariablesEditor } from '@mastra/playground-ui';
    
    function SettingsEnvVars() {
      const editor = useEnvironmentVariablesEditor({
        initialRows: [{ key: 'PUBLIC_BASE_URL', value: 'https://example.com' }],
      });
    
      return (
        <EnvironmentVariablesEditor
          editor={editor}
          actions={
            <button type="button" onClick={() => editor.getEnvironmentVariablesForSubmit()}>
              Save
            </button>
          }
        />
      );
    }
  • Added sticky row headers to DataList. (#18222)

    Use sticky="start" on the leading DataList.TopCell and render matching row cells with DataList.RowHeaderCell:

    <DataList columns="auto auto auto" variant="lined">
      <DataList.Top>
        <DataList.TopCell sticky="start">Model</DataList.TopCell>
        <DataList.TopCell>Input</DataList.TopCell>
        <DataList.TopCell>Output</DataList.TopCell>
      </DataList.Top>
      <DataList.RowStatic>
        <DataList.RowHeaderCell>Model A</DataList.RowHeaderCell>
        <DataList.Cell>1,200</DataList.Cell>
        <DataList.Cell>800</DataList.Cell>
      </DataList.RowStatic>
    </DataList>

    Metrics tables now render directly through DataList so sticky row headers share the same header colors and hover treatment as the rest of the list system. Metrics tables also follow the default DataList variant, and sticky row headers use the same neutral header treatment as column headers.

    DataList now exposes stickyHeaderBackground to keep the top header and sticky row-header fill in sync, and forwards mask to the underlying ScrollArea so sticky-start tables can disable the left edge fade.

    Added DataList.NumberCell for right-aligned numeric columns. It bakes in the tabular-figure, compact metric-table styling, with a highlight prop for the emphasized value:

    <DataList.NumberCell>1,200</DataList.NumberCell>
    <DataList.NumberCell highlight>$0.42</DataList.NumberCell>

    The metrics-specific MetricsDataTable wrapper was removed. Use DataList for DS-owned metrics table layouts.

  • Add the experimental Signals observability experience to Studio. Introduces a route-driven enterprise topics/signals trace explorer (topic, subtopic, and trace panels) and a reusable scatter plot chart component in @mastra/playground-ui, with the reusable Signals UI and data placed behind the playground-ui EE export boundary. The Signals page is gated behind a server-injected MASTRA_SIGNALS_UI runtime flag: the Studio HTML exposes window.MASTRA_SIGNALS_UI, which the CLI and deployers populate from the MASTRA_SIGNALS_UI env var (default off). When disabled, the Signals sidebar item and /signals routes are not registered. (#18138)

Patch Changes

  • Fixed overlay content components to support Base UI positioning options. (#18431)

  • Added direct Playground UI subpaths for shared helpers so apps can avoid the root barrel when importing existing utility and rule builder APIs. (#18502)

    import { is401UnauthorizedError } from '@mastra/playground-ui/utils/errors';
    import type { JsonSchema } from '@mastra/playground-ui/utils/json-schema';
    import { RuleBuilder } from '@mastra/playground-ui/components/RuleBuilder';
  • Show an unavailable state when the configured observability storage does not support listing logs, and stop polling the logs endpoint for that non-transient capability error. (#18375)

  • Added a direct utility import for the class name helper so applications can avoid the root Playground UI barrel. (#18458)

    import { cn } from '@mastra/playground-ui/utils/cn';
  • Added direct import paths for toast and Playground UI icons so apps can avoid the root Playground UI barrel when using high-traffic utilities and icon components. (#18470)

    import { Toaster } from '@mastra/playground-ui/components/Toaster';
    import { AgentIcon } from '@mastra/playground-ui/icons/AgentIcon';
    import { toast } from '@mastra/playground-ui/utils/toast';
  • Move the Memory Studio (timeline, flamegraph, and observational-memory detail) into the agent chat view as an opt-in panel. (#18272)

    • The standalone Memory nav entry, /memory routes, and the separate thread/chat list are removed; the studio is now opened from the chat view and shown inside the Memory sidepanel, with the agent layout's left resizable panel expanding when the detail opens. Clicking the flamegraph timeline drives a replay cursor that highlights the matching observational-memory record. Marker types are imported from @mastra/memory instead of being redeclared in the UI so the studio stays in sync with the stream format.
    • MemoryStudioPanel gains an optional contextWindow prop so callers can supply authoritative message/observation token counts and thresholds; when provided these take precedence over values re-derived from message markers, keeping the panel's MESSAGES/OBSERVATIONS readout in sync with the observational-memory sidebar (marker-derived values remain the fallback for standalone usage).
    • MemoryStudioPanel now shows both Messages and Observations progress bars, matching the collapsed memory sidebar. The FlameGraph zoom range is lifted into the panel and filters the observation list: collapsing the range hides out-of-range observations and "Reset zoom" restores the full list. FlameGraph gains optional controlled zoomRange/onZoomRangeChange props (uncontrolled usage is unchanged).

@mastra/react@1.2.0

Minor Changes

  • Added tasks as a first-class return value on useChat. Task state is incrementally updated from streaming task state signals and tool results — no message rescanning required. (#18374)

    Fixed step framing markers leaking into the chat UI. The MessageFactory now renders step-start parts as nothing by default instead of routing them to your fallback renderer, so internal step boundaries no longer show up as stray output. You can still opt in to rendering a step divider by supplying a StepStart renderer.

Patch Changes

  • feat(playground): render ask_user tool as interactive question UI in Studio (#18374)

    Adds an AskUserBadge component that renders suspended ask_user tool calls
    as interactive prompts with clickable option buttons (single/multi-select) or
    free-text input. The user's answer is sent as resumeData through the existing
    sendToolApproval flow to properly resume the tool.

    Plumbing changes:

    • sendToolApprovalBodySchema now accepts optional resumeData
    • agent.sendToolApproval() passes custom resumeData when provided
    • @mastra/client-js and @mastra/react expose the new parameter

@mastra/redis@1.2.1

Patch Changes

  • nodeRedisPreset now adapts the three list operations (llen, rpush, lrange) to their camelCase forms on node-redis v4+ clients (lLen, rPush, lRange). Extends the same adapter pattern already used for set and scan. ioredis and Upstash users are unaffected (defaults remain lowercase, matching their APIs). (#18408)

@mastra/schema-compat@1.3.1

Patch Changes

  • Fixed createTool type error when passing jsonSchema() or a Schema object from @ai-sdk/provider-utils as inputSchema — no more cast needed (#17435)

@mastra/server@1.47.0

Minor Changes

  • Enrich the agent-controller HTTP session surface so a client can render a status (#18446)
    line, read behavior settings, and scope threads per working directory.

    GET /agent-controller/:controllerId/sessions/:resourceId now also returns:

    • omProgress — the status-line slice of observational-memory progress
      (pending tokens vs. observation threshold, accumulated observations vs.
      reflection threshold, plus projected message removal / reflection savings)
    • tokenUsage — cumulative token usage for the current thread
    • settings — agent behavior settings (yolo, thinkingLevel,
      notifications, smartEditing)

    GET /agent-controller/:controllerId/sessions/:resourceId/threads accepts
    an optional tags query param (a JSON-encoded object). A single resourceId can
    be shared across git worktrees of the same repo (the id is derived from the git
    URL), so passing tags scopes the list to threads matching every tag (e.g.
    { projectPath } for the working directory). Each returned thread now also
    includes the scoping tags it was stamped with at creation.

    The session event stream route (.../stream) now enqueues raw event objects
    and lets the server adapter handle SSE framing, fixing a double-framing bug
    where events were wrapped twice (data: "data: {...}\n\n"\n\n) and could not be
    parsed by clients.

    @mastra/client-js gains the matching types and reads:

    • AgentControllerOMProgress and AgentControllerSessionSettings, surfaced on
      AgentControllerSessionState (omProgress, tokenUsage, settings)
    • a display_state_changed event in KnownAgentControllerEvent carrying the
      status-line figures
    • AgentControllerSession.listThreads() now accepts either a number
      (back-compat) or { limit?, tags? }

    Example:

    const session = client.getAgentController('code').session(resourceId);
    
    // Scope a worktree's thread list (and resumed session) to its own threads.
    await session.create({ tags: { projectPath: '/repo/worktree-a' } });
    const threads = await session.listThreads({ tags: { projectPath: '/repo/worktree-a' } });
    
    // Read the status-line figures and agent settings.
    const state = await session.state();
    state.omProgress; // observational-memory progress
    state.tokenUsage; // cumulative token usage
    state.settings; // { yolo, thinkingLevel, notifications, smartEditing }
  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

  • Scope harness session creation with tags so sessions sharing a resourceId can (#18446)
    each resume their own thread.

    harness.createSession() now accepts an optional tags record. The tags are
    (a) seeded into the new session's state, (b) stamped onto every thread the
    session creates (so thread listings can be filtered back to the session's
    scope), and (c) used to filter initial thread selection: a thread is a resume
    candidate only when its metadata matches every provided tag. Previously, initial
    thread selection only consulted the
    harness-global initialState.projectPath; on a multi-session server (where one
    Harness serves many scopes) a session could resume the most recently updated
    thread from a different scope that shared the resourceId. Using a generic tag
    record (e.g. { projectPath }) keeps room for future scoping dimensions without
    further API changes.

    The @mastra/client-js HarnessSession.create() method accepts { tags }, and
    the POST /harness/:id/sessions route accepts a tags body field.

    // before: initial thread chosen by resourceId only
    const session = await harness.createSession({ resourceId, id, ownerId });
    
    // after: initial thread scoped to this worktree via a tag
    const session = await harness.createSession({
      resourceId,
      id,
      ownerId,
      tags: { projectPath: '/repo/worktree-a' },
    });

Patch Changes

  • feat(playground): render ask_user tool as interactive question UI in Studio (#18374)

    Adds an AskUserBadge component that renders suspended ask_user tool calls
    as interactive prompts with clickable option buttons (single/multi-select) or
    free-text input. The user's answer is sent as resumeData through the existing
    sendToolApproval flow to properly resume the tool.

    Plumbing changes:

    • sendToolApprovalBodySchema now accepts optional resumeData
    • agent.sendToolApproval() passes custom resumeData when provided
    • @mastra/client-js and @mastra/react expose the new parameter
  • Fixed conditional workflows so that re-running or rehydrating a run (time travel) no longer leaves the wrong branch marked as active. When a paused or replayed run lands on a conditional, arms whose condition does not evaluate truthy are now correctly recorded as skipped instead of staying stuck in a running state. (#18228)

    The server workflow and schedule run-status response schemas now include the 'skipped' status so they stay in sync with core's WorkflowRunStatus.

@mastra/slack@1.5.0

Minor Changes

  • Added waitUntil support for channels so background agent runs survive serverless responses. (#17832)

    Without waitUntil, the runtime freezes the invocation as soon as the webhook response returns, killing the agent run mid-flight and leaving the user with no reply.

    Auto-detected (no config needed):

    • Cloudflare Workers — reads c.executionCtx.waitUntil
    • Netlify Functions — reads c.env.context.waitUntil

    Requires explicit config:

    Vercel needs a waitUntil function passed directly because it exposes waitUntil via AsyncLocalStorage, not the request context. AWS Lambda doesn't need waitUntil — it waits for the event loop to drain naturally.

    import { waitUntil } from '@vercel/functions';
    import { SlackProvider } from '@mastra/slack';
    
    new SlackProvider({ waitUntil });
    new Agent({
      channels: {
        adapters: { slack: createSlackAdapter({ ... }) },
        waitUntil,
      },
    });

    For runtimes where waitUntil lives on the request context but isn't covered by the default, pass resolveWaitUntil: (c) => fn | undefined instead.

    new Agent({
      channels: {
        adapters: { slack: createSlackAdapter({ ... }) },
        resolveWaitUntil: c => c.var.runtime?.waitUntil,
      },
    });

    Resolution order: waitUntilresolveWaitUntil(c) → core default.

Patch Changes

  • Normalize trailing slashes in the Slack provider baseUrl. A baseUrl with a trailing slash (e.g. MASTRA_BASE_URL=https://example.com/) previously produced double-slash callback URLs like https://example.com//slack/oauth/callback, which broke the OAuth flow and webhook delivery. The trailing slash is now stripped, so callback URLs are always well-formed. (#18483)

@mastra/tanstack-start@0.2.1

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/temporal@0.2.2

Patch Changes

  • Rename the Harness class to AgentController and its associated Harness* types to AgentController* (e.g. HarnessConfigAgentControllerConfig, HarnessModeAgentControllerMode, HarnessEventAgentControllerEvent). (#18505)

    @mastra/core: A new canonical subpath @mastra/core/agent-controller exports the AgentController class plus the AgentController* types. The legacy @mastra/core/harness subpath remains available and re-exports the deprecated Harness* aliases, so existing Harness class and type usage continues to work unchanged. New code should import from @mastra/core/agent-controller. On Mastra, the hosted-controller API is exposed under agent-controller names — getAgentController, getAgentControllerById, listAgentControllers, and the agentControllers config key — while getHarness/getHarnessById/listHarnesses and the harnesses config key remain as deprecated aliases.

    @mastra/server: The controller session API is now served exclusively under /agent-controller/... (with agent-controller:read / agent-controller:execute permissions). The legacy /harness/... routes and harness:* permissions have been removed. List responses use the agentControllers key, session responses use controllerId, and path params use :controllerId.

    @mastra/client-js: AgentController and AgentControllerSession are now the canonical resource classes, with MastraClient.getAgentController(id) / listAgentControllers() targeting the /agent-controller routes and reading the canonical agentControllers / controllerId response keys. The deprecated getHarness / listHarnesses methods and Harness / HarnessSession classes have been removed. This is a breaking change for the recently released client.

    The @mastra/core peer dependency floor is raised to >=1.47.0-0 so consumers must be on the release that introduces the canonical agent-controller surface. This applies to @mastra/server, @mastra/deployer, and mastra (CLI), and is propagated to the packages that depend on them: the deployers (@mastra/deployer-cloud, @mastra/deployer-cloudflare, @mastra/deployer-netlify, @mastra/deployer-vercel), the server adapters (@mastra/express, @mastra/fastify, @mastra/hono, @mastra/koa, @mastra/nestjs, @mastra/next, @mastra/tanstack-start), and @mastra/temporal.

@mastra/upstash@1.2.1

Patch Changes

  • Fixed workflow runs preserving their original creation time when re-persisted in Upstash storage, including concurrent saves. (#18004)

@mastra/voyageai@0.3.0

Minor Changes

  • Added support for the voyage-context-4 contextualized chunk embedding model (preview). Each chunk is embedded with awareness of the other chunks in the same document, capturing both local detail and document-level context. Supports flexible output dimensions (256, 512, 1024, 2048). (#18413)

    import { voyage, voyageContextualizedEmbedding } from '@mastra/voyageai';
    
    // Pre-configured model
    const result = await voyage.context4.doEmbed({
      values: [['Paragraph 1 from doc 1...', 'Paragraph 2 from doc 1...'], ['Content from doc 2...']],
      inputType: 'document',
    });
    
    // Or configure explicitly
    const model = voyageContextualizedEmbedding({ model: 'voyage-context-4', outputDimension: 512 });

Other updated packages

The following packages were updated with dependency changes only: