Highlights
Sandbox Deployments + One-Step mastra build (New @mastra/deployer-sandbox)
You can now deploy a full Mastra server (including Studio) into networking-capable workspace sandboxes (Vercel/E2B/Daytona) and get a live URL in seconds via the new @mastra/deployer-sandbox. Push-style deployers can opt into deployOnBuild, making mastra build bundle and deploy in one step (SandboxDeployer already opts in).
Workspace Sandbox Networking, Bulk File Uploads, and Cloning (Fleet-Ready Sandboxes)
WorkspaceSandbox gained optional networking.getPortUrl(port) plus writeFiles() for bulk uploads, enabling preview URLs and faster sandbox provisioning; multiple providers now implement this (notably Vercel/E2B/Daytona). Sandboxes also add clone() to create unstarted sibling sandboxes from a template config—ideal for per-project or per-tenant sandbox fleets.
Factory Storage Domains + New Pg/LibSQL “FactoryStorage” Backends
A new FactoryStorage contract and FactoryStorageDomain enable lifecycle-managed, per-domain application storage initialization/readiness/error reporting, rather than a single monolithic store. New backends PgFactoryStorage and LibSQLFactoryStorage persist Mastra agent state plus custom app domains through one shared Postgres pool/LibSQL connection.
New @mastra/factory Package (Software Factory Extraction + Reusable Route Surfaces)
Software Factory storage domains and route surfaces were extracted into a new @mastra/factory package (projects, work items, intake, audit, credentials, integrations, model packs, etc.) so they can be reused outside the Mastra Code web host. It also adds autonomous first-pass skills (triage/plan/review) that auto-run when items enter matching board columns, reducing required human-in-the-loop steps.
Agent Runtime Enhancements: Durable-by-Config, Multi-Turn Evals, and Per-Request Model Overrides
Agents can now opt into durable execution via AgentConfig.durable (no separate createDurableAgent call), and standalone new Agent({ durable: true }) now reliably uses durable paths across stream/generate/resume/approval APIs. runEvals now supports multi-turn conversations (inputs: string[]) plus per-turn assertions (turns) for stronger regression testing, and agent execution/approvals gain request-scoped model overrides (also fixing Studio so each tab can use a different model).
Breaking Changes
- @mastra/braintrust: Bundled Braintrust SDK upgraded v2 → v3;
BraintrustExporterConfigtypes narrowed (logger/span shims) androot_span_idsemantics differ in v3.
Changelog
@mastra/core@1.52.0
Minor Changes
-
mastra buildnow deploys in one step for push-style deployers. Deployers can opt in with the newdeployOnBuildflag on the deployer contract, and the build runs theirdeploy()right after bundling.SandboxDeployerfrom@mastra/deployer-sandboxopts in, so configuring it meansmastra buildbundles your project, deploys it into the sandbox, and prints the live URL. Existing platform deployers are unchanged. (#19577)import { Mastra } from '@mastra/core/mastra'; import { SandboxDeployer } from '@mastra/deployer-sandbox'; import { VercelSandbox } from '@mastra/vercel'; export const mastra = new Mastra({ deployer: new SandboxDeployer({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), }), });
mastra build # bundles AND deploys — prints the live URL -
The 'workers' option on the Mastra class now merges with the default workers instead of replacing them. Passing custom workers (e.g. a polling worker for an integration) no longer silently drops the built-in orchestration and background-task workers — a custom worker only replaces a default when it shares its name, and 'workers: false' still disables all workers. (#19766)
const mastra = new Mastra({ // before: only myPoller ran — orchestration was dropped // after: myPoller runs alongside the default workers workers: [myPoller], });
-
Added optional provider-driven authorization for system (non-user) actors, enabling per-agent least privilege after tenant scope has been validated. (#19338)
System actors (
actor: { actorKind: 'system' }oractor: true) skip the user-centric FGA path. Previously the only boundary for them was tenant scope — there was no way to constrain which agent could do what, and no deny path. FGA providers can now opt in to enforce least privilege for those actors.What's new
IFGAProvidergains an optionalrequireActor(actor, params)method that throwsFGADeniedErrorto deny.ActorSignalgains optionalagentId,permissions, andscopeso a provider can identify and constrain the acting agent.permissionsreuses theMastraFGAPermissionInputvocabulary — the actor analog of a user's resolved permissions. It is a self-asserted claim: a provider enforcing real least privilege should resolve authoritative grants from a trusted source keyed byagentIdrather than trusting it directly.- When a provider does not implement
requireActor, the existing trusted-actor bypass is preserved exactly — this change is fully backward compatible.
Before — system actors had tenant scoping but no provider-defined per-agent authorization:
// Cron/system run: tenant scope is required, but no per-agent limits are possible. await agent.generate('run nightly report', { actor: { actorKind: 'system', sourceWorkflow: 'nightly' }, });
After — a provider enforces least privilege on the acting agent:
import type { IFGAProvider } from '@mastra/core/auth/ee'; import { FGADeniedError } from '@mastra/core/auth/ee'; class MyFga implements IFGAProvider { // ...existing check / require / filterAccessible... async requireActor(actor, { resource, permission }) { const agentId = actor === true ? undefined : actor.agentId; // Resolve authoritative grants from a trusted source keyed by agentId. const granted = await this.grantsForAgent(agentId); // `permission` may be a single value or an array (needs ANY one of them). const required = Array.isArray(permission) ? permission : [permission]; if (!required.some(p => granted.includes(p))) { throw new FGADeniedError(null, resource, permission, 'actor lacks required permission'); } } } // The acting agent is now identified and constrained by its granted permissions. await agent.generate('run nightly report', { actor: { actorKind: 'system', agentId: 'nightly-agent', permissions: ['agents:execute', 'tools:execute'], }, });
-
Added an optional
networkingcapability andwriteFiles()method to theWorkspaceSandboxinterface. Sandbox providers that expose public port URLs can now implementnetworking.getPortUrl(port), which enables preview URLs and sandbox deploys (see the new@mastra/deployer-sandboxpackage). Use the newsupportsNetworking()type guard to detect the capability at runtime. (#19577)import { supportsNetworking } from '@mastra/core/workspace'; if (supportsNetworking(sandbox)) { const url = await sandbox.networking.getPortUrl(4111); }
-
Added
onTitleGeneratedcallback to memory options. WhengenerateTitleis enabled, passonTitleGeneratedin thememoryproperty ofagent.generate()oragent.stream()to be notified when the thread title has been generated and persisted to storage. This is a per-request callback, so each request handler can use its own callback with direct access to its response stream — no storage adapter wrapping needed. (#18998)Example:
await agent.stream('Hello', { memory: { thread: threadId, resource: userId, onTitleGenerated: title => { res.write(`event: title\ndata: ${JSON.stringify({ title })}\n\n`); }, }, });
-
Update the experimental AgentController message surface to use the canonical
MastraDBMessageshape. (#18783)The AgentController now emits, persists, and returns DB-native messages where message parts live under
content.parts, terminal status lives undercontent.metadata, and completed tool invocations retain their explicit error state. Signals such as system reminders and notifications now arrive as separate messages withrole: 'signal'instead of being flattened into assistant message content.This affects the experimental AgentController event and session APIs, including
message_start,message_update,message_end,currentMessage,listMessages,listActiveMessages,firstUserMessage, andfirstUserMessages.agentController.subscribe(event => { if (event.type === 'message_end' && event.message.role === 'assistant') { // Before: parts were flattened directly onto the message // After: read parts and terminal status from the DB-native shape const text = event.message.content.parts .filter(part => part.type === 'text') .map(part => part.text) .join(''); const stopReason = event.message.content.metadata?.stopReason; } }); // System reminders and notifications are now separate messages const messages = await agentController.session.thread.listActiveMessages(); const signals = messages.filter(message => message.role === 'signal');
-
Added a FactoryStorage contract that owns application storage domains, initializes them with the shared backend, and reports per-domain readiness and initialization errors. (#19681)
import { FactoryStorageDomain } from '@mastra/core/storage'; import { LibSQLFactoryStorage } from '@mastra/libsql'; class TasksStorage extends FactoryStorageDomain { constructor() { super('tasks'); } async init() { await this.ensureCollections([{ name: 'tasks', columns: { id: { type: 'uuid-pk' } } }]); } async dangerouslyClearAll() { await this.ops.deleteMany('tasks', {}); } } const storage = new LibSQLFactoryStorage({ url: 'file:mastra.db' }); storage.registerDomain(new TasksStorage()); await storage.init();
-
Added request-scoped model overrides to agent execution and approvals, and fixed Studio model selection so each tab can use a different model without changing the agent's configured default. (#19749)
await agent.stream(messages, { model: 'google/gemini-2.5-flash' });
-
Added access to the workspace resolved for an AgentController session. (#19547)
Use the session-owned workspace when an operation must remain isolated to that session:
const session = await controller.createSession({ resourceId, scope }); const workspace = session.getWorkspace();
Mastra Code workspace resolvers can now accept an isolated read-only skill extension:
const workspace = await getDynamicWorkspace({ requestContext, skillExtension: { id: 'review-skills', paths: ['/__review_skills__'], createSource: fallback => new ReviewSkillSource(fallback), }, });
This lets SDK consumers compose additional read-only skill roots into selected workspaces without changing the default workspace skill set.
-
Added an optional
clone()method to theWorkspaceSandboxcontract, along with the newSandboxCloneOptionstype.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides — without performing any I/O. This lets one configured sandbox act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new RailwaySandbox({ token, environmentId }); // Fresh sandbox clone for a project — provisions on start() const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
LocalSandboximplementsclone()out of the box. -
Added an optional
threadIdparameter to AgentController session creation so hosts can create or resume a session bound to an exact thread ID. (#19758) -
Added multi-turn support to
runEvals. Data items can now include aninputs: string[]array — each entry is sent sequentially to the agent on the same thread, and scorers see the accumulated output from all turns. (#18395)What changed
RunEvalsDataItemaccepts an optionalinputsarray for multi-turn conversations- Each turn runs
agent.generate()with the samethreadId, preserving conversation history runEvalsinjects both a sharedthreadIdand aresourceId(Mastra memory scopes messages by resource + thread, so both are needed for recall); the resource defaults to the generated thread and a caller-providedtargetOptions.memory.resourceis preserved.threadis now optional intargetOptions.memorysincerunEvalsowns it.- Scorers receive the accumulated output messages from all turns
- Works with gates, thresholds, and all existing scorer configurations
- Validation rejects empty
inputsarrays with aMastraError
Also added a
turnsAPI for per-turn assertions. Instead of a single holistic score over the accumulated conversation (which can hide a broken follow-up turn), each turn colocates itsinputwithgates/scorersthat evaluate only that turn's input and output. Per-turn outcomes are reported inresult.turnResultsand folded into the overallverdict. When the agent has storage configured, per-turn scorer/gate results are persisted like top-level scores.turnsis Agent-only and mutually exclusive withinput/inputs.Example — holistic multi-turn (
inputs)import { runEvals } from '@mastra/core/evals'; import { checks } from '@mastra/evals/checks'; const result = await runEvals({ target: weatherAgent, data: [ { inputs: ['What is the weather in Brooklyn?', 'What about tomorrow?', 'Compare the two forecasts.'], }, ], scorers: [checks.calledTool('get_weather', { times: 2 }), checks.includes('Brooklyn')], });
Example — per-turn assertions (
turns)const result = await runEvals({ target: weatherAgent, data: [ { turns: [ { input: 'What is the weather in Brooklyn?', gates: [checks.calledTool('get_weather')], }, { input: 'What about tomorrow?', gates: [checks.calledTool('get_weather')], // must call again this turn scorers: [{ scorer: checks.similarity('tomorrow forecast'), threshold: 0.5 }], }, ], }, ], }); result.verdict; // folds in per-turn gate/threshold outcomes result.turnResults; // [{ index, gateResults, thresholdResults, scores }]
Patch Changes
-
Added a public ensureReady() method to FactoryStorageDomain so consumers can initialize a storage domain without going through a global registry. (#19866)
-
Added a
durablefield toAgentConfigso an agent can opt into durable execution without a separatecreateDurableAgentcall. Settingdurable: true(ordurable: { cache, pubsub, maxSteps, cleanupTimeoutMs }) auto-wraps the agent withcreateDurableAgentwhen it is attached to aMastrainstance; the factory function remains available for advanced use. (#19371)const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model, durable: true, // or: { maxSteps: 10, cleanupTimeoutMs: 60_000 } }); export const mastra = new Mastra({ agents: { agent } });
-
Fixed
new Agent({ durable: true })to actually use the durable execution path when used standalone (without being registered on aMastrainstance). (#19632)Previously,
durable: trueonly took effect when the agent was attached to aMastra. Constructing an agent withdurable: trueand callingagent.stream(...)directly silently ran through the non-durable path.Now
stream,generate,resumeStream,resumeGenerate,approveToolCall,declineToolCall,streamUntilIdle,resume,recover,listActiveRuns,recoverActiveRuns,observe, andprepareall run through the durable execution path on a standalonenew Agent({ durable: true }).const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model, durable: true, }); // Now runs through the durable execution path. await agent.stream('hi');
-
Update provider registry and model documentation with latest models and providers (
8a0d145) -
Fixed model router requests through OpenRouter failing with a 400 "thinking blocks cannot be modified" error when an Anthropic model (e.g. openrouter/anthropic/claude-sonnet-4-6) used extended thinking together with parallel tool calls. The bundled OpenRouter provider was updated from 1.5.4 to 2.10.0, which no longer duplicates reasoning details on each tool call when sending conversation history back to the API. Fixes #19436 (#19493)
-
Fixed bounded process output stalling other requests while high-volume commands continue streaming. (#19600)
-
Fixed goal judge results rendering more than once during live goal runs. (#19613)
-
Ensured that canceled workflow results correctly format and deduplicate their payloads before returning. (#19225)
-
Fixed fine-grained authorization (FGA) checks for
DurableAgentexecution and tool approvals. (#19338)Durable agents now enforce
agents:executeDurableAgentoverridesstream()andgenerate()to run a workflow instead of the base agent path. Those methods now resolve default and per-call options once, then authorize the same effective actor, request context, and memory resource used for execution.Behavior change: with an FGA provider configured, durable runs that were previously allowed through are now checked and can be denied.
Durable resumes reauthorize each call
Durable approval and decline methods now forward the trusted request context and selected tool-call ID into resume authorization and workflow resume. The actor is resolved for each call and forwarded to both agent authorization and tool execution. An explicit resume actor replaces the initial actor, while a newly resolved default actor can still apply. If neither exists, the resume uses normal user authorization and fails closed when no user is available.
-
Added optional auth provider capability interfaces and type guards to
@mastra/core/server:IOrganizationsProvider(personal organization bootstrap and admin checks),IAuthInit(host-driven initialization with database, public URL, and allowed origins), andIAuthHttpHandler(mounting a provider's own HTTP auth endpoints). Server hosts can use theisOrganizationsProvider,isAuthHttpHandler, andhasAuthInitguards to detect what an auth provider supports and wire routes accordingly. (#19765) -
Fixed Babel 8 compatibility for build-time transforms. (#19393)
-
Fixed caller-supplied tool providers so selected connectionless tools can initialize and manage user connections. (#19872)
-
Updated the unsupported-adapter error for resource-scoped message listing to include convex in the list of storage adapters that support Observational Memory. (#19474)
-
Added native structured-output support lookup through the model provider registry. (#19440)
import { modelSupportsStructuredOutput } from '@mastra/core/llm'; const supportsStructuredOutput = modelSupportsStructuredOutput('openai/gpt-5.5');
-
Cap the LLM-provided delegation
maxStepsat the sub-agent's owndefaultOptions.maxSteps. Previously a supervisor's model could silently raise a sub-agent's step budget past its configured default via the delegation tool's optionalmaxStepsargument. The supervisor's model can still reduce the budget, andonDelegationStart'smodifiedMaxSteps(developer code) still overrides the cap. A warning is logged when a value is capped. (#19529) -
Fixed
requestContexttyping inside a tool'sexecutecallback. It is now non-optional, matching runtime behavior: Mastra always provides aRequestContext(creating an empty one when the caller passed none), and whenrequestContextSchemais defined the context is validated beforeexecuteruns — on failure the tool returns a validation error andexecuteis never called. No more null-checks, throws, or tool factories needed to satisfy the compiler. (#19680)const tool = createTool({ id: 'fetch-doc', requestContextSchema: z.object({ documentId: z.string(), userId: z.string() }), execute: async (input, context) => { // Before: context.requestContext was RequestContext<...> | undefined, // forcing ?. / ! / throw even though the runtime guarantees it exists // After: typed as RequestContext<{ documentId: string; userId: string }> const documentId = context.requestContext.get('documentId'); // string }, });
Callers of
tool.execute(...)are unaffected — passing a context remains optional there. Fixes #19480 -
Fixed DurableAgent throwing ToolNotFoundError when a ToolSearchProcessor meta-tool (search_tools / load_tool) was called. The durable tool-call step now resolves processor-injected per-step tools, matching the regular Agent. (#19578)
-
Improve stored
runEvalsper-turn scores (follow-up to multi-turnturns). Each persisted per-turn scorer/gate result is now labeled with its turn index (metadata.turnIndex), carries the conversation's sharedthreadId, and links to that turn's own trace span instead of the item-level span. This lets the scores UI group and label per-turn scores by conversation and turn, and resolve each score to the correct trace. (#19491) -
Fixed unbounded memory growth during long goal runs. A goal run chains many agent turns inside one stream, and the stream previously kept every chunk, step, tool result, and text delta of the entire run in memory (and in suspend snapshots). Goal evaluations now carry the goal gate's explicit
shouldContinuedecision, and the stream clears its run-lifetime buffers at each continuing evaluation — the judged turn's messages are already persisted by then. Terminal evaluations (completion, waiting for user, judge failure, budget exhaustion) do not truncate, so the final turn is preserved. (#19663)Behavior change for goal runs: run-end results now cover the final iteration of the run (everything after the last continuing evaluation) instead of every turn. Streamed chunks, token usage totals, and persisted messages are unaffected — only the aggregates resolved at the end of the run change. Agents without a
goalconfig are unaffected.const agent = new Agent({ name: 'coder', model: 'openai/gpt-5.5', goal: { judge: 'openai/gpt-5-mini' }, }); const stream = await agent.stream('Implement feature X'); const output = await stream.getFullOutput(); // Before: output.text, output.steps, output.toolCalls, and output.toolResults // aggregated every turn of the goal run (e.g. 50 turns concatenated). // After: they cover the final iteration — the turn(s) judged by the terminal // evaluation, i.e. the goal's final answer. Use output.messages (or memory) // for the full conversation; output.totalUsage still spans the entire run.
-
Fixed native Agent goals continuing to invoke the judge after an unretried API error. (#19662)
-
Fixed
onIterationCompletecallbacks receiving emptytoolResultsafter successful tool execution. Tool results now include the matching call ID, tool name, and output from the completed iteration. Fixes #19453. (#19454) -
Fixed generated files from AI SDK v7 models (e.g. gpt-image-1 image output) being corrupted in stream output and saved message history. The tagged V4 file data is now converted to the flat shape Mastra's stream pipeline and message storage expect. This covers both
fileandreasoning-fileresponse parts. (#19430)Also fixed handling of URL-backed generated files: the URL is no longer mislabeled as base64 in file chunks, UI message streams now emit the URL directly instead of a broken data URI, and reading
.uint8Arrayon a URL-backed generated file now throws a descriptive error instead of returning garbage. -
Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. (#19849)
Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option.
Before
const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir };
After
const state = { factoryProjectId: 'factory-project-1', projectRepositoryId: 'project-repository-1', sandboxId, sandboxWorkdir, };
-
Fixed goal judges changing the parent agent's observational-memory model state. (#19710)
-
Fixed durable agent runs continuing after a tool call was denied by authorization. The run now fails immediately instead of letting the model retry the denied tool. (#19886)
-
Fixed answering an ask_user question failing with a misleading "could not find a suspended run" error when the suspended run died before its state was saved (for example when persisting the suspended snapshot failed). The unresumable question is now retracted, a late answer is ignored, and the original error is surfaced instead. (#19444)
-
Fixed prebuilt agent signals so callers can supply per-run request context. (#19702)
-
Fixed Code Mode executions failing when programs return large results or error messages. (#19878)
-
Aligned A2AAgent streams with the regular Agent stream contract. A2A streams now emit a leading start chunk on fresh runs (skipped when resuming, matching Agent behavior) and the finish chunk now carries the Agent-shaped payload (stepResult.reason, output.usage) so downstream consumers can treat sub-agent streams uniformly. The previous flat finishReason and usage fields are still included for backward compatibility but are deprecated. (#19517)
-
Updated the bundled provider SDKs used by the model router for Cerebras, DeepInfra, DeepSeek, Perplexity, Together AI, and OpenAI-compatible endpoints (including custom provider URLs and the Netlify gateway) to their AI SDK v6-compatible versions. This aligns them with the other providers in the model router (OpenAI, Anthropic, Google, Groq, Mistral, xAI, OpenRouter) which already use v6-compatible SDKs, and picks up upstream provider fixes. No changes to the public API: model strings like cerebras/llama-3.3-70b or deepseek/deepseek-chat keep working as before. Unused v5-track provider SDK bundles (Groq, Mistral, xAI, and the migrated providers above) were removed from the bundle. (#19495)
-
Updated embedding model routing to use the AI SDK v6 provider SDKs (OpenAI, Google, and OpenAI-compatible) while preserving the existing embedding interface. Existing embedding model configurations continue to work without code changes. (#19500)
-
Fixed workflow cancellation for tool-wrapped steps. Cooperative tools can now stop early when
run.cancel()is called. Fixes #19599. (#19602) -
Added a working directory override when deriving local sandboxes so callers can isolate each derived checkout. (#19907)
-
Add
jsonPromptInjection: 'auto'to select native structured output when model capability data confirms support and inline JSON prompt injection otherwise. Scorer judges use this automatic path by default while preserving explicitjsonPromptInjectionoverrides and fallback retries for unexpected provider failures. (#19442) -
Added
checkpointNameto sandbox derive options so providers can attach durable checkpoint identities to derived sandboxes. (#19907) -
Fixed dataset item writes to reject circular payloads with a clear validation error before storage, instead of failing with database-specific serialization errors. Silently lossy JSON values (nested
undefined, functions, symbols, bigints, and non-finite numbers) and non-plain objects (Date,Map,Set, class instances, and customtoJSON()objects) are now also rejected with the offending path, so identicalexternalIdretries can no longer conflict with the persisted payload. Added a publicsafeStringifyutility for serializing values that may contain circular references. (#19603)import { safeStringify, ensureSerializable } from '@mastra/core/utils/safe-stringify'; const value: Record<string, unknown> = { prompt: 'hello' }; value.self = value; safeStringify(value); // '{"prompt":"hello","self":"[Circular]"}' ensureSerializable(value); // { prompt: 'hello', self: '[Circular]' }
-
Remove leftover branches that selected the evented workflow engine inside the regular
Agentloop. The agentic-execution workflow now always uses the in-process workflow engine, andAgentno longer maintains an ephemeralMastra(with its own pubsub andstartWorkers()lifecycle) just to host the evented path. This removes a class of subtle behavior differences between the two engines fromAgent.stream()/Agent.generate()and simplifies the suspend/resume scope lifecycle. (#18666)No public API changes. The evented workflow infrastructure itself is unchanged and continues to be used by background tasks, notifications, the score-traces workflow, and other subsystems that explicitly opt into it.
-
Fixed
requestContexttyping in the dynamicskillsagent option. When an agent definesrequestContextSchema, theskillscallback now receives a typedRequestContext(with key autocomplete and typo checking), matchinginstructions,tools,memory, and the other dynamic options. Previously it was alwaysRequestContext<unknown>. (#19554)const agent = new Agent({ requestContextSchema: z.object({ documentId: z.string(), userId: z.string() }), // Before: requestContext was RequestContext<unknown> — any key compiled // After: requestContext is RequestContext<{ documentId: string; userId: string }> skills: ({ requestContext }) => { requestContext.get('documentId'); // typed as string return ['./skills/basic']; }, });
Fixes #19553
-
Fixed thread cloning to preserve configured memory state, including observational memory progress. (#19917)
-
Fix the
createDurableAgentJSDoc examples to constructRedisServerCachewith a connected Redis client ({ client }) instead of a{ url }config the class does not accept. (#19569) -
Added stable metrics and logs capability reporting for observability storage. The system packages response now includes
observabilityStorageCapabilitieswithmetricsandlogsflags, enabling capability-based detection that is resilient to bundler-generated constructor name changes. (#19305)const packages = await client.getSystemPackages(); console.log(packages.observabilityStorageCapabilities?.metrics); // true console.log(packages.observabilityStorageCapabilities?.logs); // true
Studio now uses the capability response instead of relying on constructor names, with a fallback for older servers.
-
Fixed background tasks never executing when Mastra is used as a library without a Mastra server. Previously, background-eligible tool calls (including background subagent delegations) were dispatched but stayed in the
runningstate forever because the task workers only started with a Mastra server (mastra devor a deployed server). Now the workers start automatically on the first background task dispatch or resume, so background tasks complete in apps that embed Mastra directly (for example Express or Next.js servers). Fixes #19339. (#19476) -
Fixed durable goal objectives so active execution time is preserved across runs and excludes tool approval waits. (#19837)
-
Fixed a crash on Cloudflare Workers where calling generate() on an Agent not registered to a Mastra instance threw
TypeError: this.#intervalHandle.unref is not a function. The scheduler now only calls unref() on its polling interval when the runtime provides it (Node.js); on runtimes where setInterval returns a number (workerd) the call is skipped. Fixes #19462. (#19471) -
Fixed composite storage initialization so transient failures can retry without restarting the process. (#19697)
-
Moved the server config routes and provider credential helpers into @mastra/factory as a reusable ConfigRoutes class. Route handlers now receive their auth checks through an injected RouteAuth seam and storage domains through constructor options, so hosts other than the Mastra Code web app can mount the same routes. (#19866)
-
Fixed workspace LSP support for TypeScript 7 projects. Code inspection (diagnostics, hover) now works in workspaces using TypeScript 7, including hoisted monorepo and pnpm installations. No configuration changes are needed — existing
lsp: truesetups keep working, and TypeScript 6 and earlier behavior is unchanged. (#19618)Fixes #19601
-
Fixed agents with channels failing on Cloudflare Workers with
No such module "chat". (#19570)Agents with Slack, Discord, or Telegram channels now deploy to Cloudflare Workers and other serverless targets without extra configuration — channels initialize correctly and webhooks work out of the box. The
bundler.dynamicPackages: ['chat']workaround is no longer needed for Node deploys. Projects that don't use channels still load the Chat SDK lazily.Fixes #19254
@mastra/acp@0.4.0
Minor Changes
-
Added a
createClientoption tocreateACPTool()andAcpAgentfor customizing the ACP client, and fixed a crash when the ACP agent command fails to start. (#19559)Custom ACP client (
createClient)Some ACP agents call custom extension methods (
extMethod/extNotification) on the client. The built-in client rejected these with a "Method not found" error and could not be replaced, so those agents were unusable without reimplementing the whole connection. The newcreateClientoption receives the default client and returns the client used for the connection:import { createACPTool } from '@mastra/acp'; const tool = createACPTool({ id: 'grok', description: 'Dispatch tasks to Grok', command: 'grok', createClient: defaultClient => Object.assign(defaultClient, { extMethod: async (method, params) => ({}), extNotification: async (method, params) => {}, }), });
The
Clienttype is now re-exported from@mastra/acpfor fully custom implementations.Spawn failure handling
If the configured
commandcould not be started (for example the executable does not exist), the child process 'error' event was unhandled and crashed the host process. Tool execution now rejects with the spawn error instead.Fixes #19109
Patch Changes
@mastra/ai-sdk@1.6.3
Patch Changes
-
Fixed the AI SDK v6 native approval flow in handleChatStream so approval responses are collected across all assistant messages instead of only the final message. Approving a tool call now resumes its exact run and tool call, multiple approval responses in one request resume sequentially in a single framed response stream, and already-resolved responses from history are safely skipped instead of executing again. (#19528)
-
Fix durable agent streams that could terminate right after the initial
startevent, leaving the client with an empty response. Streams now continue through step boundaries and deliver the full reasoning, tool, and text output. (#19671)
@mastra/apple-container@0.3.0
Minor Changes
-
Added
clone()support to the sandbox providers.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides foridandenv, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new E2BSandbox({ apiKey, template: 'base' }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
idleTimeoutMinutesis a best-effort hint that maps to each provider's native lifetime knob (RailwayidleTimeoutMinutes, E2B/Modal/Vercel timeout in milliseconds, DaytonaautoStopInterval, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
Patch Changes
@mastra/auth-better-auth@1.1.2
Patch Changes
-
Added a deferred instance mode and organization management to MastraAuthBetterAuth so it can be passed directly to a server host without a wrapper adapter. The provider can now be constructed with just a secret and will build its Better Auth instance (including running migrations) against the host database during init. It also bootstraps a personal organization for new users (ensureOrganization), checks organization admin roles (isOrganizationAdmin), and exposes the Better Auth HTTP handler (handleAuthRequest) so hosts can mount it under /auth/api/*. (#19765)
Before
import { betterAuth } from 'better-auth'; import { MastraAuthBetterAuth } from '@mastra/auth-better-auth'; const auth = new MastraAuthBetterAuth({ auth: betterAuth({/* ... */}) });
After (bring-your-own instance still works)
import { MastraAuthBetterAuth } from '@mastra/auth-better-auth'; const auth = new MastraAuthBetterAuth({ secret: process.env.BETTER_AUTH_SECRET! }); // host calls auth.init({ database, publicUrl, allowedOrigins }) during startup
-
Updated the minimum better-auth version to 1.6.13 to pull in the fix for a stored cross-site scripting vulnerability in better-auth's OIDC provider and MCP plugins (GHSA-86j7-9j95-vpqj). (#19584)
-
Fixed Better Auth bearer token authentication and implemented user lookup in
@mastra/auth-better-auth(fixes #19110). (#19629)Bearer tokens now work after credentials sign-in
signIn/signUpreturn Better Auth's raw session token, but Better Auth only accepts signed session cookies. Sending that token asAuthorization: Bearer <token>previously always failed authentication. The provider now signs unsigned tokens with the Better Auth secret before verifying the session — matching the semantics of Better Auth's bearer plugin. The session cookie name is also resolved from the Better Auth instance, so secure-cookie setups (__Secure-prefix) work too.getUser()andgetUsers()are now implementedPreviously
getUser()was a stub that always returnednull, breaking Studio user lookup and author enrichment. It now resolves users by ID through Better Auth's internal database adapter, andgetUsers()supports batch lookups.
@mastra/auth-studio@1.3.2
Patch Changes
-
MastraAuthStudionow automatically creates a personal organization for users who don't belong to one yet, and can check whether a user is an organization admin — matching the behavior already available inMastraAuthWorkosandMastraAuthBetterAuth. This lets hosts like a self-hosted MastraCode deployment authorize organization-level actions without users needing to manually set up an organization first. (#19858) -
MastraAuthStudio.ensureOrganizationnow dedupes concurrent bootstraps for the same user, so parallel tabs or requests for a brand-new sign-in no longer end up creating duplicate personal organizations. (#19858) -
Changed the default shared API URL for Studio auth from http://localhost:3010/v1 to https://platform.mastra.ai/v1, so deployed instances work against the production platform without extra configuration. Set MASTRA_SHARED_API_URL or the sharedApiUrl option to point at a different environment. Production cookie settings (Secure + Domain=.mastra.ai) are now only auto-enabled when the shared API URL is explicitly configured on .mastra.ai, keeping local development cookies host-only. (#19958)
@mastra/auth-workos@1.6.4
Patch Changes
- Added organization management and host integration to MastraAuthWorkos so it can be passed directly to a server host without a wrapper adapter. The provider now bootstraps a personal organization for new users (ensureOrganization), checks organization admin roles (isOrganizationAdmin), and resolves its redirect URI from the host public URL during init when not configured explicitly. (#19765)
@mastra/blaxel@0.6.0
Minor Changes
-
Added
clone()support to the sandbox providers.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides foridandenv, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new E2BSandbox({ apiKey, template: 'base' }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
idleTimeoutMinutesis a best-effort hint that maps to each provider's native lifetime knob (RailwayidleTimeoutMinutes, E2B/Modal/Vercel timeout in milliseconds, DaytonaautoStopInterval, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
Patch Changes
@mastra/braintrust@1.3.0
Minor Changes
-
Breaking change (#19507)
Updated the bundled Braintrust SDK from v2 to v3 and replaced the SDK-specific logger and span types with stable Mastra interfaces/shims. Compatible Braintrust v2 and v3 logger and span objects remain supported.
Braintrust v3 uses a separate W3C trace ID for
root_span_id. Mastra's returnedspanIdremains the Braintrust row ID and span ID for feedback and lookup.Migration
The
BraintrustExporterConfiginterface changed, so if you were using thebraintrustLoggerfield or thegetCurrentSpan()method, those now hold narrower types that are no longer tied to the Braintrust SDK. It is unlikely that this is affecting you but technically a breaking change, which is why it is outlined.Applications that independently upgrade Braintrust and use Nunjucks prompt templates should follow the Braintrust v2 to v3 migration guide: https://www.braintrust.dev/docs/sdks/typescript/migrations/v2-to-v3
Patch Changes
@mastra/client-js@1.33.0
Minor Changes
-
Added Agent Learning response types for entity discovery, snapshots, flows, theme details, examples, history, and noise. (#19871)
import type { ThemeDetailResponse, ThemeFlowResponse } from '@mastra/client-js';
-
Added a
requestContextoption to agent controller session methods in @mastra/client-js. You can now pass custom request context tosendMessage,steer,followUp,approveTool, andrespondToToolSuspension, matching whatagent.generate()already supports. The context is merged into the run's request context on the server, so it reaches dynamic instructions and tools. (#19531)const session = client.getAgentController('code').session('user-1'); await session.sendMessage('hello', { requestContext: { userId: 'user-1', tier: 'pro' } });
-
Added exact thread binding when creating AgentController sessions through the JavaScript client. (#19902)
-
Added request-scoped model overrides to agent execution and approvals, and fixed Studio model selection so each tab can use a different model without changing the agent's configured default. (#19749)
await agent.stream(messages, { model: 'google/gemini-2.5-flash' });
-
Added reconnect support to
AgentControllerSession.subscribe()so SSE subscriptions recover after proxy timeouts or transport errors. Fixes #19202. (#19560)await session.subscribe({ onEvent: event => { /* handle event */ }, reconnect: true, });
Patch Changes
-
Fixed AgentController live events and thread history returning serialized message timestamps instead of Date values. (#18783)
-
Fixed AgentControllerSession.subscribe() so it resolves only after the stream is established and rejects when it cannot connect (leaving no background retry loop). Reconnect now applies only after an established stream drops, backs off exponentially, and fires a new onReconnect callback on each re-established stream so consumers can re-sync missed events: (#19580)
const subscription = await session.subscribe({ onEvent: event => applyEvent(event), onError: error => showDisconnected(error), reconnect: { maxRetries: 5, delayMs: 1000, maxDelayMs: 30_000 }, onReconnect: async () => { // Events emitted while disconnected are not replayed — re-sync state. applyState(await session.state()); }, });
-
Updated generated route types so
memory.resourceis optional in agent generate and stream request bodies. The server can derive the resource ID from the authenticated user viamapUserToResourceId, so clients no longer need to provide it. Fixes #19518. (#19524) -
Replace
anywithunknownin generated route types so clients get real type errors instead of silently unchecked values. Route-types generation now deduplicates shared schemas into reusable type aliases, shrinking the generatedroute-types.generated.tsfrom ~94K to ~22K lines (77% smaller). The memory config response now typesworkingMemory(enabled, scope, template, schema, version) instead ofunknown. (#19573)If your code read fields from responses that were previously typed
any, TypeScript now requires you to narrow them before use:// Before: `metadata` was `any`, so this compiled even when unsafe const value = response.metadata.someField; // After: `metadata` is `unknown` — narrow it first const metadata = response.metadata; if (metadata && typeof metadata === 'object' && 'someField' in metadata) { const value = metadata.someField; } // ...or cast if you know the shape: (metadata as MyMetadata).someField
Code that reads
workingMemoryfrom the memory config response no longer needs casts —config.workingMemory?.enabled,scope,template,schema, andversionare now typed directly. -
Added stable metrics and logs capability reporting for observability storage. The system packages response now includes
observabilityStorageCapabilitieswithmetricsandlogsflags, enabling capability-based detection that is resilient to bundler-generated constructor name changes. (#19305)const packages = await client.getSystemPackages(); console.log(packages.observabilityStorageCapabilities?.metrics); // true console.log(packages.observabilityStorageCapabilities?.logs); // true
Studio now uses the capability response instead of relying on constructor names, with a fallback for older servers.
@mastra/code-sdk@1.0.0
Major Changes
-
Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. (#19849)
Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option.
Before
const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir };
After
const state = { factoryProjectId: 'factory-project-1', projectRepositoryId: 'project-repository-1', sandboxId, sandboxWorkdir, };
Minor Changes
-
Moved model packs in Mastra Code web to database-backed storage and refreshed the built-in packs. (#19849)
Model packs are now stored in the Factory database
When running with a Factory backend, custom model packs are saved in a new model-packs storage domain scoped to your organization instead of the local settings.json file. Local (non-tenant) mode keeps the file-backed behavior.
Pick from available models
The settings Model tab now loads the list of available models from a new /web/config/models endpoint, so the Factory default model picker and model pack editor only offer models you actually have credentials for. Model pickers are searchable comboboxes instead of plain dropdowns, and pack activation now resolves the correct scoped session so packs can be activated from settings.
Default packs updated to the latest model releases
- Anthropic: build and plan anthropic/claude-fable-5, fast anthropic/claude-haiku-4-5
- OpenAI: build and plan openai/gpt-5.6
- Observational memory default model is now google/gemini-3.5-flash
-
Added a Factory default model for server-backed Factories in Mastra Code web. Set it in Settings under the Model tab and every factory run (like issue triage) starts on that model. The Model tab now also hosts model packs, replacing the separate Packs tab — packs stay session-scoped while the default model is stored on the Factory project itself. (#19849)
-
Added an input processor extension for embedding surfaces while preserving Mastra Code's required processors. (#19702)
-
Added support for injecting pre-built storage and vector store instances into Mastra Code.
MastraCodeConfig.storagenow accepts aMastraCompositeStoreinstance in addition to a storage config, and the newMastraCodeConfig.vectorslot accepts aMastraVectorinstance. When an instance is provided it is used as-is — no connection test or LibSQL fallback — so hosted deployments can share a single Postgres connection pool between Mastra storage and application tables. (#19623)Before
await createMastraCode({ storage: { backend: 'pg', connectionString } });
After
const storage = new PostgresStore({ id: 'code-storage', connectionString }); const vector = new PgVector({ id: 'code-vectors', connectionString }); await createMastraCode({ storage, vector });
-
Add goal execution to the headless
runMCAPI. Goal runs use the same GoalManager and system-reminder signal path as the TUI and resolve on terminalgoal_evaluationevents without manual continuation messages. (#19441)const run = runMC({ controller, session, goal: { objective: 'Implement and verify the requested change', judgeModelId: 'openai/gpt-5-mini', maxRuns: 20, }, }); for await (const event of run) { console.log(event.type); } const result = await run.result;
-
Add browser-based OAuth authentication for HTTP MCP servers to Mastra Code. (#19467)
When an HTTP MCP server rejects a connection with an authorization error, the
/mcpselector now shows a "needs auth" badge and an Authenticate action.
Choosing it opens the provider's consent page in the browser and completes the
OAuth 2.1 authorization-code flow (PKCE + Dynamic Client Registration) over a
loopback callback server, persists the tokens, and reconnects — no manual
configuration required for a bare{ "url": ... }server entry. A Cancel
authentication action aborts an in-flight flow and returns the server to the
needs-auth state.The server manager gains
authenticateServer(name)and
cancelServerAuthentication(name),McpServerStatusgains an optional
needsAuthflag, and the OAuthredirectUrlin MCP server config is now
optional (it defaults to a stable loopback URL). The config also accepts
callbackPortas a shorthand that synthesizes
http://localhost:<callbackPort>/callback, the Claude Code / Codex
convention, so configs written for those clients (like Slack's official MCP
plugin config) work verbatim.callbackPortandredirectUrlare mutually
exclusive.const server = manager.getServerStatuses().find(s => s.name === 'supabase'); if (server?.needsAuth) { // Opens the consent page in the browser, completes the OAuth flow, and // resolves with the reconnected server status. const status = await manager.authenticateServer('supabase', { onAuthorizationUrl: url => openInBrowser(url), }); console.log(status.connected); // Abort an abandoned browser flow and return the server to needs-auth: // await manager.cancelServerAuthentication('supabase') }
-
Added step-based OAuth APIs for browser-driven provider sign-in and tenant-aware credential resolution. Hosted applications can now inject a credential store so each request resolves the caller's credentials without copying stored secrets into process environment variables. (#19638)
import { startAnthropicLogin } from '@mastra/code-sdk/auth/providers/anthropic'; const { url, verifier } = await startAnthropicLogin();
-
Added access to the workspace resolved for an AgentController session. (#19547)
Use the session-owned workspace when an operation must remain isolated to that session:
const session = await controller.createSession({ resourceId, scope }); const workspace = session.getWorkspace();
Mastra Code workspace resolvers can now accept an isolated read-only skill extension:
const workspace = await getDynamicWorkspace({ requestContext, skillExtension: { id: 'review-skills', paths: ['/__review_skills__'], createSource: fallback => new ReviewSkillSource(fallback), }, });
This lets SDK consumers compose additional read-only skill roots into selected workspaces without changing the default workspace skill set.
Patch Changes
-
Added on-disk verification to the update utilities:
runUpdatenow returns the package manager's stderr, and the newperformUpdatelocates the running install, delegates the update to the tool that owns it (for example vite-plus), verifies the on-disk version when available, and reports when a readable installed version remains unchanged. (#18792) -
Fixed Moonshot AI API key resolution so keys saved via /api-keys (MOONSHOT_API_KEY) work when selecting moonshot models (#19655)
-
Fixed provider request history repair so incompatible tool-call IDs are sanitized and retried instead of being blindly resent after a provider rejects the request (#19969)
-
Fixed goal duration so it persists across pauses and process restarts. (#19837)
-
Fixed session thread cloning failing with "Source thread not found" when the cached dynamic memory instance was bound to a previous storage instance. The memory cache is now scoped to the storage it was created with. (#19969)
-
Fixed Mastra Code retries for EPIPE and closed provider connections. (#19691) (#19692)
-
Fixed ACP clients dropping standalone signal messages such as system reminders and notification summaries, while preserving assistant text deltas across interleaved signals without inserting separators. (#18783)
-
Added a session notification when a GitHub plugin is automatically updated to its latest version (#19943)
const unsubscribe = pluginManager.onGithubPluginsUpdated(pluginNames => { console.log(`Updated plugins: ${pluginNames.join(', ')}`); }); // Call during shutdown. unsubscribe();
-
Moved custom model providers and custom model packs off settings.json in the factory web app: both now live in the app database (org-scoped rows in deployed mode, a sentinel local scope in no-auth mode). Custom providers saved in the web settings page are picked up by model resolution and the model catalog through a new pluggable custom-providers source in the SDK, so the gateway no longer reads the host machine's settings.json for them, and models from your custom providers appear in the web model pickers. (#19964)
Hosts that store custom providers elsewhere (like the factory's database) register a source at boot; when none is registered, the SDK keeps reading settings.json as before:
import { setCustomProvidersSource } from '@mastra/code-sdk/agents/custom-provider-source'; setCustomProvidersSource(tenant => (tenant ? snapshotForOrg(tenant.orgId) : []));
-
Fixed cloned session threads reading from a previous storage instance. The dynamic memory cache now invalidates when the storage or vector instance changes, so thread cloning always uses the current database. (#19966)
-
Added a memory-settings storage domain: observational memory settings (observer and reflector models, thresholds, attachment observation) changed in the web app are now stored in the app database — one row per user — instead of settings.json, and the settings page reads them back from the database. Factory-mounted agent controllers no longer seed observational memory settings from the host machine's settings.json (new
disableSettingsOmSeedSDK option), so server sessions start from built-in defaults plus whatever is stored in the database. The OM settings model pickers in the web UI are now searchable comboboxes. (#19964)Server embedders that persist memory settings in their own database can opt out of the settings.json seed:
import { createMastraCode } from '@mastra/code-sdk'; const mastraCode = await createMastraCode({ cwd: process.cwd(), // Don't seed observer/reflector models or thresholds from the host // machine's settings.json — sessions start from built-in defaults. disableSettingsOmSeed: true, });
-
Fixed Amazon Bedrock prompt caching for long Mastra Code conversations. (#19690)
-
Fixed a crash (
TypeError: Cannot read properties of undefined (reading 'includes')) when a Mastra store instance is injected into the SDK from a project whose dependency graph contains duplicate copies of @mastra/core. Injected stores are now detected structurally instead of withinstanceof, so stores built against a different core copy are recognized correctly instead of being mistaken for a storage config. (#20030)
@mastra/convex@1.4.0
Minor Changes
-
Added Observational Memory support to the Convex storage adapter. Agents using
ConvexStorecan now enableobservationalMemoryin@mastra/memory, including async observation buffering and reflections. (#19474)To enable it, add the new
mastraObservationalMemoryTableto your Convex schema and redeploy:import { defineSchema } from 'convex/server'; import { mastraThreadsTable, mastraMessagesTable, mastraResourcesTable, mastraObservationalMemoryTable, // ...other Mastra tables } from '@mastra/convex/schema'; export default defineSchema({ mastra_threads: mastraThreadsTable, mastra_messages: mastraMessagesTable, mastra_resources: mastraResourcesTable, mastra_observational_memory: mastraObservationalMemoryTable, // ...other Mastra tables });
Then run
npx convex deploy(ornpx convex dev) so the new table and storage operations are live. All observational memory writes run inside the deployed Convex storage mutation, so buffered-observation swaps and reflection generations are atomic.
Patch Changes
-
Improved type safety of the Convex storage layer. Table names accepted by the internal ConvexDB are now a closed union (core storage tables plus the observational memory table) instead of any string, so table-name typos are caught at compile time instead of silently routing records to the mastra_documents fallback table. The internal observational memory operations no longer take a table-name argument. (#19485)
-
Removed the Convex adapters' only Node builtin dependency. ConvexStore, the vector adapters, and the storage domains now use the Web Crypto API (crypto.randomUUID) instead of importing node:crypto, so @mastra/convex itself no longer requires the Node.js runtime ("use node") to bundle inside a Convex project. (#19498)
@mastra/daytona@0.6.0
Minor Changes
-
Added networking and bulk file upload support to
DaytonaSandbox: (#19577)- Public port URLs:
sandbox.networking.getPortUrl(port)returns the preview URL for a port, enabling preview URLs and deploys with the new@mastra/deployer-sandboxpackage. Passpublic: truefor tokenless URLs. - Bulk file upload:
sandbox.writeFiles(files)uploads multiple files in one call via the SDK's nativefs.uploadFiles(). - Detached lifecycle:
getPortUrl(),stop(), anddestroy()now work from a fresh process by looking up the existing sandbox by itsid, without starting a stopped sandbox first.
import { DaytonaSandbox } from '@mastra/daytona'; const sandbox = new DaytonaSandbox({ id: 'my-preview', public: true }); await sandbox.start(); const url = await sandbox.networking.getPortUrl(4111);
- Public port URLs:
-
Added
clone()support to the sandbox providers.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides foridandenv, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new E2BSandbox({ apiKey, template: 'base' }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
idleTimeoutMinutesis a best-effort hint that maps to each provider's native lifetime knob (RailwayidleTimeoutMinutes, E2B/Modal/Vercel timeout in milliseconds, DaytonaautoStopInterval, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
Patch Changes
@mastra/deployer@1.52.0
Minor Changes
-
Added automatic detection of Software Factory projects when the Mastra entry imports and constructs a
MastraFactorybinding. Detected projects receive amastra-project.jsonmanifest in.mastra/output/identifying the project type and UI asset location. (#19948)Use the lightweight public helper when project-type detection is needed before a full bundle:
import { analyzeEntryProjectType } from '@mastra/deployer/build'; const projectType = await analyzeEntryProjectType('./src/mastra/index.ts');
Patch Changes
-
Fixed Babel 8 compatibility for build-time transforms. (#19393)
-
Fixed "Error: ENOTDIR: not a directory, open '...chunk-XYZ.js/package.json'" errors being printed during
mastra buildwhen using custom bundler options withoutexternals: true. Package resolution no longer treats module files as directories when looking up dependency metadata, so builds run without these confusing (but harmless) errors in the output. (#19514) -
Added secure Agent Learning reads to local Studio development. Requests stay same-origin, and browser-supplied credentials and tenant scope are ignored. (#19759)
MASTRA_PLATFORM_ACCESS_TOKEN=<organization-api-key> \\ MASTRA_PROJECT_ID=<project-id> \\ mastra dev
-
Fixed Mastra development startup with Babel 8. (#19849)
@mastra/deployer-cloudflare@1.2.8
Patch Changes
- Fixed Babel 8 compatibility for build-time transforms. (#19393)
@mastra/deployer-sandbox@0.1.0
Minor Changes
-
New package: deploy a full Mastra server (including Studio) into any workspace sandbox that supports networking, and get a live public URL in seconds. Works with
@mastra/vercel,@mastra/e2b, and@mastra/daytonasandboxes. Built for ephemeral environments: instant previews, PR/CI smoke deploys, agent-built app verification, and multi-tenant untrusted instances. (#19577)Deploy from your Mastra config
import { Mastra } from '@mastra/core/mastra'; import { SandboxDeployer } from '@mastra/deployer-sandbox'; import { VercelSandbox } from '@mastra/vercel'; export const mastra = new Mastra({ deployer: new SandboxDeployer({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), }), });
Then run
mastra build— it bundles the project and deploys it into the sandbox in one step. Redeploys reuse the same sandbox and skip dependency installs when the install inputs (package.json, bundled lockfiles, and the install command) are unchanged.Manage the deployment
The sandbox name is the identity —
getDeployment()retrieves the deployment from any process or codebase, without importing the Mastra project:import { getDeployment } from '@mastra/deployer-sandbox/client'; import { VercelSandbox } from '@mastra/vercel'; const dep = await getDeployment({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), }); // never wakes a stopped sandbox await dep.stop(); // snapshot-stop (resumable) await dep.destroy(); // permanent delete
Deploy programmatically (CI / agents)
import { deployToSandbox } from '@mastra/deployer-sandbox'; import { VercelSandbox } from '@mastra/vercel'; const sandbox = new VercelSandbox({ sandboxName: 'ci-preview', ports: [4111] }); const deployment = await deployToSandbox({ sandbox, dir: '.mastra/output' }); console.info(deployment.url);
Resolve and route at runtime
The server-only
@mastra/deployer-sandbox/clientexport includesgetDeployment()to resolve the current URL and manage the deployment (stop(),destroy(),logs(), with optional wake-on-demand), pluscreateSandboxHandler()andcreateSandboxProxy()helpers to serve a sandbox behind a stable URL on your own domain.
Patch Changes
@mastra/docker@0.5.0
Minor Changes
-
Added
clone()support to the sandbox providers.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides foridandenv, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new E2BSandbox({ apiKey, template: 'base' }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
idleTimeoutMinutesis a best-effort hint that maps to each provider's native lifetime knob (RailwayidleTimeoutMinutes, E2B/Modal/Vercel timeout in milliseconds, DaytonaautoStopInterval, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
Patch Changes
@mastra/dynamodb@1.1.3
Patch Changes
-
Fixed
listMessages()silently truncating large threads, returning incorrect pagination metadata, and failing to retrieve included-message context beyond the first DynamoDB page. Large threads now return complete paginated results with correcttotalandhasMore, andincludelookups (e.g. jumping to a message deep in a thread) again return the requested surrounding context instead of partial or empty windows. (#19562) -
Fixed a TypeScript build error in the DynamoDB store where date range filtering in listMessages failed strict null checks (#19567)
-
Fixed
listMessagespagination sohasMoreisfalsewheninclude(e.g.withNextMessages/withPreviousMessages) already returns every message in the queried thread(s). PreviouslyhasMorewas derived fromoffset + perPage < totalonly, so it stayedtrueeven when the included context completed the full set — matching the behavior of the pg store. (#19371) -
Fixed
listMessagesreportinghasMore: falsetoo early whenincludecontext messages fall outside the activeresourceIdordateRangefilters. Only messages matching the filters now count toward the returned total, so later filter-matching pages are no longer hidden. (#19576)
@mastra/e2b@0.7.0
Minor Changes
-
Added networking and bulk file upload support to
E2BSandbox: (#19577)- Public port URLs:
sandbox.networking.getPortUrl(port)returns the public URL for a port, enabling preview URLs and deploys with the new@mastra/deployer-sandboxpackage. - Bulk file upload:
sandbox.writeFiles(files)uploads multiple files in one call. - Snapshot-stop:
stop()now pauses the sandbox immediately (snapshotting filesystem, memory, and running processes) instead of leaving it running until its timeout. The nextstart()with the sameidresumes it, with background processes still running.destroy()still kills the sandbox permanently. - Detached lifecycle:
getPortUrl(),stop(), anddestroy()now work from a fresh process by looking up the existing sandbox by itsidmetadata, without resuming a paused sandbox. Stopping or destroying never wakes (or bills) a paused sandbox first.
import { E2BSandbox } from '@mastra/e2b'; const sandbox = new E2BSandbox(); await sandbox.start(); const url = await sandbox.networking.getPortUrl(4111);
- Public port URLs:
-
Added
clone()support to the sandbox providers.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides foridandenv, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new E2BSandbox({ apiKey, template: 'base' }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
idleTimeoutMinutesis a best-effort hint that maps to each provider's native lifetime knob (RailwayidleTimeoutMinutes, E2B/Modal/Vercel timeout in milliseconds, DaytonaautoStopInterval, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
Patch Changes
@mastra/evals@1.6.0
Minor Changes
-
Added context-recall LLM scorer that evaluates how well retrieved context covers the claims in a ground-truth reference answer. Complements the existing context-precision scorer by measuring retrieval completeness rather than relevance ranking. (#19733)
import { createContextRecallScorer } from '@mastra/evals/scorers/prebuilt'; const scorer = createContextRecallScorer({ model: 'openai/gpt-5-mini', options: { context: [ 'Einstein was born on 14 March 1879 in Ulm, Germany.', 'Einstein developed the theory of special relativity in 1905.', ], }, });
Patch Changes
@mastra/factory@0.1.0
Minor Changes
-
Move the Factory project CRUD and source-control connection routes into
@mastra/factoryas aProjectRoutesclass. The routes take their storage handles (FactoryProjectsStorage,SourceControlStorage), the allowed version-control integration ids, and aRouteAuthadapter at construction time, replacing the oldProjectDomainthat resolved domains through theFactoryStorageregistry. The now-unusedFactoryDomainbase class was removed from the web host. (#19866) -
Move the audit domain, agent git-action auditing, intake capabilities, and intake routes into
@mastra/factory.AuditDomainnow takes its storage handles (AuditStorage,FactoryProjectsStorage) and aRouteAuthadapter directly instead of resolving them through the factory storage registry, fans out to pluggableAuditSinks, and resolves agent tenants through an injectedagentTenantcallback. Intake routes ship as anIntakeRoutesclass that callsIntakeStoragedirectly (the intermediate intake store module was removed). (#19866) -
Added autonomous first-pass skills to the Software Factory. Work items now get an automatic investigation, planning, or review pass as soon as they enter the matching board column — no human input needed mid-run: (#20058)
- factory-triage runs when an issue enters triage: it investigates the issue, diagnoses the root cause, and requests a move to planning (or done if the issue should be closed).
- factory-plan runs when an item enters planning: it produces a phased implementation plan and requests a move to execute.
- factory-review runs when a pull request enters review: it reviews the changes, posts a verdict, and requests completion.
Instead of stopping to ask questions, the skills decide and record each decision as an assumption, batching assumptions and genuinely-human questions into one terminal handoff message. The superseded interactive skills (understand-issue, understand-pr) were removed.
-
Move the
FactoryIntegrationcontract and the OAuthstatesigner into@mastra/factory. The integration interface (routes, tools, diagnostics, intake/version-control capabilities,IntegrationContext) now lives at@mastra/factory/integrations/base, andcreateStateSigner/StateSignerat@mastra/factory/state-signing, so integrations can be implemented against the package without importing the web host. (#19866) -
Added the @mastra/factory package. It now owns the Software Factory storage domains (projects, work items, intake, audit, credentials, integrations, model packs, queue health, source control) that previously lived inside the mastracode web app, so they can be reused outside the web server. (#19866)
-
Moved the server config routes and provider credential helpers into @mastra/factory as a reusable ConfigRoutes class. Route handlers now receive their auth checks through an injected RouteAuth seam and storage domains through constructor options, so hosts other than the Mastra Code web app can mount the same routes. (#19866)
-
Move the Factory work-item (kanban board) routes into
@mastra/factoryas aWorkItemRoutesclass. The routes take their storage handles (WorkItemsStorage,FactoryProjectsStorage,QueueHealthStorage), anAuditEmitter, and aRouteAuthadapter at construction time. The request-body validators (parseCreateWorkItem,parseUpdateWorkItem) now live with the routes, the pass-through work-item store module was removed in favor of callingWorkItemsStoragedirectly, andcomputeFactoryMetricstakes a single object parameter. (#19866)
Patch Changes
-
Move the WorkOS audit integration into
@mastra/factory/integrations/workos. Its Admin Portal route now resolves the caller through theRouteAuthseam onIntegrationContextinstead of web-host auth helpers, and@mastra/auth-workosbecomes a package dependency. (#19866) -
Move the factory auth module into
@mastra/factory/auth. The provider-neutral (#19866)
auth gating (mountFactoryAuth,buildAuthRoutes,createFactoryAuthGate),
theRouteAuthimplementation (createFactoryRouteAuth), and the WorkOS/SSO
helpers now live next to the route seam they implement, with factory naming
throughout. -
The Factory's default
publicUrlis nowhttp://localhost:4111(the Factory server, which serves both the UI and the API) instead ofhttp://localhost:5173. Generated Factory projects now run from a single server, so OAuth callback URLs and auth redirects derived frompublicUrlpoint at the right origin out of the box. If you serve the SPA from a separate origin (for example a Vite dev server on :5173), setpublicUrl(orMASTRACODE_PUBLIC_URL) explicitly. (#20036) -
Factory board now picks up new GitHub/Linear intake automatically (gentle 30s poll) and refreshes work-item positions immediately when the tab regains focus, instead of requiring a manual page reload (#20071)
-
Fixed GitHub PATs saved in Settings not taking effect for the gh CLI in already-running Factory sessions until the server was restarted (#20069)
-
Forwarded closed Platform GitHub event-log deliveries into Factory governance before dispatching repository subscriptions, and kept default GitHub rules from auto-starting issues or pull requests created before the Factory. (#19988)
-
Track per-stage automation in Factory metrics. Stage history now stamps the exiting actor (
exitedBy) alongside the entering one,isAutomationActorclassifies rules-engine, agent (agent:*), and webhook (github:*) actors as automation, andcomputeFactoryMetricsreports astageAutomationbreakdown per stage: how many passes were fully automated (entered and exited by automation on the first visit) and how those automated passes ended up (done,canceled,reworked, or still in flight). Adds thecanceledterminal stage to the board vocabulary (FACTORY_RULE_STAGES) — a tracked non-completion that feeds neither throughput nor cycle time — and rewords organization-required errors to be auth-provider neutral. (#19844) -
Fixed @mastra/factory build output so published modules use explicit .js import extensions and resolve correctly under Node ESM (#19954)
-
Deployed factories now authenticate API and Studio requests with the same provider, so Studio sessions work without extra configuration. (#19966)
-
Fixed Factory metrics windowing to use inclusive UTC calendar days. Date-only
from/tobounds now include both selected days, an item completing at the current instant is counted in today's throughput (previously it could be dropped on the window's exclusive edge), andwindowDaysreflects the number of gap-filled day buckets. Cards feed the source mix only when created inside the window. (#19971) -
Fixed duplicate repositories in Factory source control settings. (#19971)
-
Move the API-surface assembler from mastracode/web into @mastra/factory as
routes/surface—assembleWebApiRoutesis nowassembleFactoryApiRoutesandWebApiRoutesDepsis nowFactoryApiRoutesDeps. The module composes fs/config/oauth/skills/intake/work-item routes plus every registered integration's route surface (with disabled-status stubs for absent github/linear integrations) from explicitly threaded dependency handles. (#19866) -
Move the GitHub integration and the sandbox fleet into
@mastra/factory. The fleet is now a DI-constructedSandboxFleetclass (@mastra/factory/sandbox/fleet) that owns provisioning, reattach, teardown, idle windows, and per-replica budgets instead of reading a seeded runtime-config registry. The GitHub routes, webhook, sandbox materialization, project locks, and session subscriptions (@mastra/factory/integrations/github) resolve tenants through theRouteAuthseam and receive the fleet and factory storage viaIntegrationContext, so the web host no longer exportsgetSeededSandbox/getSeededGithubIntegrationservice locators. (#19866) -
Move the filesystem routes (
@mastra/factory/routes/fs) and skill routes (@mastra/factory/routes/skills) into@mastra/factory. The skill prepare/invoke routes are now aSkillRoutesclass that resolves users and tenants through theRouteAuthseam instead of web-host auth helpers. Diagnostics fields exposed by the GitHub and Linear integrations renamewebAuthEnabledtofactoryAuthEnabledto match the package's auth seam naming. (#19866) -
Moved custom model providers and custom model packs off settings.json in the factory web app: both now live in the app database (org-scoped rows in deployed mode, a sentinel local scope in no-auth mode). Custom providers saved in the web settings page are picked up by model resolution and the model catalog through a new pluggable custom-providers source in the SDK, so the gateway no longer reads the host machine's settings.json for them, and models from your custom providers appear in the web model pickers. (#19964)
Hosts that store custom providers elsewhere (like the factory's database) register a source at boot; when none is registered, the SDK keeps reading settings.json as before:
import { setCustomProvidersSource } from '@mastra/code-sdk/agents/custom-provider-source'; setCustomProvidersSource(tenant => (tenant ? snapshotForOrg(tenant.orgId) : []));
-
Fixed cloned session threads reading from a previous storage instance. The dynamic memory cache now invalidates when the storage or vector instance changes, so thread cloning always uses the current database. (#19966)
-
Added a memory-settings storage domain: observational memory settings (observer and reflector models, thresholds, attachment observation) changed in the web app are now stored in the app database — one row per user — instead of settings.json, and the settings page reads them back from the database. Factory-mounted agent controllers no longer seed observational memory settings from the host machine's settings.json (new
disableSettingsOmSeedSDK option), so server sessions start from built-in defaults plus whatever is stored in the database. The OM settings model pickers in the web UI are now searchable comboboxes. (#19964)Server embedders that persist memory settings in their own database can opt out of the settings.json seed:
import { createMastraCode } from '@mastra/code-sdk'; const mastraCode = await createMastraCode({ cwd: process.cwd(), // Don't seed observer/reflector models or thresholds from the host // machine's settings.json — sessions start from built-in defaults. disableSettingsOmSeed: true, });
-
Move the Linear integration into
@mastra/factory/integrations/linear.LinearIntegrationnow owns the full connection lifecycle (OAuth token exchange, single-flight refresh, scope checks, and connection caching) as class methods, the routes and agent tools resolve tenants through theRouteAuthseam instead of web-host auth imports, and thegetSeededIntegrationruntime-config indirection is gone — the host hands the integration instance and storage handles directly viainitialize(). (#19866) -
Fixed Factory automation so polled GitHub events reach governance rules, authenticated sessions start with the correct ownership, and board moves reliably notify active or idle agents. (#19979)
-
Move the
MastraFactoryassembly root into@mastra/factory.factory-entry.tsnow lives at the package root export (@mastra/factory), alongside the extractedworkspace,spa-static,server-error, andsandbox/reattachhelpers. Factory skills ship with the package and are copied into deploy output via the consuming app's build script. (#19866) -
Fixed web chat sessions getting stuck in a "Connection lost — reconnecting…" loop while the session workspace was still starting up (#20067)
-
Fixed a server startup crash when the factory's storage backend could not be recognized by the SDK. The factory now tells the SDK explicitly whether its Mastra store is Postgres or LibSQL, so agent state wiring works even when the project's dependency graph contains duplicate copies of Mastra packages. (#20030)
@mastra/hono@1.5.8
Patch Changes
-
Fixed agent replies from channels being silently dropped on Cloudflare Workers. (#19299)
The bot would receive a message and the webhook returned
200 OK, but no reply was posted and nothing was logged. Channel replies run after the webhook responds, and on Cloudflare Workers that remaining work was being dropped once the response was sent. It is now kept running to completion, so the reply is delivered. (#19285)
@mastra/inngest@1.8.3
Patch Changes
-
Fixed Inngest TypeScript build failures for workflows whose steps use typed request context. (#19225)
-
Fixed nested Inngest workflows to resume the suspended child step when only the parent workflow step is provided. (#19752)
@mastra/libsql@1.17.0
Minor Changes
-
Added LibSQLFactoryStorage for persisting Mastra agent state and lifecycle-managed application domains through one LibSQL connection. (#19681)
import { LibSQLFactoryStorage } from '@mastra/libsql'; const storage = new LibSQLFactoryStorage({ url: 'file:mastra.db' }); await storage.init();
Patch Changes
-
Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. (#19849)
Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option.
Before
const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir };
After
const state = { factoryProjectId: 'factory-project-1', projectRepositoryId: 'project-repository-1', sandboxId, sandboxWorkdir, };
-
Fixed an uncaught
RangeError: Maximum call stack size exceededwhen writing deeply-nested values (such as a long chainedError.cause) to LibSQL storage.safeStringifynow caps its recursion depth and substitutes a"[Max depth exceeded]"sentinel instead of overflowing the stack. (#19610) -
Fix factory storage error classification and schema drift handling. (#20002)
isUniqueViolationin the LibSQL factory storage now only matches real unique-index violations (SQLITE_CONSTRAINT_UNIQUE/SQLITE_CONSTRAINT_PRIMARYKEY). Previously anySQLITE_CONSTRAINTfailure — including NOT NULL violations — was reported as a "Unique constraint violation", which masked the real error (seen asUnique constraint violation on collection 'factory_rule_evaluations'when polled ingestion inserted nullable columns into a stale table).ensureCollectionsin both the LibSQL and Postgres factory storage adapters now relaxes stale NOT NULL constraints when a column becomes nullable in the schema. Postgres usesALTER COLUMN ... DROP NOT NULL; LibSQL rebuilds the table in place since SQLite cannot drop NOT NULL directly. Existing rows and unique indexes are preserved.
@mastra/mcp@1.15.0
Minor Changes
- Complete the OAuth authorization-code loop in MCPClient for HTTP MCP servers. Connections rejected with an authorization error now surface a
needs-authstate (readable viagetServerAuthState()), and the newauthenticate(serverName)method runs the interactive flow end to end: it captures the authorization code on a local loopback callback server (exported ascreateOAuthCallbackServerfor hosts with custom redirect handling), exchanges it for tokens, and reconnects. (#19466)
Patch Changes
- Fixed MCP clients getting stuck after a failed reconnect to streamable-HTTP-only servers.
listTools(),callTool(), andforceReconnect()now work once the server is reachable again. Fixes #19862 (#19894)
@mastra/memory@1.23.1
Patch Changes
-
Fixed observational memory aborting the agent run when OpenRouter injects a transient provider error into the response stream (e.g. "JSON error injected into SSE stream" with google/gemini-2.5-flash). These mid-stream errors carry the HTTP status on a numeric code property and are now recognized as retryable, so the observer retries with backoff instead of failing the turn. (#19640)
-
Updated the async-buffering compatibility error to include
@mastra/convexin the list of storage adapters that support Observational Memory. (#19474)
@mastra/modal@0.4.0
Minor Changes
-
Added
clone()support to the sandbox providers.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides foridandenv, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new E2BSandbox({ apiKey, template: 'base' }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
idleTimeoutMinutesis a best-effort hint that maps to each provider's native lifetime knob (RailwayidleTimeoutMinutes, E2B/Modal/Vercel timeout in milliseconds, DaytonaautoStopInterval, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
Patch Changes
@mastra/mongodb@1.14.0
Minor Changes
-
Added a
filterFieldsoption toMongoDBVector.createIndex(). Declaring the metadata fields you filter on lets Mastra register them as native filter fields in the Atlas vectorSearch index, so filtered queries are pushed straight into$vectorSearchinstead of first materialising matching document_ids. This removes the 16 MB BSON ceiling that previously capped metadata-filtered queries at roughly 342,000 matching documents. (#19006)await vectorStore.createIndex({ indexName: 'my-index', dimension: 1536, metric: 'cosine', filterFields: ['category', 'tenant_id'], });
Queries that filter only on declared fields (using operators Atlas Vector Search supports) take the fast path automatically. Filters that reference an undeclared field, or use an unsupported operator, keep working through the existing pre-filter.
Patch Changes
- Fixed
MongoDBVector.createIndex()leaving index setup incomplete. Previously, when the vector search index already existed, the call silently skipped creating the companion full-text search index. Repeated or interruptedcreateIndex()calls now finish creating the remaining search index instead of leaving setup incomplete. (#19006)
@mastra/observability@1.16.2
Patch Changes
-
Fixed cost estimates for recently added provider models, cached input tokens, and long-context requests. (#19830)
-
Fixed cost estimates when AI SDK provider names differ from embedded pricing data, including Vercel AI Gateway and Google Vertex. (#19513)
@mastra/pg@1.17.0
Minor Changes
-
Added PgFactoryStorage for persisting Mastra agent state and lifecycle-managed application domains through one PostgreSQL pool. (#19681)
import { PgFactoryStorage } from '@mastra/pg'; const storage = new PgFactoryStorage({ connectionString: process.env.DATABASE_URL! }); await storage.init();
Patch Changes
-
Fixed a missing semicolon in the SQL generated by exportSchemas(). The CREATE INDEX statement for the favorites table ran into the next CREATE TABLE statement, so applying the exported schema as a single query (for example with pg's client.query() or a Supabase migration) failed with a Postgres syntax error. Fixes #19942 (#19944)
-
Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. (#19849)
Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option.
Before
const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir };
After
const state = { factoryProjectId: 'factory-project-1', projectRepositoryId: 'project-repository-1', sandboxId, sandboxWorkdir, };
-
Fixed an issue where the process could crash when Postgres drops an idle database connection (e.g., due to a backend restart, failover, or network failure). (#19469)
Previously, a dropped idle connection caused an uncaught exception that terminated the process. Now the connection drop is caught and logged as a warning, and the store automatically reconnects on the next database operation.
User-provided connection pools are unaffected and keep their existing error handling.
-
Fix factory storage error classification and schema drift handling. (#20002)
isUniqueViolationin the LibSQL factory storage now only matches real unique-index violations (SQLITE_CONSTRAINT_UNIQUE/SQLITE_CONSTRAINT_PRIMARYKEY). Previously anySQLITE_CONSTRAINTfailure — including NOT NULL violations — was reported as a "Unique constraint violation", which masked the real error (seen asUnique constraint violation on collection 'factory_rule_evaluations'when polled ingestion inserted nullable columns into a stale table).ensureCollectionsin both the LibSQL and Postgres factory storage adapters now relaxes stale NOT NULL constraints when a column becomes nullable in the schema. Postgres usesALTER COLUMN ... DROP NOT NULL; LibSQL rebuilds the table in place since SQLite cannot drop NOT NULL directly. Existing rows and unique indexes are preserved.
-
Added stable metrics and logs capability reporting for observability storage. The system packages response now includes
observabilityStorageCapabilitieswithmetricsandlogsflags, enabling capability-based detection that is resilient to bundler-generated constructor name changes. (#19305)const packages = await client.getSystemPackages(); console.log(packages.observabilityStorageCapabilities?.metrics); // true console.log(packages.observabilityStorageCapabilities?.logs); // true
Studio now uses the capability response instead of relying on constructor names, with a fallback for older servers.
@mastra/platform-workspace@0.2.0
Minor Changes
-
Renamed the environment variable read by PlatformSandbox and PlatformFilesystem for platform authentication from MASTRA_PLATFORM_ACCESS_TOKEN to MASTRA_PLATFORM_SECRET_KEY. The old variable still works as a deprecated fallback. (#19932)
-
Added
clone()support toPlatformSandbox.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (access token, project, environment, network isolation, timeout, instructions, env, idle timeout) with per-instance overrides forid,sandboxId,env, andidleTimeoutMinutes, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19647)const template = new PlatformSandbox({ accessToken, projectId, environmentId, }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
This brings
PlatformSandboxup to parity with the other sandbox providers (@mastra/railway,@mastra/e2b,@mastra/daytona,@mastra/modal,@mastra/docker,@mastra/blaxel,@mastra/apple-container,@mastra/vercel) so it can be used withMastraFactoryfleets and the MC Web factory.
Patch Changes
-
PlatformSandboxnow includes its caller-facingidon thePOST /v1/projects/:projectId/sandboxwire body when provisioning a new sandbox. The Mastra Platform treats this as an advisory recovery key so callers can opt into checkpoint-based sandbox recovery — if the platform recognizes the id from a previous session, the new sandbox boots from the most recent checkpoint instead of the base template. Unknown ids fall through to a fresh sandbox, so existing callers see no change in behavior. (#19648)No API changes — the value sent is the same
idyou already pass tonew PlatformSandbox({ id })(or the auto-generated one).
@mastra/playground-ui@42.0.0
Minor Changes
-
Added ClampedText, a design-system component that clamps text to a number of lines and shows a "Read more" toggle only when the clamp actually cuts content. Detection is based on the rendered layout (element measurement, re-checked on resize and after fonts load), not on character count. (#19565)
-
Added reusable Ask User and Task List components for agent interfaces. (#19624)
-
Added separate Sankey node identity, display label, display value, and layout weight accessors, plus constrained labels with full hover text. Stable layout weights can now keep node positions fixed while current record weights animate bar and ribbon sizes. (#19871)
<Sankey data={records} columns={columns} getRecordNodeId={(record, column) => String(record[`${column.id}Id`])} getRecordNodeLabel={(record, column) => String(record[`${column.id}Label`])} getRecordNodeValue={(record, column) => Number(record[`${column.id}Count`])} getRecordWeight={record => Number(record.count)} getRecordLayoutWeight={record => Number(record.windowMaxCount)} > <SankeyChart /> </Sankey>
-
Added compact sizing, custom interactive elements, and trailing actions to sidebar navigation items. (#19959)
<MainSidebar.NavLink // Compact density for dense lists size="sm" // Bring your own interactive element (e.g. a router Link or button) render={<Link to="/sessions/feature-work">Feature work</Link>} // Trailing control rendered beside the row, independently clickable action={ <Button size="icon-sm" variant="ghost" onClick={onDelete}> <TrashIcon /> </Button> } />
-
Added optional Sankey node activation with mouse and keyboard support, including per-node eligibility. (#19896)
<SankeyChart onNodeClick={({ column, value }) => openDrilldown(column.id, value)} isNodeClickable={({ value }) => drillableNodeIds.has(value)} />
-
Added a
revealScrollbarOnHoverprop toScrollArea. Set it tofalseto keep the overlay scrollbar hidden until the user actively scrolls, instead of also revealing it when the pointer hovers the area. Defaults totrue, so existing usage is unchanged. (#20037)<ScrollArea revealScrollbarOnHover={false}>{content}</ScrollArea>
-
Added a borderless surface appearance, compact content density, and a dedicated card link component with native link semantics, plus hover, focus, and active states for interactive cards. (#19747)
-
Added a composable Sankey chart with deterministic colors, gradient ribbons, connected hover feedback, percentage labels, configurable margins, and opt-in column palettes. Redesigned Signals around Agent Learning theme-flow analysis with weighted graph metrics, shared signal colors, compact distribution summaries, and a guided trace-to-signal onboarding pipeline when usable data is not yet available. Fixed the empty state to match Sankey colors and emphasize the Mastra Engine stage. (#19472)
Patch Changes
-
Added a disabled prop to DataList.SelectCell so rows can block checkbox toggling while an async selection save is pending. (#20038)
-
Fixed the ClampedText read-more button to announce its expanded state to screen readers, and fixed clamp measurement so font-load re-measure and effect cleanup still run in browsers without ResizeObserver. (#19582)
-
Fixed composer inputs to show one scrollbar for long messages. Added a
maxHeightprop for configuring the scrolling viewport. (#19876) -
Exported the shared toast API through the Toaster component entry point. (#19876)
-
Improved breadcrumb navigation with clearer hover and pressed feedback for clickable path segments. (#19840)
-
Added reusable Composer compound components for building controlled chat inputs. (#19470)
-
Fixed named form fields adding a second page scrollbar inside app scroll containers. (#19617)
-
Improved sidebar density and prevented date range timelines from clipping edge labels. (#19972)
-
Removed interaction hints from the date range timeline. (#19987)
-
Fixed hidden search labels so search fields are announced correctly by assistive technologies. (#19840)
@mastra/posthog@1.2.0
Minor Changes
-
Added feedback export to the PostHog observability exporter. Feedback recorded with
addFeedback()now flows to PostHog as native$ai_feedbackevents and appears as "User feedback" on the linked trace, alongside the traces the exporter already sends. No configuration changes are needed. (#19922)const trace = await mastra.observability.getRecordedTrace({ traceId }); await trace.addFeedback({ feedbackType: 'thumbs', value: 'down', comment: 'Wrong answer', }); // The PostHog exporter now forwards this as a $ai_feedback event
Fixes #19893
Patch Changes
@mastra/rag@2.4.2
Patch Changes
-
Added a typed
turbopufferentry to thedatabaseConfigoption ofcreateVectorQueryTool. This lets you set the Turbopuffer consistency level for vector queries. (#19627)const vectorTool = createVectorQueryTool({ vectorStoreName: 'turbopuffer', indexName: 'documents', model: embedModel, databaseConfig: { turbopuffer: { consistency: 'eventual', // lower latency, recently written data may not be visible yet }, }, });
@mastra/railway@0.4.0
Minor Changes
-
Added
clone()support to the sandbox providers.clone()constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides foridandenv, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). (#19616)const template = new E2BSandbox({ apiKey, template: 'base' }); const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start();
idleTimeoutMinutesis a best-effort hint that maps to each provider's native lifetime knob (RailwayidleTimeoutMinutes, E2B/Modal/Vercel timeout in milliseconds, DaytonaautoStopInterval, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
Patch Changes
- Allowed derived Railway sandboxes to override the checkpoint name used for checkpoint-backed creation and refresh. (#19907)
@mastra/react@1.3.0
Minor Changes
-
Added request-scoped model overrides to agent execution and approvals, and fixed Studio model selection so each tab can use a different model without changing the agent's configured default. (#19749)
await agent.stream(messages, { model: 'google/gemini-2.5-flash' });
Patch Changes
-
Fixed binary file attachments missing during live streaming in
useChatwhenenableThreadSignalsis enabled. Attachments sent asUint8ArrayorArrayBuffernow render in the optimistic pending bubble and stay visible after the signal echo, without requiring a page refresh. (#19439) -
Fix type errors in
useChatsend-message flow after generated route body types tightenedrequestContextfromanytoRecord<string, unknown>. (#19573)
@mastra/server@1.52.0
Minor Changes
-
Added request-scoped model overrides to agent execution and approvals, and fixed Studio model selection so each tab can use a different model without changing the agent's configured default. (#19749)
await agent.stream(messages, { model: 'google/gemini-2.5-flash' });
-
Added exact thread binding to the AgentController session creation endpoint. (#19902)
Patch Changes
-
Fixed agent generate and stream requests being rejected with a 400 error when
memory.resourcewas omitted but server auth was configured withmapUserToResourceId. The request body schema requiredmemory.resourceeven though the server derives the resource ID from the authenticated user and overrides any client-provided value. (#19524)Clients no longer need to send a placeholder resource ID:
{ "messages": ["what was my last message?"], "memory": { "thread": "test-thread" } }If a request uses memory and neither the body nor the authenticated request context provides a resource ID, the server now returns a clear 400 error. Fixes #19518.
-
Prevented redundant agent controller thread switches from interrupting active responses. (#19333)
-
Fixed agent replies from channels being silently dropped on Cloudflare Workers. (#19299)
The bot would receive a message and the webhook returned
200 OK, but no reply was posted and nothing was logged. Channel replies run after the webhook responds, and on Cloudflare Workers that remaining work was being dropped once the response was sent. It is now kept running to completion, so the reply is delivered. (#19285) -
Fixed durable-agent check in recover route (#19632)
-
Fixed agent controller session routes to use the active request context when creating or resolving sessions. (#19642)
-
Fixed dataset item endpoints to return HTTP 400 when payloads contain circular values, silently lossy JSON values (nested
undefined, functions, symbols, bigints, and non-finite numbers), or non-plain objects (Date,Map,Set, class instances, and customtoJSON()objects) that cannot be serialized faithfully. Also fixed the add/update dataset item endpoints to persist the caller-providedrequestContextentries instead of the live server request context instance, which contained internal server state and could fail storage serialization. (#19603) -
Documented the optional
requestContextbody field on agent controller run routes (send message, steer, follow-up, tool approval, tool suspension) so it appears in the generated OpenAPI spec. The server already merged this field into the request context; only the route schemas were missing it. (#19531) -
Replace
anywithunknownin generated route types so clients get real type errors instead of silently unchecked values. Route-types generation now deduplicates shared schemas into reusable type aliases, shrinking the generatedroute-types.generated.tsfrom ~94K to ~22K lines (77% smaller). The memory config response now typesworkingMemory(enabled, scope, template, schema, version) instead ofunknown. (#19573)If your code read fields from responses that were previously typed
any, TypeScript now requires you to narrow them before use:// Before: `metadata` was `any`, so this compiled even when unsafe const value = response.metadata.someField; // After: `metadata` is `unknown` — narrow it first const metadata = response.metadata; if (metadata && typeof metadata === 'object' && 'someField' in metadata) { const value = metadata.someField; } // ...or cast if you know the shape: (metadata as MyMetadata).someField
Code that reads
workingMemoryfrom the memory config response no longer needs casts — `config.workingMemory?