Highlights
Durable execution for Agents API (no deploy required)
Stored agents created via the Agents API can now run with durable: true, enabling durable execution (with configurable loop settings) without shipping new code, and inheriting cache/pubsub from your server for multi-replica durability.
Cloudflare Sandbox provider for remote workspaces
The new @mastra/cloudflare-sandbox provider runs commands and file operations via a deployed Sandbox Bridge Worker, adding a new deployment/runtime option for Mastra workspaces.
MCP protocol upgrades: stateless 2026-07-28 + multi-round elicitation
@mastra/mcp adds opt-in support for the stateless 2026-07-28 protocol revision (server + client) plus updated elicitation support using the spec’s multi round-trip mechanism, enabling more modern MCP interoperability without breaking legacy connections.
Sandbox checkpoints (Local + Platform/Railway capability signaling)
Sandboxes now advertise supportsCheckpoints, and LocalSandbox gains filesystem-backed checkpoints (checkpointName/seedCheckpointName) so sessions can boot from and persist real snapshots—unlocking faster warm-start patterns (also supported in Platform and Railway sandboxes).
RAG: persistable GraphRAG snapshots
@mastra/rag adds serialize() and GraphRAG.deserialize() so knowledge graphs can be saved/restored instead of rebuilt on every start, dramatically reducing startup cost for large corpora.
Breaking Changes
- None called out in this changelog.
Changelog
@mastra/core@1.60.0
Minor Changes
-
Added
ProcessHandle.closeStdin()to signal end-of-file to background processes. Local and Docker sandboxes support closing stdin, while providers without an available stdin-close API return a provider-specific unsupported-operation error. Providers signal the unsupported case with the newUnsupportedStdinCloseError, and the base class supplies that behavior by default so existingProcessHandlesubclasses keep compiling. Callinghandle.writer.end()also closes stdin, and finishes without an error when the provider cannot close stdin. (#21606)Also in: @mastra/agentcore@0.4.0, @mastra/apple-container@0.4.0, @mastra/blaxel@0.7.0, @mastra/daytona@0.8.0, @mastra/docker@0.6.0, @mastra/e2b@0.9.0, @mastra/modal@0.5.0, @mastra/platform-workspace@1.3.0, @mastra/railway@0.6.0, @mastra/vercel@1.4.0
-
Added live-tail PubSub subscriptions with
startFrom: "latest"and an explicitsupportsOffsetscapability. (#21535)await pubsub.subscribe(topic, callback, { startFrom: 'latest' });
The default remains
"earliest", and existing consumer groups keep their checkpoint. -
Added optional
filenameproperty toFilePayload. When an agent emits a file chunk with a filename, the Channel layer now preserves it through to the Chat SDK adapter instead of generating a name from the MIME type. Existing file chunks without a filename continue using thegenerated.<ext>fallback. (#21135)const chunk: FilePayload = { data: fileBuffer, mimeType: 'text/plain', filename: 'report.txt', // preserved through to the channel adapter };
-
Client-executed tools (tools without a server-side
execute) now fire theironOutputlifecycle hook when the browser returns the tool result on a follow-up request. PreviouslyonOutputonly fired after a server-sideexecute, so client tools never reported their output. (#21377)const agent = new Agent({ // ... tools: { browserTool: createTool({ id: 'browserTool', description: 'Runs in the browser', inputSchema: z.object({ query: z.string() }), // No execute — the client runs the tool. This hook now fires when // the client sends the result back. onOutput: async ({ toolCallId, output }) => { console.log('client tool resolved', toolCallId, output); }, }), }, });
The hook fires for trailing tool results that match a raw tool call from the preceding assistant message, so the follow-up request must include both (as
@mastra/client-jsdoes automatically). This works with standard, legacy, and durable agents, including requests with a same-name serializedclientToolsentry. The callback receives{ toolCallId, toolName, output, abortSignal }. Because the input is client-provided, treat the output as untrusted data. Delivery is at least once, and the hook does not fire when an input processor rejects the request. Keep hooks idempotent. -
Added
AgentControllerWireEvent,JsonReadyAgentControllerEvent,ErrorCarryingAgentControllerEventandWireDisplayStateto@mastra/core/agent-controller. They describe an agent controller event as it crosses an HTTP boundary — display-state Maps as records, errors as{ name, message }, dates as ISO strings — and are derived fromAgentControllerEventitself, so a client can type what it receives without redeclaring the controller's events by hand. (#21761)import type { AgentControllerWireEvent } from '@mastra/core/agent-controller'; function onEvent(event: AgentControllerWireEvent) { if (event.type === 'display_state_changed') { // a record after JSON, where the controller holds a Map Object.values(event.displayState.activeTools); } if (event.type === 'error') { event.error.message; } }
-
MCP tools served over HTTP now see the authenticated caller. When an MCP server runs behind a Mastra server with
server.authconfigured, the resolved user is bridged intoextra.authInfoautomatically, on both the streamable HTTP and SSE transports. Previouslyextra.authInfowas always undefined because the request handed to the MCP transport was rebuilt without the auth data. (#21689)Custom verification
If your own middleware verifies the caller, build the auth info yourself with the new
server.mcpOptions.setRequestAuthhook:export const mastra = new Mastra({ mcpServers: { myServer }, server: { middleware: [verifyBearerToken], mcpOptions: { setRequestAuth: (req, requestContext) => { const payload = requestContext.get('bearerPayload'); req.auth = { token: payload.token, clientId: payload.sub, scopes: payload.scope.split(' ') }; }, }, }, });
Fixes #17291
Also in: @mastra/hono@1.7.0, @mastra/mcp@1.17.0, @mastra/server@1.60.0
-
Added an opt-in
propagateflag on the FGA actor signal. Setactor: { actorKind: 'system', propagate: true }when starting a workflow run and the actor is forwarded into the agent and tool calls made for declarative.then(agent)and.then(tool)steps, so system and scheduled runs no longer fail membership resolution. Propagation stays opt-in, skips custom stepexecutefunctions, and can be overridden per step. (#19064) (#21713) -
Added per-message Signal metadata to Channel handlers. Use
ctx.signalMetadatato attach serializable, non-sensitive context that follows both idle and active message delivery: (#21330)handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.signalMetadata.attachmentIds = ['file-1'] await defaultHandler(thread, message) }, }
-
Added
delegation.hookErrorStrategyso a failing delegation hook no longer passes silently. (#21695)Previously, if
onDelegationStart,messageFilter, oronDelegationCompletethrew, the error was only logged and the delegation carried on. A throwingonDelegationStartcould not block a subagent, and a throwingonDelegationCompletelost whatever the hook was responsible for with no programmatic signal.Detecting hook failures
Hook failures are now always recorded on the run's request context, whatever strategy you choose:
const requestContext = new RequestContext(); await parentAgent.generate('Research AI trends', { requestContext }); const hookErrors = requestContext.get('__mastra_delegationHookErrors') ?? [];
Failing the delegation instead
Opt in to fail-closed behavior. A throwing
onDelegationStartthen blocks the subagent, and a throwingmessageFilteroronDelegationCompletesurfaces to the parent as a failed tool call:await parentAgent.generate('Research AI trends', { delegation: { hookErrorStrategy: 'throw', onDelegationComplete }, });
The default remains
'warn', so existing behavior is unchanged. Fixes #21624. -
Added checkpoint support to LocalSandbox and a checkpoint capability signal to sandboxes. Sandboxes now expose
supportsCheckpointsso features can detect whethersnapshot()persists real state. LocalSandbox gained filesystem-backed checkpoints: passcheckpointNameto seed the working directory onstart()and persist it onsnapshot(), andseedCheckpointNameas a boot-only fallback (for example a shared warm base image) that never gets overwritten by later snapshots. (#21798)import { LocalSandbox } from '@mastra/core/workspace'; const sandbox = new LocalSandbox({ workingDirectory: './workspace', checkpointName: 'session-123', seedCheckpointName: 'repo-base', }); await sandbox.start(); await sandbox.snapshot();
-
Added
modelSettings.timeoutso you can put a time limit on agent runs. SettotalMsto cap how long a whole run may take, including every reasoning step, tool call and retry, andstepMsto cap a single model call. Going over either limit fails with aMastraTimeoutError. AtotalMstimeout ends the run outright, while astepMstimeout moves on to the next model when you have configured fallback models, so a slow provider no longer stalls the run. Closes #15667 (#21724) -
Added foundational support for an upcoming experimental memory capability across storage, runtime, and developer tooling. (#19538)
Also in: @mastra/code-sdk@1.3.0, @mastra/factory@0.8.0, @mastra/libsql@1.21.0, @mastra/memory@1.27.0, @mastra/mongodb@1.18.0, @mastra/mysql@0.8.0, @mastra/pg@1.21.0
-
Added a
durableoption to stored agents so agents created through the Agents API can run with durable execution — no code deployment required. (#21715)await mastraClient.createStoredAgent({ id: 'helper', name: 'Helper', instructions: 'You are a helpful assistant.', model: { provider: 'openai', name: 'gpt-5' }, durable: true, });
Pass
truefor defaults, or{ maxSteps, cleanupTimeoutMs }to tune the durable loop. Cache and pubsub are inherited from the server's Mastra instance, so configure distributed backends there for durability across replicas. Automatic recovery is still configured in code viarecovery.durableAgents.Also in: @mastra/client-js@1.41.0, @mastra/dsql@1.3.1, @mastra/editor@0.14.0, @mastra/libsql@1.21.0, @mastra/mongodb@1.18.0, @mastra/mssql@1.7.1, @mastra/mysql@0.8.0, @mastra/pg@1.21.0, @mastra/server@1.60.0, @mastra/spanner@1.6.1
-
Added an opt-in
persistPartialOnAbortstream option that saves the assistant text streamed before a cancellation. Only the pre-abort snapshot is persisted, so output a provider keeps producing after cancellation is discarded, and nothing is saved when no text was streamed. Aborted streams still persist nothing by default. Also fixed thread creation tracking so a thread created during step persistence is no longer re-created on finish. Fixes #17510. (#21600) -
Added invoker-bound tool provider connections. Providers now receive the connection kind, toolkit, and live RequestContext so they can execute as the authenticated user without coupling provider identity to the Memory resource. The stored connection ID continues to select the exact provider account. (#21783)
import type { ToolProviders } from '@mastra/core/tool-provider'; const toolProviders: ToolProviders = { crm: { tools: { CREATE_LEAD: { toolkit: 'salesforce' }, }, connections: { salesforce: [ { kind: 'invoker', toolkit: 'salesforce', connectionId: 'connected-account-id', }, ], }, }, };
Patch Changes
-
Update provider registry and model documentation with latest models and providers (
587f6ef) -
Fixed workspace skill resolvers so concurrent requests keep their own resolved skill set throughout execution. (#21524)
-
Fixed routed models so tool results can include images and files. (#21183) (#21569)
-
Fixed model-backed processors (language detector, prompt injection detector, PII detector, system prompt scrubber, and moderation) dropping the request context. Their internal detection agents now receive the caller's RequestContext, so dynamic model resolvers and gateways can select models per request. (#21709)
-
Fixed the
tailparameter of the workspaceexecute_commandandget_process_outputtools rejecting numeric strings. Models sometimes send number parameters as strings, for example"10"instead of10. Thetimeoutparameter already tolerated this, buttailfailed validation and the command never ran, wasting a turn. In observed cases the model then fabricated a success result instead of retrying. Numeric strings are now coerced to numbers fortailin both tools. (#21493) -
Fixed generated streams to preserve provider metadata on text and file events. (#21441)
-
Added
settled()to the base memory class. Memory implementations can do work in the background after an agent run returns, and this gives callers a way to wait for it before closing a storage connection they own. The default implementation does nothing;@mastra/memoryoverrides it. (#21708)Also in: @mastra/memory@1.27.0
-
Fixed workflow cancellation so sleep() and sleepUntil() stop promptly without overwriting the canceled run status. (#21570)
-
Fixed evented workflow restarts so conditional branches resume at the correct execution path and aggregate their results. (#21603)
-
Improved observational memory progress restoration by rebuilding UI status from the durable memory record instead of polluted message history. Restored progress now also reports the memory thresholds you configured, where it previously always showed the built-in defaults. (#21604)
-
Refactor MastraCompositeStore domain wiring to be data driven. The constructor resolve block and the init roll call previously enumerated every storage domain by hand, so a domain registered in the stores map but missing from either list was silently skipped: init reported success while the domain's tables were never created. Both lists are now replaced by iteration over a typed DOMAIN_KEYS constant with a compile time exhaustiveness guard, and a conformance test proves a newly registered domain cannot dodge init. DOMAIN_KEYS is a new exported constant, following the existing EDITOR_DOMAINS pattern. No behavior change for the existing domains, with two narrow carve outs: a stores map entry without an init function is now skipped during init instead of throwing a TypeError, and an entry present in the stores map that the old roll call never named now gets initialized. (#21672)
-
Fixed agent memory so that calls now fail immediately when the given thread belongs to a different resource. Previously agent.stream() and agent.generate() would silently run the model and drop the turn instead of reporting the ownership mismatch (#21641). (#21691)
-
Fixed durable agents pausing between model steps by suppressing unused internal workflow step events that repeatedly serialized cumulative conversation state. (#21529)
-
Fixed durable step-start events publishing full model requests. (#21572)
-
Fixed replay streams so disconnecting during history replay also stops the live stream. (#21556)
-
Fixed dataset experiments with memory-enabled agents when no resource or thread IDs are provided. (#21552)
-
Fixed tool request context schemas to pass transformed values, coercions, and defaults to execute while retaining input-form values for nested validation. Explicit mutations now preserve schema input encoding and reject unencodable transformed writes without corrupting the shared context. (#21653)
-
Skills discovery no longer blocks agent turns: the skills processors serve the cached catalog and revalidate in the background, and refresh swaps the catalog atomically. Mid-session skill changes now appear one turn later (plus a staleness cooldown of up to 30 seconds); pass blockingRefresh: true to SkillsProcessor or SkillSearchProcessor to restore same-turn freshness by awaiting the refresh before the first step. (#21555)
-
Restored the Studio memory bar token counts after a reload by reading them from the stored observational memory record. (#21604)
Also in: @mastra/memory@1.27.0, @mastra/playground-ui@50.0.0
-
Retry transient Windows file locks when atomically replacing provider registry cache files. (#21425)
-
Fixed
DurableAgent.listActiveRuns()andrecoverActiveRuns()loading every running run's full workflow snapshot into memory at once. Candidate runs are now fetched from storage in bounded batches of 100 rows, so discovering or recovering runs against a large backlog no longer risks exhausting process memory. Fixes #21501. (#21518) -
Fixed array textStream output so it always stays valid JSON, even when the first streamed chunk already includes elements (#21559)
-
Exported
ReservedThreadMetadataKey, the list of thread-metadata keys an agent controller session owns for its own bookkeeping (selected model and mode, observer/reflector config, token usage, persisted preferences). Packages that cannot import the list as a value can now pin their copy of it to the real one: (#21739)import type { ReservedThreadMetadataKey } from '@mastra/core/agent-controller'; const RESERVED = { currentModelId: true /* … */ } satisfies Record<ReservedThreadMetadataKey, true>;
The list itself is unchanged — only its keys are now nameable, so a package that mirrors it fails to compile the moment the two fall out of step.
-
Added regression coverage keeping memory-sourced messages exempt from the thread ID check, so resource-scoped observational memory can pull in messages from a resource's other threads without failing the turn. (#21702)
-
Honor stored agent version overrides on direct programmatic agent calls. Version overrides supplied via
MASTRA_VERSIONS_KEYon the requestContext, theversionscall option, or Mastra-levelversionsdefaults now resolve the called agent itself to its stored version (previously they only applied to sub-agent delegation and HTTP routes), fixing the inconsistency where published overrides were served over HTTP but silently ignored in programmatic paths. (#21383) -
LocalSandbox now replaces an existing checkpoint atomically. A concurrent boot never observes a missing or half-written checkpoint while a snapshot is being saved. (#21798)
-
Fixed tool search and skill search losing loaded state for HTTP requests that pass a thread via memory options. Processors now resolve the thread ID from the memory request context, and single-name load_tool results are recognized when reconstructing loaded tools from conversation messages with storage: 'context'. Fixes #21508 (#21521)
-
Fixed durable run recovery so a reconnecting thread subscriber receives the remaining output and terminal event of a recovered run. (#21526)
Only one recovery of a run can be active at a time. A second concurrent
durableAgent.recover(runId)call now fails fast with the errorDURABLE_AGENT_RECOVER_ALREADY_IN_PROGRESSinstead of restarting the run twice. -
Fixed durable agent runs failing during shutdown by waiting for in-flight workflow persistence before closing storage. (#21532)
-
Rewrite system-reminder tags in Azure-bound user text and system instructions to avoid content moderation refusals while preserving stored history. (#21599)
-
Fixed
MockMemoryso its working memory merge keeps parity with@mastra/memory: anullnow deletes a field on the first write and inside newly created nested objects, instead of being stored literally. (#21687)Also in: @mastra/memory@1.27.0
-
Fixed stale tool suspension metadata when resuming with
agent.resumeStream(). (#21319)A tool suspended with
suspendSchemaleft itssuspendedToolsentry on the saved assistant message after it was resumed viaagent.resumeStream(resumeData, { runId, toolCallId }). A client reloading the thread read the already-resolved tool as still waiting for input, and could send the next resume to the wrong tool call. The entry is now cleared for both resume conventions.Falsy resume payloads are handled correctly
A tool whose
resumeSchemais a primitive can legitimately be resumed withfalse,0or"", which is how a boolean human-in-the-loop tool declines. Those payloads now clear the suspension entry, and a delegatedagent-*orworkflow-*tool resumed with one keeps resuming its existing sub-run instead of silently starting a fresh one.Approving one tool call no longer clears another's pending state
When two calls of the same tool were waiting and one of them was approved after its approval requirement had changed, the other call's pending state could be removed, leaving it stuck and unresumable. Approving one call now leaves the other waiting as expected.
Fixes #19083.
-
Sub-agent delegation no longer attempts to resume when the model supplies
resumeDatawithout a suspended run. Previously the delegation step chose the resume path onresumeDataalone and calledresumeGenerate/resumeStreamwith an undefined run id, throwingAGENT_RESUME_NO_SNAPSHOT_FOUNDbefore the sub-agent executed. It now starts a fresh delegation in that case. (#21729) -
Stop writing run-local workflow events to the server cache. (#21675)
Durable agents wrap
mastra.pubsubin aCachingPubSub, so every publish was mirrored into the shared cache for replay. Per-runworkflow.events.v2.<runId>watch events are only ever consumed in the publishing process, and their payloads accumulate step results — mirroring them filled shared stores (e.g. Redis) with lists no other instance could read.CachingPubSubalready skipped caching for publishes markedlocalOnly, but the caching layer sits above the proxy that sets that flag, so it never saw it.CachingPubSubOptionsnow accepts an optionalshouldCache?: (topic: string) => booleanpredicate, and the durable agent uses it to keep run-local topics out of the cache. Those events are still delivered live to subscribers; agent stream topics are unaffected and still replay as before. -
Estimate media token cost in TokenLimiterProcessor instead of serializing base64 payloads as text. File parts and media-shaped tool results (
{ data, mediaType }) previously fell through toJSON.stringify, so a single image could add thousands of phantom tokens and truncate history unnecessarily (#21731). (#21737) -
Fixed getWorkflowRunById and getWorkflowRunSteps so nested workflows report the correct single suspended leaf step. Obsolete suspension details are removed after the step resumes or completes. (#21229) (#21565)
-
Fix per-step
reasoningTextandreasoningaccumulating across steps for reasoning models. Each step in a multi-step run now reports only the reasoning produced during that step, matching the existing behavior of the per-steptextfield. Run-levelreasoningTextandreasoningremain the full concatenation across all steps. (#21711) -
Fixed
createStep(tool)sometimes misinterpreting a tool as a custom step. Copied or renamed tools — such as tools resolved through a tool provider — are now correctly detected and build a proper tool step. This applies to both the defaultcreateStepand the one exported from@mastra/core/workflows/evented. (#21512) -
Restore pending tool approval state when reloading v6 UI messages. (#17948)
-
Fix AgentController subscriptions hanging when remote runs lose their lease (#21540)
-
Fixed output processors missing response messages after a mid-run memory save (#21204). (#21533)
-
Preserve channel approval cards when tool output is hidden or suppressed by a custom renderer. (#21402)
Resolves #21162
Custom renderers can reuse the built-in approval and tool-event formatting:
import { formatToolApproval, renderBuiltInToolEvent, type ToolDisplayFn } from '@mastra/core/channels'; const renderTool: ToolDisplayFn = event => event.kind === 'approval' ? formatToolApproval(event.displayName, event.argsSummary, event.toolCallId, true) : renderBuiltInToolEvent(event, 'cards');
-
Stop evicting long-running agent runs at the suspended-run TTL.
MASTRA_SUSPENDED_RUN_TTL_MSnow bounds how long a run-scoped internal workflow may sit idle, not how long a run may take. Previously the lazy sweep measured wall-clock age from registration, so any run that legitimately executed past the TTL (30 minutes by default) had its workflow registration and run scope dropped mid-flight and went silent. Abandoned or suspended-and-never-resumed runs are still released on the same bound, so the memory protection is unchanged. Operators who raised the knob to work around this can return it to the default. (#21693) -
onAbort now receives the partial assistant text streamed before a mid-generation abort, alongside the completed steps (#21718)
-
Added public read-only thread query methods on
AgentControllerand ainitStorage()method that initializes storage without provisioning a workspace. Use these to read threads or messages without paying the workspace/sandbox startup cost thatcreateSessionincurs. (#21474)// Before: had to create a session (which called Workspace.init() -> sandbox.start()) const session = await controller.createSession({ resourceId }); const threads = await session.thread.list(); // After: read directly from storage, no session, no workspace const threads = await controller.queryThreads({ resourceId }); const messages = await controller.queryThreadMessages({ threadId, limit: 50 }); const thread = await controller.queryThreadById({ threadId });
queryThreads,queryThreadById, andqueryThreadMessageswere already used internally; they are now part of the publicAgentControllerAPI. Each lazily callsinitStorage(), so callers don't need to pre-init. -
Added stable submit_plan identity to suspension payloads and resumed results. (#21658)
-
Fixed tool results being lost when an agent calls a server-side tool and a client-side tool in the same step. The server-side tool's result is now streamed to the client and saved to history before the agent hands control back for the pending client-side tool, so clients no longer wait forever on a tool call that already finished. (#21688)
-
Fixed model configuration validation so configs with non-string routing fields (
id,providerId, ormodelId) are rejected with the standard "Invalid model configuration provided" error instead of failing later inside the model router. Fixes #21588 (#21664) -
Require workflow execution authorization when resuming runs through core APIs (#21602)
-
Fixed
ToolCallFilterdestroying stored tool calls. The filter used to rewrite the shared message list, so its filtered history was written back to memory and the original tool calls and results were permanently replaced with text. It now filters only the prompt sent to the model, leaving stored messages, memory, and UI history untouched. (#21631) (#21669)Behavior change: tool calls made during the current agent loop are no longer filtered by default, so the agent can still act on results it just produced. Use
filterAfterToolStepsto filter during the loop:// Keeps only the two most recent tool-producing steps new ToolCallFilter({ filterAfterToolSteps: 2 });
-
Fixed automatic tool resumption so approval-gated tools require an explicit trusted approval decision and remain suspended for ambiguous input. (#21539)
-
Fixed
requestContextSchematyping so it matches the open-map runtime without losing declared-key safety, and restored assignability of schema-typed agents to bareAgent. (#21525)Declared keys on
get/set/has/deletestay strictly typed (typos still fail). Runtime-only keys — including reserved middleware keys likemastra__resourceId— are available throughgetRaw/setRaw/hasRaw/deleteRaw, which return/acceptunknowninstead of forcingas nevercasts.Also fixed generic helpers typed as
(agent: Agent) => ...rejecting agents that declare arequestContextSchema(TRequestContextnow defaults toanyonAgent/SubAgentso the invariant generic remains assignable).const ctx = new RequestContext<{ tenantTier?: 'free' | 'pro' }>(); ctx.set('tenantTier', 'pro'); ctx.setRaw('session.cache', { hits: 0 }); const cache = ctx.getRaw('session.cache'); // unknown const schemaAgent = new Agent({ id: 'repro', name: 'repro', instructions: 'hi', model: 'openai/gpt-5.6-sol', requestContextSchema: z.object({ tenantTier: z.enum(['free', 'pro']).optional() }), }); declare function driveAgent(agent: Agent): Promise<void>; void driveAgent(schemaAgent); // ok
Fixes #21286
-
Fixed Anthropic cache-write usage to preserve 5-minute and 1-hour token counts across streams and observability. (#21563)
-
Added a
blockingoption toAgentController.onSessionCreated. Blocking listeners are awaited beforecreateSession()resolves, so hosts can seed session state (for example observational-memory settings loaded from storage) before the caller can start a run. Blocking listeners run sequentially in registration order before fire-and-forget listeners are notified. Listener failures remain isolated and logged; default (non-blocking) listeners keep their fire-and-forget behavior. (#21423)controller.onSessionCreated( async session => { // Runs before createSession() resolves for a newly materialized session. const settings = await loadSettings(session.identity.getResourceId()); if (settings) await session.state.set(settings); }, { blocking: true }, ); // Default listeners stay fire-and-forget. controller.onSessionCreated(session => audit(session));
-
Fixed workflow snapshot persistence emitting duplicate durable operation IDs. Running a workflow on a durable engine such as
@mastra/inngestloggedAUTOMATIC_PARALLEL_INDEXINGwarnings whenever a step suspended, because two snapshot writes on the same execution path shared one operation ID. Each snapshot write now uses a distinct ID, so suspend, resume, cancel, pause and sleep runs no longer produce the warning. Fixes #21639. (#21696) -
Fixed DurableAgent snapshots to prune duplicated foreach suspension data so long-running agents do not exceed storage document limits. (#21537)
-
Fix sub-agent delegation permanently grafting the supervisor's memory onto a memory-less sub-agent instance. Sub-agents are usually long-lived singletons, so the first supervisor to delegate would set its memory on the shared instance and every later supervisor — and every direct invocation of that sub-agent — would keep reading and writing that first supervisor's memory. The supervisor's memory is now passed through the delegated run's request context, so inheritance applies for that invocation only and the sub-agent instance is never mutated. (#21692)
Inherited memory is scoped to a single in-process delegation: it applies to the sub-agent the supervisor delegated to and not to agents that sub-agent delegates to in turn, and it is not carried across a durable run's suspend/resume boundary. A sub-agent that needs memory in its own right should declare it, as before.
-
Fixed observed durable run cancellation so calling
cleanup()immediately afterabort()no longer removes run state before terminal events and lifecycle callbacks are delivered. (#21527)Related to #21522
-
Tightened
AgentConfig.toolstyping so each entry must be an actual tool object. Previously a plain function such astools: { myTool: () => realTool }passed type checking and then threwTOOL_INVALID_FORMATat runtime, because the provider-defined tool member of the union is all-optional with an index signature. Provider-defined tools now require anidwhen used as a tools-map entry, mirroring the existing runtime check. Settingtoolsitself to a resolver function is still supported. (#21705) -
Fixed MCP-exposed agents so nested tools receive elicitation and other MCP context. (#20549) (#21548)
-
Normalize instruction-file paths (AGENTS.md/CLAUDE.md/CONTEXT.md) to forward slashes in dynamic
system-reminderinjection. On Windows,node:pathproduced backslash-separated paths that leaked into the prompt reminders and the metadata used to avoid re-injection; paths are now identical across platforms and match the paths tool calls report. Windows filesystem APIs accept forward slashes, so file reads are unaffected. (#21071)
@mastra/ai-sdk@1.9.0
Minor Changes
- Add
version: 'v7'support to the AI SDK UI helpers.toAISdkMessages(),toAISdkStream(),handleChatStream()/chatRoute(),handleNetworkStream()/networkRoute(), andhandleWorkflowStream()/workflowRoute()now accept'v7'and return streams and messages typed against AI SDK v7, so apps on AI SDK v7 no longer need casts at the route boundary. (#21720)
@mastra/auth-auth0@1.2.2
Patch Changes
-
Fixed reading request headers from Express-style plain header objects so cookie-based auth providers no longer throw and fail with a misleading 401. (#21261)
Related to #21253
Also in: @mastra/auth-better-auth@1.1.4, @mastra/auth-clerk@1.2.3, @mastra/auth-cloud@1.2.4, @mastra/auth-google@0.1.1, @mastra/auth-neon@0.3.1, @mastra/auth-okta@0.2.1, @mastra/auth-studio@1.3.4
@mastra/auth-better-auth@1.1.4
Patch Changes
-
Resolve a default
activeOrganizationIdfrom the user's oldest existing membership when the stored better-auth session has noactiveOrganizationIdset. Nothing in a default sign-in flow calls the organization plugin'ssetActive, so the field was always null and org-scoped consumers saw users with no organization. (#21482)import { MastraAuthBetterAuth } from '@mastra/auth-better-auth'; const mastraAuth = new MastraAuthBetterAuth({ auth }); const user = await mastraAuth.authenticateToken(token, request); // Populated even when the sign-in flow never called `setActive`. console.log(user?.session.activeOrganizationId);
The resolution is read-only and best-effort: the session row is not mutated, users with no memberships still authenticate, and a failed lookup falls back to today's behavior. Caveats: the resolved value is an inferred default, not an explicit user selection, and a session whose active organization was deliberately cleared via
setActiveis indistinguishable from one that was never set, so it also receives the default. A membership removed by an administrator stops being applied to new sessions within about a minute.
@mastra/braintrust@1.3.6
Patch Changes
-
Fixed suspended and resumed workflow runs appearing as two disconnected traces in Braintrust. Suspended and resumed workflow runs now appear in one Braintrust trace, with the resumed run nested under the span it was suspended from. As part of this change, Braintrust traces are grouped by the Mastra trace ID instead of a random ID, so multiple runs that share an explicitly provided trace ID now appear as one Braintrust trace. Fixes #20771. (#21047)
Upgrade note: a workflow run that was suspended before this upgrade and resumed after it still appears as two Braintrust traces — the older half was grouped under a random ID that the new version cannot recover — and the resumed half may display without a root span. This affects only runs in flight across the upgrade; runs suspended and resumed on the same version are unaffected. If this matters for your traces, drain suspended runs before upgrading.
@mastra/clickhouse@1.15.2
Patch Changes
-
Fixed ClickHouse replication initialization rejecting pre-existing tables that use local engines. Initialization now warns that
CREATE TABLE IF NOT EXISTSwill leave those tables unchanged and continues creating any missing replicated tables. (#21538) -
Fixed Studio metrics and logs detection for ClickHouse observability storage. Fixes #21435. (#21536)
@mastra/client-js@1.41.0
Minor Changes
-
Added
Agent.readPlan()for loading submitted plan Markdown. (#21658)const agent = client.getAgent('agent-id'); const plan = await agent.readPlan('.mastracode/plans/add-dark-mode.md');
-
Fixed the agent controller event types, which described payloads the server never sends. (#21739)
KnownAgentControllerEventwas written by hand and had drifted from the controller. Narrowing onom_activationgave you anenabledboolean that does not exist,om_statusastatusstring instead of the token windows,om_thread_title_updatedatitleinstead ofnewTitle, andsubagent_endonly atoolCallId— itsagentType,result,isErroranddurationMswere missing.usage_updatetyped its payload asunknown, so every consumer cast it. Seven events the controller emits (state_changed,command_exit,tool_suspension_cancelled, and the four remainingsubagent_*events) were not typed at all and fell throughisKnownAgentControllerEvent.isKnownAgentControllerEventnow returnstruefor those seven events as well. If you route unrecognised events to a fallback branch, they no longer reach it — give them a case in yourswitchor they are silently dropped.thread_creatednow deliversthread.createdAtandthread.updatedAtasDates, the way themessage_*events already did — the stream carries them as ISO strings.Payload drift is now a compile error instead of a wrong field at runtime, so handlers reading the old fields need updating.
// Before: compiled, but `enabled` is always undefined if (event.type === 'om_activation' && event.enabled) { ... } // After: tsc rejects it; the event carries cycleId, tokensActivated, generationCount, … if (event.type === 'om_activation') { console.log(event.tokensActivated) }
Patch Changes
-
Updated observability trace response types to include derived span statuses. (#21450)
-
Fixed the agent controller event types to match what the server actually sends.
KnownAgentControllerEventis now derived from the wire type@mastra/coreexports instead of a hand-written copy: thedisplay_state_changedfields are no longer all optional, and theerrorevent no longer claims itserrormay be a bare string. Runtime behavior is unchanged; only fallbacks written for those two type gaps become unnecessary. (#21761)import type { KnownAgentControllerEvent } from '@mastra/client-js'; function onEvent(event: KnownAgentControllerEvent) { if (event.type === 'display_state_changed') { // before: event.displayState.isRunning ?? false const running = event.displayState.isRunning; } if (event.type === 'error') { // before: typeof event.error === 'string' ? event.error : event.error.message const message = event.error.message; } }
-
session.state()now accepts athreadId, so reopening a chat can load the durable task list for that specific thread. (#21545)const state = await session.state({ threadId: 'thread-123' });
-
Fixed the agent controller types so they match what the server actually sends. The REST types are now derived from the route contracts the server publishes instead of being maintained by hand, so they can no longer drift. (#21503)
Three types were describing fields that never arrive over the wire:
AgentControllerAvailableModel.apiKeyEnvVar— the models route sendsid,provider,modelName,hasApiKeyanduseCountonly. The field is gone; reading it was alwaysundefined.AgentControllerThreadInfo— now the thread shapelistThreads()actually returns (id,title,updatedAt,tags,state). It no longer claimsresourceIdandcreatedAt, which that route does not send.createThread()andcloneThread()return the newCreateAgentControllerThreadResponse(id,title,resourceId,createdAt,updatedAt), which is a different shape from a listing entry.
Only
apiKeyEnvVarneeds action on your side. If you read it to tell whether a model is usable, readhasApiKeyinstead:const models = await client.getAgentController('my-controller').listModels(); // Before: typed string | undefined, undefined at runtime, so always empty const usable = models.filter(model => model.apiKeyEnvVar); // After const usable = models.filter(model => model.hasApiKey);
The thread types need no migration:
listThreads(),createThread()andcloneThread()each infer the shape their own route returns.PermissionPolicy,ToolCategoryandAgentControllerTaskSnapshotare now re-exported from@mastra/corerather than redeclared, so the SDK and core can't disagree about them.AgentControllerActiveRunis now exported from the package root alongside the other agent controller types. -
Preserve tool-call
providerMetadataat the message-part level during client-tool continuations. (#21703)The stream reducers nested
providerMetadatainsidetoolInvocation, but the server reads it frompart.providerMetadatawhen rebuilding the prompt. As a result the metadata was dropped on the recursive request, and Gemini thinking models (e.g.gemini-3-flash-preview) failed the follow-up turn withFunction call is missing a thought_signature in functionCall parts.
@mastra/cloudflare-sandbox@0.2.0
Minor Changes
- Add a Cloudflare Sandbox provider that executes commands and writes workspace files through a deployed Sandbox Bridge Worker. (#21596)
@mastra/code-sdk@1.3.0
Minor Changes
- Added opt-in discovery for MCP servers configured globally in Claude Code and Codex CLI. Enable the sources with
mcp.claudeCodeGlobalormcp.codexGlobalin Mastra Code settings. (#21598)
Patch Changes
-
Fixed custom command discovery loading Markdown files from node_modules. (#21680)
-
Stop an unread hook stdin from crashing the host process. A hook command that exits without reading its stdin closes the pipe mid-write, and the resulting EPIPE arrived as an unhandled socket error rather than a throw, so the surrounding
try/catchnever saw it and Node tore the process down. The socket error is now absorbed; the hook's real outcome still comes from its exit code. (#21796)
@mastra/deployer@1.60.0
Patch Changes
-
Fixed deployer analysis to use one deterministic external dependency set across analysis, bundling, and validation. Deprecated externals such as
nodemailer,jsdom,sqlite3, andfastembedare now declared and installed as runtime dependencies instead of bundled. This can cause a one-time experiment-worker digest and build-key change for affected projects. (#21483) -
Fixed
mastra buildpinning the wrong dependency version when the app and its parent workspace install different copies. The build now starts package lookup from the app directory, so the generated.mastra/output/package.jsonuses the app's installed version and the deployed server starts correctly. (#21213) -
Fixed mastra dev bundling for createRoute imports in path-aliased route files. (#21804)
-
Improved pnpm build failures to name blocked dependencies and report the required allowBuilds configuration as a user error. (#21488)
@mastra/duckdb@1.6.2
Patch Changes
@mastra/editor@0.14.0
Minor Changes
-
Added authenticated-user execution and deterministic connected-account routing to ComposioToolProvider. Invoker-bound connections execute as the authenticated Composio user against the exact stored connected account, including accounts shared through Composio ACLs. (#21783)
Use userIdResolver when application user IDs need mapping to Composio user IDs:
import { MASTRA_USER_KEY } from '@mastra/server/auth'; import { ComposioToolProvider } from '@mastra/editor/composio'; const composio = new ComposioToolProvider({ apiKey: process.env.COMPOSIO_API_KEY!, userIdResolver: ({ requestContext }) => { const user = requestContext?.getRaw(MASTRA_USER_KEY); if (!user || typeof user !== 'object' || !('id' in user) || typeof user.id !== 'string') return undefined; return user.id; }, });
Pinned caller-supplied connections now route to their exact account instead of allowing Composio to auto-select one.
Patch Changes
-
Fixed Composio tool results not being validated. Resolved Composio tools now keep the output schema supplied by Composio, so tool results are checked against it: real API responses (including null or extra fields) still pass, while structurally invalid output is rejected instead of being silently returned. This also lets Composio tools be used with APIs that require an output schema, like createStep(tool). (#21511)
-
Fixed editor-owned agent instructions failing silently. Agents configured with
editor: { instructions: true }now throw a clear error instead of running with empty instructions when no published version is available in Studio. This affected agents that were never provisioned, only had a draft version, were deleted, had a published version with no instructions, or hit a storage error while loading. Fixes #21373 (#21395)Before: the agent ran normally with an empty system prompt.
After: resolving or generating with the agent throws until a published version with instructions exists.
// Agent definition — Studio owns the instructions: export const agent = new Agent({ id: 'support-agent', editor: { instructions: true }, model: 'openai/gpt-4o', });
// Throws until a version is published in Studio: const agent = client.getAgent('support-agent', { status: 'published' }); await agent.generate('hi'); // Use status: 'draft' to run against the latest draft instead, without publishing: const draftAgent = client.getAgent('support-agent', { status: 'draft' }); await draftAgent.generate('hi');
-
Keep agent snapshot updates as drafts until explicitly published. (#21528)
@mastra/evals@1.8.0
Minor Changes
-
Added an
includeConversationHistoryoption to the Prompt Alignment scorer so multi-turn agent runs are scored in context. (#21683)Previously the scorer only saw the current turn. In a conversation a short reply like
"A"has no meaning on its own, so the judge could not tell what the user asked for and scored a perfectly good response as misaligned. The scorer now optionally includes the prior turns from the agent's memory, uses them to interpret the current prompt, and still scores only the current response.Before
const scorer = createPromptAlignmentScorerLLM({ model: 'openai/gpt-5-mini', options: { evaluationMode: 'user' }, });
After
const scorer = createPromptAlignmentScorerLLM({ model: 'openai/gpt-5-mini', options: { evaluationMode: 'user', includeConversationHistory: true, // or { maxMessages: 6 } }, });
The option is off by default, so existing scores do not change. It only applies to agent runs, which are the runs that carry remembered messages. Fixes #21638
Patch Changes
- Fixed LLM-judge scorers scoring an intermediate reply instead of the agent's final answer. When agent output contains multiple assistant messages (multi-step runs), scorers such as Prompt Alignment now evaluate the last assistant response that contains text. Fixes #21645 (#21686)
@mastra/factory@0.8.0
Minor Changes
-
Add per-repository worktree teardown commands and run them during terminal, explicit, and destructive Factory session cleanup. (#21564)
-
Made Factory session workspace resolution lazy. Resolving a session now returns the workspace immediately with a lazy sandbox handle; sandbox provisioning, repository materialization, branch checkout, and setup run in the background at session start (or on the first filesystem/sandbox operation) instead of blocking agent start. Storage reads during resolution are parallelized, failed background materializations are retried on the next use, and metadata-only resolutions such as thread-list polling never trigger sandbox work. (#21803)
-
Added org-visible Factory sessions: sessions store a visibility property derived from origin, org members can open org-visible sessions, and factory-ui shows session owners and access errors. (#21460)
-
Added a configurable allowlist of reviewer bots that can trigger GitHub review and comment notifications. Set MASTRACODE_GITHUB_AUTHORIZED_BOTS (comma-separated logins) or pass authorizedBots to GithubIntegration to trust bots beyond the built-in defaults; previously only coderabbitai[bot] and devin-ai-integration[bot] were accepted and every other bot was dropped without a log line. Bot logins now match case-insensitively and rejected senders are logged. Fixes #21621 (#21697)
-
Sped up new Factory agent sessions with warm repo base checkpoints. When a repository is connected, Factory now builds a base sandbox checkpoint (clone plus setup command) in the background, rebuilds it when pull requests merge to the default branch or pushes land there, and keeps it fresh via the periodic reconcile sweep. New sessions boot from the base checkpoint and skip the full clone and setup, falling back to the previous cold path when no checkpoint is available. (#21803)
Patch Changes
-
Speed up the local dev watch for the design system:
pnpm dev:uinow rebuilds@mastra/playground-uion save, so design-system edits show up in the Factory UI without a manual rebuild.pnpm dev:playgroundpicks up the same watch. The watch starts from a full build and then skips type declaration emit on every rebuild, which brings each save from ~9s down to ~1.5s. (#21646)Declarations stay frozen at that starting build for the length of a dev session — run
pnpm --filter @mastra/playground-ui buildafter changing a component's props. The published build is unchanged and still emits declarations.Also in: @mastra/playground-ui@50.0.0
-
Factory sessions can start before their sandbox is ready: resolving a session returns its workspace immediately, and background checkpoint-build failures now show up in logs instead of disappearing. (#21803)
-
Fixed session materialization timing being overwritten when sessions resume. The initial-materialize timestamp is now recorded once and preserved across idle-reap, checkpoint restore, and sandbox recreation, so time-to-first-materialize measurements reflect the true initial cost. Historical metrics captured before this fix are not backfilled. (#21520)
-
Prevent Factory handoff files from colliding across work items (#21763)
-
Added Factory session state to browser tabs and the sidebar, so a running session can be followed without switching to its window. (#21426)
- Session tab favicons are color-coded: amber while initializing, green while the agent works, blue when it is your turn, red on failure.
- Sidebar status dots now cover workspaces and user sessions alike, with Initializing / Working / Ready tooltips in the same three colors, so a tab and its sidebar row read the same.
- Failures show on the favicon only; the sidebar has no error dot yet.
- Tab titles show the session's identifier —
#1567for GitHub pull requests and issues,COR-210for Linear — or the thread title for user sessions. - Board kickoff toasts gained a New Tab action, so a ready session opens without leaving the board.
- Fixed a pinned session losing its sidebar slot when five other sessions were busy at once.
-
Fixed Linear issue reconciliation for issues that are not assigned to a project. (#21601)
-
Fixed workspace failures vanishing from the chat transcript. A workspace that failed to clone or start only flipped an internal flag that nothing rendered, so the session simply looked stuck with no reason given. The failure now appears as an error notice in the transcript — the same message the terminal already printed — for both the
workspace_errorand the failingworkspace_status_changedevent. (#21746) -
Fixed the Factory chat transcript drawing the same content twice after coming back to a tab. While a run streams, leaving the tab drops the event stream and the transcript refetches on return: an assistant reply the server had persisted as its own step, and a steer whose live event was missed, both landed on screen a second time. The refetched window is now paired against what is already drawn — by message id, by tool call, then by the text itself — so it only inserts what is genuinely missing. (#21651)
Also fixed a tool call rendering as two half-filled cards when a steer interrupted it: live tool state followed the newest assistant message instead of staying with the call it belongs to.
-
Resume a skill run that was aborted out from under it. (#21802)
An aborted run was recorded as a terminal failure on the assumption that an
abort is deliberate. In practice the dominant cause is the process going away
underneath the run — an operator restarting the server — and the run stream does
not say which happened. Cards were dead-ending at attempt 1 with nothing on the
board to press, needing a human to nudge each one by hand after every restart.Aborted runs are now retried like any other interrupted work, still bounded by
the existing attempt cap. -
Stop Factory from waking itself on its own GitHub comments. (#21800)
Factory recognised its own writes by comparing the event sender against
GITHUB_APP_SLUG. That variable names the deployment's own self-hosted GitHub
App, which is a different App than the one a Platform deployment posts as — and
on such a deployment it is legitimately unset, so the check compared against
undefined[bot]and never matched. Every self-loop guard silently failed open.The visible result: triage published its handoff comment, that comment came back
through ingress, re-invoked triage, and cancelled the run that had written it —
leaving the public verdict stuck at "Pending" while both runs reported success.The Platform integration now names the App it actually posts as, overridable
withMASTRA_PLATFORM_GITHUB_APP_SLUG, and identity resolution is centralised so
an unresolved identity is reported as unknown rather than collapsing into
"not Factory". -
Let a Factory run finish its stage when the previous role handed off in the same (#21802)
session.factory_transition_work_itemre-checked its authority at execution time by
comparing the live run binding against the binding row that existed when the
tool was built, requiring the same row id. But handing the next role its turn in
an existing session legitimately rotates that row: the previous role's binding is
revoked and a new one is issued for the same session and the same work item.
Tools built for the earlier role stay live across that rotation, so they were
keyed to a row that the handoff itself had just replaced.The visible result: planning produced a complete plan, called its terminal
transition toexecute, and was refused with "Factory agent binding is
unavailable, revoked, or no longer matches this session." The item stopped in
Planning with the plan written but never advanced, and the decision that carried
it reported success. Every leg that continues an item in an existing session —
planning after triage, and the review-feedback wakes — failed the same way.Authority is now the work item the session is bound to rather than the
individual binding row, so a rotation no longer strands the run it exists to
start. Re-pointing a session at a different work item is still refused. -
Start the implementation run when a work item enters Building. (#21802)
Building was the one stage on the Work board with no entry rule, so an item
arriving there stopped: the plan was approved and nothing picked it up until
somebody pressed Build by hand. Every other stage advances itself, which made
Building the single manual step in an otherwise continuous path from intake to
review.The run it starts carries a prompt rather than activating a skill. Skills exist
here to define a handoff — a terminal message later rules match on to decide
what happens next — and Building already has one: it ends by opening a pull
request, which arrives as its own event and raises the Review card. Rules could
previously only express "invoke this skill", so the decision vocabulary now
accepts a prompt as the alternative to a skill name. -
Credit the reporter as a co-author on work their issue caused (#21802)
When Factory builds a fix for a GitHub issue, the build prompt now asks for a
Co-Authored-Bytrailer naming the person who reported it, so the reporter shows
up as a contributor on the pull request rather than only in the issue thread.Only GitHub issues qualify. A Linear card stamps a display name and a manual card
stamps nothing, and neither resolves to the GitHub account a trailer needs, so
those are left uncredited rather than credited to nobody. Issues Factory filed
itself are skipped.Intake already stamped the reporter's login but the stage rules could not see it;
the intake-stamped metadata now reaches rules that run on a stage. -
Stop reporting a skill kickoff as successful when it was queued onto a run that was already ending. (#21802)
Signals sent into an active session settle as
deliver, which acknowledges routing but promises nothing about execution. If the in-flight run finished before draining its queue, the prompt was dropped: no turn started, no error surfaced, and the decision was marked succeeded while the work item sat in its new stage with nobody working on it. The dispatcher now confirms the signal actually landed in the thread and retries the decision when it did not, so the next attempt finds the session idle and takes the instrumented wake path. -
Dismiss runs still parked on a work item when it reaches a terminal stage. A merged or closed pull request cannot answer a suggested run, so the card no longer keeps asking. (#21802)
-
Check who sent a GitHub comment or review before letting it wake an agent, and ingest default-branch
pushevents. (#21800)The sender gate listed event kinds under names the webhook classifier never produces, so the identity check that keeps untrusted commenters from waking Factory agents was skipped for every comment and review event. The gate now names the kinds the classifier actually emits. Separately,
pushevents were dropped by the event filter before ingestion; they are now ingested and forwarded to Factory's event pipeline so downstream consumers (such as the upcoming warm base-checkpoint refresh) can observe default-branch pushes. -
Ingest
pull_request.openedfrom the Platform event poller so a newly opened pull request mints its Review card. The poller forwards an allow-list of events to the rules engine, andopenedwas missing from it — so on deployments without a direct webhook (the only path a local deployment has), a pull request the factory authored itself was never reviewed. (#21802) -
Keep kickoff skill resolution off the sandbox so clicking Review on a board card no longer blocks on full sandbox provisioning. Project skill roots (
.claude/skills,.agents/skills,<configDir>/skills) are guarded while the session sandbox is unmaterialized — discovery reports them empty instead of forcing materialization — and a skills rescan fires automatically once materialization completes so repo-local skills become visible. Bundled Factory skills (e.g. factory-review) resolve from local disk in milliseconds. (#21802) -
Stop the GitHub event worker from crashing when it is constructed before source-control storage is initialized. (#21801)
workers()dereferenced the integration's source-control storage eagerly while building the reconcile worker, but that storage is only attached later byversionControl.initialize. A deployment that constructs workers first crashed with "source-control storage has not been initialized". The worker now receives a lazy handle that resolves the storage slices at call time, once the worker is actually running. -
Deliver GitHub review feedback to the factory rules on the polling path. Submitted reviews and new pull request comments were dropped before the rules engine ran, so the agent that authored a branch was never woken when a reviewer asked for changes. (#21802)
-
Route pull request comments to the agent that authored the pull request, and stop provenance from branding commenters as Factory. (#21802)
Comments on a PR arrive as
issue_commentwithissue.pull_requestset, and the ingress explicitly dropped them. That closed the most common feedback path of all: on a Factory-authored PR, GitHub refuses a formal--approve/--request-changesverdict from the account that opened it, so the review skill falls back togh pr comment— which was discarded. External review bots leaving plain comments were dropped for the same reason.A new
pullRequestCommentCreatedrule event now carries those comments, reading the pull request from theissuepayload so provenance binds the comment to the authoring Work item rather than mistaking the number for an issue's. The default rule sends a high-prioritysendMessageto theworkrole, which wakes an idle session. Factory's own comments are ignored, becausefactoryAuthoredcannot distinguish the Work role from the Review role and reacting to them would let an agent wake itself in a loop.Separately,
factoryAuthoredwas derived from PR provenance for every event, which proves the pull request came from Factory, not the sender of the event. Any human or review bot commenting on a Factory-authored PR was therefore marked as Factory. Provenance-based attribution is now skipped for events whose sender is responding to the PR — comments, submitted reviews, and re-requested reviews — where only the app login identifies Factory. -
Wait for the run that swallowed a kickoff, instead of racing it. (#21802)
A signal queued onto an already-running turn can be dropped when that turn ends
before draining its queue. That case was detected correctly and then handed to
the generic exponential backoff, which spends all five attempts inside about
thirty seconds — while the run it is waiting on takes minutes. Every attempt
landed on the same busy session and the card gave up roughly ten times too early.The dispatcher now waits for the in-flight run to end and redelivers into the
freed session, which is the event that actually resolves the condition. The
decision settles within its original lease without spending the retry budget, and
a redelivery that is dropped again still goes back on the queue. -
Only credit reporters who are real GitHub accounts (#21802)
The issue poller stamps a placeholder login when GitHub returns no author, which
would have become aCo-Authored-Bytrailer crediting an account nobody owns.
Reporter credit now requires a login that matches GitHub's grammar. -
Re-review a pull request when a push lands while its card is still in Reviewing. (#21802)
A push to a card already sitting in Reviewing was dropped rather than deferred:
the rule returned nothing, and once the in-flight pass finished it transitioned
to Done having reviewed code that was no longer current, with no record that
newer commits had arrived. That is the exact ordering the review loop produces —
a review asks for changes, the authoring agent pushes a fix, and the fix lands
before the card finishes leaving Reviewing.Only Intake now suppresses the re-review. A push during Reviewing re-enters the
stage, which supersedes the stale pass: the stage rule already cancels the run
in flight and selects the right skill for the entry it sees.Transition decisions carry a
reenterflag for this, since a transition to the
stage an item already holds is otherwise inert — the common case is a board
being corrected into a state it already has, not work that needs restarting. -
Credit the author on review follow-up pull requests (#21802)
When a review pass ships mechanical fixes as a follow-up pull request, those
commits now carry aCo-Authored-Bytrailer for the human whose work they build
on — the reviewed pull request's author, or, when that author is a bot, the
reporter of the issue the pull request closes. -
Carry pull request review feedback back to the agent that wrote the code. (#21802)
A
changes_requestedreview is meant to wake the authoring work session, but
when the pull request carried no provenance the event resolved to the pull
request's own Review card.addressReviewFeedbackdeliberately refuses to act
on the review board — a Review card reacting to its own posted review would loop
the reviewer against itself — so the wake was dropped and the author never heard
about the feedback.Review and pull request comment events now follow the linked card's
parentWorkItemIdback to the item that authored the pull request, so the
existing guard becomes true for the right item instead of never. A pull request
card with no authoring item still emits nothing. -
Close the work/review loop: a review that requests changes now wakes the agent that authored the pull request. (#21802)
pull_request_reviewwebhooks were accepted and classified urgent, but no matching rule event existed, so the delivery was dropped after classification and the authoring agent was never told. PR subscriptions did not cover this — they sync PR activity into a thread's notification inbox for the agent to read on its next turn, but nothing starts that turn.A new
pullRequestReviewSubmittedrule event mapspull_request_review/submitted, and the default rule sends a high-prioritysendMessageto theworkrole, which wakes an idle session. Onlychanges_requestedfires;approvedandcommentedstay quiet, and the Review card that posted the review never reacts to its own output. -
Send Factory's own pull requests straight to Review. (#21802)
A pull request entered Review only when its author passed a repository-collaborator
permission check. A GitHub App bot is never a collaborator, so every pull request
Factory opened itself scored as untrusted and parked in Intake, waiting on a human
click — the exact opposite of the intent, since those are the pull requests whose
provenance Factory knows best.Factory authorship is now its own trust signal for pull requests: the branch came
from a Work run this Factory dispatched. Issues are deliberately unchanged, because
auto-triaging an issue Factory opened is a self-loop with no upside. -
Stop a running local Factory session from wedging when its checkout directory disappears. A tool spawned into a removed directory fails with
spawn /bin/sh ENOENT— an error that names the shell rather than the sandbox — so it was never recognized as a dead sandbox, and every later filesystem or command tool (including GitHub token refresh) failed the same way for the rest of the run. A missing working directory is now treated as the local equivalent of a destroyed sandbox: if the session is still live, the revival ladder rebuilds the checkout and retries the command; if the session was retired (retirement deletes the checkout on purpose), the run fails fast with a clear retirement error instead of resurrecting the retired checkout — before provisioning anything, so a retired session never consumes a sandbox or fleet budget slot, and a sandbox already mid-build when retirement lands is torn down rather than left bound to a dead session. A missing command reports the same ENOENT code, so the working directory is probed to tell the two apart and healthy sandboxes are never rebuilt for an unknown command. (#21803) -
Report what actually happened to a Factory run. A human dragging a card on the board now arms the item, so the run it asks for starts instead of parking for approval, and a run that dies mid-flight — a provider error, or a cancellation by the next decision — is recorded as failed on the decision instead of reported as a success. (#21802)
-
Factory sessions now revive a sandbox that dies mid-session instead of erroring the turn. When a command fails with a destroyed-sandbox error (for example after idle garbage collection), or with an exec-transport error whose connection never opened (so the command provably never started), the session drops the dead handle, re-runs the provisioning pipeline (reattach, checkpoint-seeded provision, or fresh clone), and retries the command once. Transport errors where the command may have already run are surfaced instead of replayed, so side effects like
git commitcannot execute twice. Concurrent failures coalesce onto a single revival. (#21803) -
Retire a parked proposal when the run it asked for starts anyway. Approving a (#21802)
proposal mints a fresh decision rather than dispatching the parked one, so the
original stayedproposedforever and the card kept asking to start a run that
had already finished. The dispatcher now dismisses proposals for the same work
item and role as anyinvokeSkillit dispatches, so the waiting badge only ever
marks a loop that is genuinely stopped. -
Make the board honest about runs it is waiting on, and about clicks that fail. (#21766)
Four gaps closed on the work board:
- A card click that failed while refreshing workspaces before starting a run did
nothing at all — no run, no error. It now surfaces the failure instead of
swallowing it, so an expired session reads as an expired session. - A run a rule proposed could not be approved from the card. The card menu now
offers it. - After a plan was approved, the Building run became unreachable from the card,
leaving the item stranded mid-loop. - A card with a proposed run looked idle. It now says it is waiting on someone,
with the approval inline.
- A card click that failed while refreshing workspaces before starting a run did
-
Stop showing "Linked card could not be filed" when nothing failed. (#21766)
A linked-card decision that already succeeded is deliberately reset to
retry
when its card is rematerialized, so the card gets re-filed. The board read any
retryas "already failed at least once" and put an error on the card, so a
routine replay looked like a broken automation — 16 cards were showing a failure
nobody caused.A card now reports an error only when the effect has actually been attempted or
left an error behind. A replay reads as what it is: the work it is doing. -
Fixed Factory completion so moving a GitHub issue to Done marks it as pending close and removes its remaining triage status labels. (#21515)
-
Deliver GitHub pull request signals to the session that actually owns the subscribed thread, and skip subscriptions whose thread this deployment does not hold. (#21800)
A subscription records the Factory project as its resource, but an unscoped session registers under its own id, so delivery looked for the thread under a resource that did not own it and failed with "Thread not found" on every matching event. Delivery now reads the thread from storage to find its owning resource. A subscription naming a thread that is absent is skipped rather than failed, so a pull request's events reaching a deployment that never owned the thread no longer fabricate a session or retry in a loop.
-
Fixed observational memory in Factory web user sessions ignoring the stored memory settings. Sessions created from the web UI now start with the observer and reflector models, thresholds, and attachment preferences saved in your memory settings, instead of falling back to the built-in default model — which failed with a missing API key error when that provider was not configured. (#21423)
-
Fixed restarting a review after deleting its thread. It no longer fails with "git clone failed: a branch named ... already exists". Reused Platform sandboxes now delete the previous session's local branches when they are recycled. A new session for the same branch starts fresh from the base branch. Branch checkout also recovers from leftover or broken branch refs instead of failing the workspace. (#21268)
-
The Review board now has a Canceled column. A pull request closed without merging used to reappear in Intake — the queue of pull requests still waiting for review — carrying a "Canceled" chip to explain why it looked out of place. It now sits in its own column. (#21323)
-
Fixed the Factory session favicons being nearly invisible in light-themed browsers: the Mastra mark now switches to black on light and white on dark, matching the default favicon, and the state dots use the design system's light-theme accents so amber, green, blue, and red stay legible on a white tab strip. (#21510)
-
Improved Factory stage handling so run routes and board columns stay aligned with the rule stage vocabulary. (#21516)
-
Smoothed out the chat transcript. A streamed reply now reveals at a steady pace instead of arriving in clumps, and a tool card that turns up while the agent is working fades in rather than popping onto the page. Both stop for readers who ask for reduced motion. (#21499)
-
Improved chat scrolling in the factory. Sending a message now scrolls once and parks it near the top with room under it, and the view stays on the agent's newest output — tool progress, subagents, the streamed reply — instead of standing still or jumping back up to what you just sent. (#21523)
Scroll up to read back and the chat stops following. Return to the bottom and it picks the stream up again. The jump-to-latest button no longer flickers when you send a message.
The room under the live turn is released when the run ends, so a finished conversation settles against the composer instead of leaving most of the window blank.
-
Board cards now show one status line instead of stacking several. A card reports what you just triggered, then what a rule is doing on its own, then what a click will do — one thing at a time, in that order. (#21323)
Automatic actions say what they do rather than where they sit in a queue. A card reads "Starting an automated run…" while a rule works, and "Automated run could not start" when it gives up, with the raw error one hover away next to Retry. An action the server is still re-attempting says "— retrying…", so a card looping through retries no longer looks like one starting for the first time.
Unfiled GitHub and Linear items now use the same card as filed work. Clicking anywhere on the card starts its default run and reports "Starting run…" while that resolves, instead of looking inert. A link to the issue or pull request sits beside the title, and the remaining actions are in the card's actions menu.
A resting card also shows less. The click hint and the actions menu fade in when you point at the card or reach it with the keyboard, the hint shares the author's line instead of taking a row of its own, and labels are shorter. Touch screens have no hover, so they keep both visible.
-
The turn-end filesystem capture no longer blocks agent turn completion. Readers of the persisted workspace file listing wait up to 10 seconds for the in-flight capture to observe the just-ended turn's files; if a capture takes longer, a reader can temporarily receive the previous listing. (#21679)
-
Platform GitHub event polling is now scoped to the repositories linked to a Factory project. Previously the worker polled every repository the underlying GitHub App installation exposed, which for customers who grant broad org access meant hundreds of unnecessary requests per polling cycle. With this change, no polling happens for repositories that are not linked to a project, and repositories added or removed from a project are picked up automatically on the next polling cycle — no worker restart or additional configuration required. (#21772)
-
Fixed how the Factory chat transcript reads agent controller events. It branched on an
om_activation.enabledflag the controller never sends, and cast token usage and memory progress into hand-written shapes that had drifted from the streamed payloads. Both now read the shapes the controller actually emits, so the status line and memory rings stay correct as those payloads evolve. (#21739) -
Fixed model packs so each user can set a default for new interactive Factory chats while preserving thread-specific pack and model choices, refreshing edited packs, and leaving Factory work runs unaffected. (#21762)
-
Fixed Linear intake so issues only land in the Factory project their Linear project is routed to. Previously, opening any board pulled in every selected Linear project's open issues and auto-triaged them there, repeating for each Factory project you viewed. (#21698)
Routing Linear projects
In Settings › Intake, each selected Linear project now picks the Factory it feeds. A project left unrouted no longer feeds any board, and boards only show the Linear intake feed for projects routed to them. Organizations with a single Factory keep working with no configuration.
Deleted cards stay deleted
Removing an intake card now also clears the stored routing decision behind it, so it no longer reappears on the next intake poll.
Fixes #21614
@mastra/inngest@1.8.7
Patch Changes
-
Fixed Inngest workflow resumes failing when persisted state grows too large by restoring resume state from storage instead of copying it into events. (#21549)
-
Fixed durable agent resumes to restore request context and continue the original trace after suspension. (#21566)
-
Fixed durable Inngest streams retaining events after consumers unsubscribe and prevented internal workflow watch events from filling replay caches. (#21572)
@mastra/langsmith@1.3.11
Patch Changes
- Added TTL-specific Anthropic cache-write token details alongside the aggregate LangSmith usage value. (#21563)
@mastra/mcp@1.17.0
Minor Changes
-
Added elicitation support on the
2026-07-28protocol revision using the spec's multi round-trip mechanism, completing the opt-inprotocolVersionsupport. (#20931)Server — tools keep the same promise-shaped API on both eras:
execute: async (inputData, options) => { const answer = await options.mcp.elicitation.sendRequest({ message: 'What is your favorite color?', requestedSchema: { type: 'object', properties: { color: { type: 'string' } } }, }); // ... };
On a
2026-07-28request, the tool call first returns aninput_requiredresult. After the client answers, the call retries with the answer attached. The tool function re-executes from the top on each retry, so keep side effects idempotent (or place them after the last elicitation) and keep the order ofsendRequest()calls deterministic. Legacy connections keep the existing push-basedelicitation/createflow unchanged.Client — a handler registered with
elicitation.onRequest()now fires on both eras: on2026-07-28connections, embedded elicitation requests are dispatched through the same handler and the originating tool call retries automatically. The warning that elicitation only works on legacy connections is removed. -
Added per-server time budgets and duration metrics to MCP discovery methods. (#21560)
const { tools, errors, durations } = await mcp.listToolsWithErrors({ perServerTimeoutMs: 3_000, });
-
Added opt-in support for the stateless MCP protocol revision
2026-07-28behind aprotocolVersionflag on bothMCPServerand the MCP client. Omitting the flag keeps today's behavior unchanged. (#20929)Server
const server = new MCPServer({ name: 'My Server', version: '1.0.0', tools: { weatherTool }, protocolVersion: '2026-07-28', cacheHints: { 'tools/list': { ttlMs: 60_000, cacheScope: 'private' }, }, });
With the flag set:
- One HTTP endpoint serves both protocol eras:
2026-07-28clients are served natively (stateless), and legacy clients are served through an automatic stateless fallback. startStdio()serves both eras, selecting the era from the connection's opening exchange.- Tool, prompt, and resource change notifications also reach
2026-07-28clients throughsubscriptions/listen. - Tool log messages honor the caller's per-request
logLevelopt-in. - Optional
cacheHintsadvertisettlMs/cacheScopeon cacheable results such astools/list. startHTTP()continues to enforce configured host and origin guards. Session and handler-lifetime transport options fail with a clear error instead of being ignored.
Client
const mcp = new MCPClient({ servers: { weather: { url: new URL('https://example.com/mcp'), protocolVersion: 'auto', // probe, fall back to legacy // or '2026-07-28' to pin the revision and fail loudly when unavailable }, }, });
Elicitation handlers currently only fire on legacy connections; support for the
2026-07-28input-required mechanism ships separately. - One HTTP endpoint serves both protocol eras:
@mastra/memory@1.27.0
Minor Changes
-
Added
continuationHintsto observational memory configuration, so an agent that drives its (#20665)
own control flow can stop memory from proposing what it says next.<current-task>and<suggested-response>were always requested and there was no way to turn
them off. A<suggested-response>is injected into the agent's context and the continuation
reminder tells the agent to follow it, which makes memory a second controller competing with
the agent's own. Passfalseto disable both sections, or an object to disable them
individually — keeping<current-task>while dropping<suggested-response>is the common
case.import { Memory } from '@mastra/memory'; const memory = new Memory({ options: { observationalMemory: { observation: { continuationHints: { suggestedResponse: false } }, reflection: { continuationHints: false }, }, }, });
Disabling a section removes it fully: the Observer and Reflector no longer describe or
reference it, and a previously stored hint stops being injected into the agent's context once
both observation and reflection disable it.Defaults are unchanged — existing configurations keep both sections enabled.
Patch Changes
-
Added
memory.settled(), which waits for background memory work to finish. Observational memory runs observation and reflection cycles in the background after an agent run returns, and those cycles kept writing to the database after callers had closed their storage connection. Awaitmemory.settled()before closing a store you own. Fixed the observational memory reflector repeating compression attempts that could not succeed: it now stops as soon as an attempt returns the same result as the previous one, instead of always working through the full retry ladder. This cuts the model calls and database statements a single reflection produces. Fixes #21617 (#21708) -
Log discarded degenerate observer/reflector output so failures are diagnosable. When observational memory's degenerate-repetition detector trips, the raw model output was previously thrown away — the error "Observer produced degenerate output after retry" gave no way to tell a real repetition loop apart from a detector false-positive on legitimately repetitive content. Detection sites now log bounded diagnostics (length, duplicate-window ratio, most-repeated window, head/tail snippets) to the OM debug log, and the thrown observer errors include a compact version of the same diagnostics. (#21807)
-
Fixed schema-based working memory so that
nullconsistently deletes a field. Previously, anullonly removed a field that already existed: on the very first write, or inside a nested object created for the first time, thenullwas stored literally. This mattered because strict-mode model providers pad every field they are not updating withnull, so a first write could be saved as{ "role": null }. Working memory updates are also no longer stored by reference to the object passed in. (#21687) -
Fixed observational memory removing Markdown link labels from the observation (#20665)
context given to agents. Links shared in observations previously collapsed to
bare, unlabelled URLs; their label text is now preserved. Semantic tags are
still stripped and collapsed-item markers behave as before. -
Improved observational memory step latency by reusing loaded records and token counts across repeated status checks. (#21562)
-
Fixed observational memory status updates so they remain available to live clients without creating standalone messages in stored conversation history. (#21604)
-
Added regression coverage for resource-scoped observational memory: context loaded from a resource's other threads is accepted on the current thread, keeps its original thread ID, and is never re-saved onto the current thread. (#21702)
@mastra/mysql@0.8.0
Patch Changes
-
Fix the alterTable existing-column probe reading the wrong information_schema field casing. MySQL returns result fields with uppercase keys through mysql2, so the probe's existing-column set was always empty and every warm boot re-ran 107 ALTER TABLE ADD COLUMN statements that failed with ER_DUP_FIELDNAME and were silently swallowed, taking metadata locks on production tables for nothing. The probe now reads whichever key casing is present. Measured on docker mysql:9.7: warm init drops from 326 to 109 client-server round trips and issues zero ALTER statements. (#21633)
-
Cut warm initialization from around 110 client-server round trips to single digits with an init-scoped schema snapshot, on top of the column-probe casing fix that removed the ALTER TABLE storm. Three information_schema reads at the start of init() now answer table, column, and index existence locally; createTable, alterTable, createIndex, and hasColumn consult the snapshot and maintain it as objects are created, and the memory domain's raw CREATE INDEX for idx_om_lookup_key consults it too instead of raising and swallowing ER_DUP_KEYNAME on every boot. The snapshot lives for exactly the init window and is cleared in a finally, so runtime callers keep querying the live catalog. Measured on docker mysql:9.7: warm init 109 to 111 round trips down to 7 (6 excluding measurement scaffolding), cold init 253 down to 153 or 154 across runs, with an identical cold-init table and index census before and after. (#21634)
@mastra/observability@1.17.1
Patch Changes
-
Fixed the
__truncatedmarker on span data. It now counts only the fields dropped by the object-key limit, so traces no longer report more omitted keys than were really dropped. Values beyond the limit are no longer read. (#21630) -
Fixed span serialization so internal tracing fields are removed only from framework-owned payloads while preserving user data with the same key names. (#21332)
-
Advertise quota-pause support to the Mastra platform.
MastraPlatformExporternow sendsx-mastra-observability-capabilities: quota-pause-v1on every request (batch uploads for all five signal types, plus the traces recovery probe), letting the platform respond with402 Payment Requiredto clients that understand the quota-pause contract while shielding legacy clients from retry loops. No configuration change is required: (#21447)import { MastraPlatformExporter } from '@mastra/observability'; const exporter = new MastraPlatformExporter({ accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN, });
-
Fixed Anthropic cache-write cost estimates by applying TTL-specific rates without double counting aggregate tokens. (#21563)
@mastra/otel-bridge@1.5.1
Patch Changes
- Fixed workflow traces breaking apart after suspend and resume when using OtelBridge. A resumed workflow run now continues the OpenTelemetry trace it started in instead of starting a brand-new one, even when the resume happens in a different process. This restores the trace continuity introduced in #12276 for setups that route spans through OpenTelemetry. Fixes #20771. (#21047)
@mastra/otel-exporter@1.3.9
Patch Changes
- Added TTL-specific Anthropic cache-creation token attributes alongside the aggregate OpenTelemetry attribute. (#21563)
@mastra/pg@1.21.0
Patch Changes
-
Stop a dropped Postgres connection from killing the process while a client is checked out. (#21765)
PgFactoryStorageattached anerrorlistener to the pool, but pg only routes pool-level errors for idle clients — it hands ownership of a client to the borrower for the duration of a checkout. A backend restart or network blip that landed on a client mid-transaction therefore reached an emitter with nothing listening, and Node escalated it to an uncaughtException that took the whole server down (Connection terminated unexpectedlyatpg/lib/client.js), even though the idle siblings were logged and discarded cleanly.Pools created by
PgFactoryStoragenow attach a listener once per physical connection as it is established, so a client stays covered while borrowed. While the client is idle the pool's own listener already reports the failure, so the extra listener stays quiet and a dropped connection is announced once, as the right thing. The pool still discards the failed connection and reconnects on the next checkout; the failure is now logged instead of fatal. Caller-supplied pools are left untouched, as before. -
Improved workflow run list performance in
@mastra/pgwhen filtering by workflow name. The default index avoids sorting the ordered result query for workflows with large run histories. Paginated requests still use a separate count query. (#21308) -
Improved PgVector upsert performance when writing many vectors at once. Batches are now written with a small number of multi-row inserts instead of one insert per vector, which reduces database round trips and connection pool usage during RAG and memory ingestion. (#21719)
-
Make
listWorkflowRunsstatus filtering indexable on Postgres. The status predicate previously wrapped every snapshot in aregexp_replace(snapshot::text, ...)::jsonbsanitization step, which forced a sequential scan over the wholemastra_workflow_snapshottable. Onjsonbsnapshot columns Postgres already rejects the problematic Unicode escape sequences at insert time, so the sanitization was a no-op there and the query now uses a plainsnapshot->>'status'comparison backed by a new default expression index on(workflow_name, snapshot->>'status', "createdAt" DESC). Legacy tables whose snapshot column is stilljsonortextkeep the sanitizing path. (#21684)
@mastra/platform-workspace@1.3.0
Patch Changes
-
PlatformSandbox restarts use the current sandbox connection after the previous sandbox is deleted or the platform does not return an instance URL, so later commands do not hit a stale sidecar. (#21798)
-
Added fallback checkpoint forwarding for Platform sandboxes so the workspace proxy can seed fresh sessions without changing their primary recovery key. (#21803)
const sandbox = new PlatformSandbox({ id: 'session-42', seedCheckpointName: 'repo-base', });
-
PlatformSandbox now restarts a timed-out sidecar health probe on the next command instead of falling back to the slower lease-based exec path for the sandbox's lifetime. If the in-sandbox sidecar was just slow to boot, later commands recover the fast private-network transport automatically. (#21798)
-
Declared checkpoint support (
supportsCheckpoints) so checkpoint-based features like warm base checkpoints and boot-from-checkpoint know snapshots are real. (#21798)// Gate checkpoint-dependent work on the provider's capability flag. if (sandbox.supportsCheckpoints) { await sandbox.snapshot(); // persists a checkpoint that can seed a later boot }
Also in: @mastra/railway@0.6.0
@mastra/playground-ui@50.0.0
Minor Changes
-
Added filled and ghost destructive Button variants for dangerous actions. (#21790)
<Button variant="destructive">Delete</Button> <Button variant="destructive-ghost">Remove</Button>
-
Streamed replies now arrive one word at a time instead of in bursts. (#21499)
Chunks reach the browser unevenly — a proxy flushes, a tool call ends, the model changes pace — so a reply used to lurch: ten words at once, then nothing for a fifth of a second.
MarkdownRenderernow paces a reply markedstreamingitself, revealing it one word at a time at the speed the reply is actually arriving. Bursts and gaps stop reaching the page, and a change of pace reads as one rather than as a jolt.<MarkdownRenderer streaming={part.state === 'streaming'}>{part.text}</MarkdownRenderer>
Each word fades in as it lands, and code fades in whole — a fence or a piece of inline code appears with its background rather than a token at a time. A word fades in once and only once, so a paragraph never flickers as the rest of the reply arrives behind it.
A thread opened from history renders whole. A reply opened part-written joins it rather than retyping it, and what was already on screen when you opened it stays put: only the words landing from then on animate. Readers who ask for reduced motion get the text at once, unanimated.
-
Improved
MessageScrollerso chat transcripts follow the stream. WithautoScrollon, the reader is carried with the newest output while they sit at the end, and a new user turn brings them back to it. Only scrolling away stops the following — content growing under the reader, or landing above them, no longer moves them or flashes the jump-to-end button. (#21523)A turn opening animates the scroll only for a reader who had scrolled away. Someone already at the end is carried by whatever the turn grows under itself, so a surface can reserve room under a live turn and let its own transition set the pace:
<MessageScrollerProvider autoScroll> <MessageScrollerViewport> <MessageScrollerContent> {/* the viewport is a size container, so a live turn can reserve a share of it */} <div className="min-h-[70cqh]">{liveTurn}</div> </MessageScrollerContent> </MessageScrollerViewport> </MessageScrollerProvider>
-
Added token and estimated cost totals to full trace details in Studio. Opening a trace now shows input tokens, output tokens, and estimated cost in both the trace side panel and the dedicated trace page, including when the trace is opened from a direct link outside the loaded list. Subtrace panels omit these values because they represent totals for the full trace. (#21541)
-
Added an "lg" size variant to sidebar navigation items for taller (36px) rows. (#21509)
-
Replaced the task list's progress bar and "2/4 completed" label with a compact status strip in the header. One bar per task, colored by state (green completed, orange in progress, grey pending), with the exact count on hover. Frees the vertical space the progress bar used to take. (#21507)
<TaskList>is unchanged. The strip now derives everything from the tasks, soTaskListCountandTaskListCountPropsare removed, andTaskListProgresstakes the tasks instead of a completed/total pair. Both were reachable from@mastra/playground-ui/components/ai/task-list.There is no replacement for
TaskListCount— the count lives in the strip's tooltip. Callers ofTaskListProgresspass the tasks they already render:const tasks = [ { id: '1', content: 'Inspect code', activeForm: 'Inspecting code', status: 'completed' }, { id: '2', content: 'Add tests', activeForm: 'Adding tests', status: 'in_progress' }, { id: '3', content: 'Build package', activeForm: 'Building package', status: 'pending' }, ]; // Before <TaskListCount completed={1} total={3} /> <TaskListProgress completed={1} total={3} /> // After <TaskListProgress tasks={tasks} />
Patch Changes
-
Improved trace details with readable times, precise hover values, responsive layouts, and clearer statuses. (#21660)
-
Improved plan action hierarchy, kept expand and collapse labels on one line, and hid expansion for short plans. (#21658)
@mastra/posthog@1.3.3
Patch Changes
- Added TTL-specific Anthropic cache-creation token properties alongside the aggregate PostHog usage value. (#21563)
@mastra/rag@2.6.0
Minor Changes
-
Added
serialize()andGraphRAG.deserialize()so a knowledge graph can be saved and restored instead of rebuilt on every process start. Building a graph compares every chunk against every other chunk, which is slow for large document sets; now you can do that work once and reload the result. (#21704)// Before: the graph had to be rebuilt every time const graphRag = new GraphRAG(1536, 0.7); graphRag.createGraph(documentChunks, embeddings); // After: build once, save the snapshot, and reload it later const snapshot = graphRag.serialize(); await writeFile('./graph.json', JSON.stringify(snapshot)); const restored = GraphRAG.deserialize(JSON.parse(await readFile('./graph.json', 'utf8'))); restored.query({ query: queryEmbedding, topK: 10 });
A snapshot is plain JSON, so you can store it in any database, file, or cache you already use. Loading a snapshot that does not match the graph's embedding dimension now fails immediately with a clear error instead of later during a query. Closes #3926.
@mastra/railway@0.6.0
Patch Changes
-
Added support for booting new Railway sandboxes from a fallback checkpoint while keeping future snapshots isolated to the sandbox's own checkpoint. (#21803)
const sandbox = new RailwaySandbox({ checkpointName: 'session-42', seedCheckpointName: 'repo-base', });
@mastra/react@1.4.4
Patch Changes
- Fixed custom suspended-tool resume data and reset approval state when legacy stream approvals fail. (#21658)
@mastra/redis@1.4.1
Patch Changes
- Fixed increment() never applying the configured TTL. Counter keys written via increment() now expire like every other key in RedisServerCache, preventing unbounded Redis key growth. (#21519)
@mastra/redis-streams@0.4.0
Minor Changes
-
Added live-tail Redis Streams subscriptions with
startFrom: "latest". New consumer groups skip retained entries while existing groups keep their checkpoint. Subscriptions also preserve their position when Redis recreates a missing group. (#21535)await pubsub.subscribe(topic, callback, { startFrom: 'latest' });
Patch Changes
- Fixed subscribe() creating Redis streams without a TTL. When streamIdleTtlMs is configured, the stream key created via MKSTREAM is now stamped with the TTL atomically, so topics that are subscribed to but never published to (for example per-run control topics) no longer linger in Redis forever. (#21519)
@mastra/server@1.60.0
Minor Changes
- Added support for loading submitted agent plan Markdown from an agent workspace. (#21658)
Patch Changes
-
Fixed
GET /agent-controller/:controllerId/sessions/:resourceId/threadsandGET /agent-controller/:controllerId/sessions/:resourceId/threads/:threadId/messagesprovisioning a workspace/sandbox on every request. Both endpoints previously routed through session creation as a side effect, stalling read-only page visits 5–17s and consuming a sandbox slot per visit. They now read from storage directly. Session creation and workspace/sandbox provisioning continue to happen on the write path as before. (#21474) -
Fixed serialized tool schemas to hide runtime-only background and suspended-run fields from user input forms. (#21451)
-
Fixed two things the agent controller's HTTP routes got wrong. (#21739)
Threads listed through
GET /agent-controller/:id/sessions/:resourceId/threadsleaked internal session bookkeeping as scoping tags: a session's persistedthinkingLevelandnotificationspreferences showed up next to real tags likeprojectPath, and could be passed to thetagsfilter. They are now filtered out like the other reserved keys.Workspace failures streamed over SSE arrived empty.
workspace_errorandworkspace_status_changedcarry anError, whosenameandmessageare non-enumerable, so JSON serialization sent"error": {}and browser clients could not show why the workspace failed. Only the genericerrorevent was being flattened; every event that carries anErrornow is.The session-state route also returns a typed
tokenUsageobject instead of an opaque record, so clients get the same shape thedisplay_state_changedevent carries. -
Fixed trace detail responses to report derived span statuses consistently with trace lists. (#21450)
-
Resolve the public origin from
X-Mastra-Public-Hostwhen present, ahead ofX-Forwarded-Host. (#21787)Gateways that overwrite
X-Forwarded-Hostwith their own internal domain (Railway's does) leftgetPublicOrigin()resolving an internal hostname, so deployed apps built OAuth callback URIs likehttps://my-project-qa-qa.up.railway.app/api/auth/sso/callbackand sign-in failed against providers that validate the redirect URI. The same mis-resolved origin also failed the same-origin check onredirect_uri, silently collapsing the post-login landing page to/. -
Fixed agent controller session state hydration to return durable tasks for the requested thread. (#21545)
@mastra/vectorize@1.1.1
Patch Changes
- Fixed Cloudflare Vectorize updates inserting a duplicate vector under a random ID instead of replacing the requested one, and added support for deleting multiple vectors by ID. Deleting by metadata filter remains unsupported. (#21582) (#21694)
Other updated packages
The following packages were updated with dependency changes only:
- @mastra/agent-builder@1.1.13
- @mastra/arize@1.3.9
- @mastra/arthur@0.4.9
- @mastra/datadog@1.4.1
- @mastra/deepeval@0.1.3
- @mastra/deployer-cloud@1.60.0
- @mastra/deployer-cloudflare@1.2.17
- @mastra/deployer-netlify@1.2.17
- @mastra/deployer-sandbox@0.3.2
- @mastra/deployer-vercel@1.2.17
- @mastra/express@1.5.2
- @mastra/fastify@1.5.2
- @mastra/koa@1.7.2
- @mastra/laminar@1.3.11
- @mastra/langfuse@1.4.9
- @mastra/longmemeval@1.1.17
- @mastra/mcp-docs-server@1.2.17
- @mastra/nestjs@0.2.17
- @mastra/next@0.2.16
- @mastra/opencode@0.1.17
- @mastra/sentry@1.2.11
- @mastra/tanstack-start@0.2.16
- @mastra/temporal@0.3.2
- @mastra/upstash@1.4.1