Highlights
MCP Events (poll/stream/webhooks + Studio inspection)
Experimental MCP Events (SEP-3415 draft) let MCP servers publish event streams that clients can poll, keep a stream open for, or receive via signed webhooks. Core/server/client add inspection APIs so Studio can list event types, stream occurrences, and test-emit events under mastra dev.
Observability Upgrades: Span Aggregation + “Time to First Chunk” + Richer Trace/Span Querying
New aggregateSpans() storage contract enables per-span analytics (e.g., p95 latency per tool, token/cost per agent) via the span-aggregate feature. Observability queries gain timeToFirstChunkMs fields, filtering, and sorting, plus expanded trace-scope fields (root entityId, root name, root spanType) and richer querySpans() context/parent/root-entity filters (with updated store capabilities to detect support).
Agent Controller Sessions Move to Session IDs (Multi-session per resource)
Sessions are now addressed by sessionId (not resourceId + scope) across core, server, and client-js, enabling many sessions per resource while keeping resourceId as the memory/thread owner key. New APIs include controller.getSession(sessionId), updated createSession()/deleteSession() signatures, and server routes under /agent-controller/:controllerId/sessions/:sessionId/....
Sandboxes Get a Native Filesystem API (Faster workspace operations)
WorkspaceSandbox can expose an optional filesystem for one-roundtrip reads/writes/listings instead of shelling out per operation. This lands across providers (e.g., E2B/PlatformSandbox) and is adopted by SandboxFilesystem in @mastra/code-sdk for faster file operations.
End-User Scoped Connect Integrations + Teams Channel
@mastra/connect adds per-resource (end-user) connection scoping for tools/credentials, preventing one user’s connections from serving another user’s requests, plus userConnectRoutes() to let authenticated end users connect their own accounts. Connect also adds Microsoft Teams as a supported channel.
Breaking Changes
- Agent controller sessions are keyed by
sessionId(scope removed): core/controller APIs, server routes, and client-jscontroller.session()/session.create()signatures updated. - Thread-row query contract changed: use
timeRange.traces/timeRange.threadsinstead oftraces.timeRange. querySpans()contract changed: removedmodel/providerfilters and droppedcost,inputTruncated,outputTruncated,errorTruncatedfrom responses (requires updated storage adapters).- Channel
resolveResourceId/resolveThreadIdcontexts now requirerequestContext(tests/manual calls must pass it).
Changelog
@mastra/core@1.79.0
Minor Changes
-
Breaking: Agent controller sessions are now addressed by their session id. Many sessions can share one
resourceId, soresourceIdonly says whose threads and memory a session works on. (#27336)- Added
controller.getSession(sessionId). Removedcontroller.getSessionByResource(resourceId, scope). - Removed the
scopeoption fromcreateSession(). Give each session its ownidinstead. Whenidis omitted it defaults to theresourceId, so existing one-session-per-resource code keeps working. deleteSession()takes{ sessionId }, andgenerateThreadTitle()takes an optionalsessionId.- The controller request context exposes
sessionIdnext toresourceId.
// Before await controller.createSession({ resourceId: 'repo-1', scope: 'run-a' }); const session = await controller.getSessionByResource('repo-1', 'run-a'); await controller.deleteSession({ resourceId: 'repo-1', scope: 'run-a' }); // After await controller.createSession({ id: 'run-a', resourceId: 'repo-1' }); const session = await controller.getSession('run-a'); await controller.deleteSession({ sessionId: 'run-a' });
createSessionthrows when an explicitresourceIddoesn't match the resource of the live session with that id, so a session id can't be used to take over another resource's session. - Added
-
aggregateTraces()can now group by the root span'sentityId, and trace queries and aggregates acceptentityIdas a trace-scopewherefield. Use it to split traces that have noentityName(for example, scorer runs recorded before scorers set one) by the primitive that produced them, then drill into a group by filtering on the sameentityId. (#27684)const { rows } = await client.aggregateTraces({ timeRange, where: { op: 'notExists', path: 'entityName' }, groupBy: ['entityId'], measures: ['count'], });
Trace-scope
entityIdreads the root span only. Insidespans.someandspans.none,entityIdstill matches any span in the trace. Stores that support the trace-scope field declare the newtrace-query-root-entity-idstorage feature.Also in: @mastra/clickhouse@1.26.0, @mastra/duckdb@1.15.0, @mastra/pg@1.32.0
-
Added the storage contract for span aggregation. Storage adapters can implement
aggregateSpans()and advertise thespan-aggregatefeature to answer questions like "p95 latency per tool" or "token spend per calling agent" over individual spans. (#27669)class MyObservabilityStore extends ObservabilityStorage { getFeatures(): readonly ObservabilityStorageFeature[] { return [...(super.getFeatures() ?? []), 'span-aggregate']; } // `plan` is already validated: tool calls grouped by name, with count and p95 duration async aggregateSpans(plan: TrustedSpanAggregatePlan): Promise<SpanAggregateResponse> { // e.g. { rows: [{ dimensions: { name: 'search' }, measures: { count: 42, 'duration.p95': 812 } }], truncated: false } } }
Span aggregates use the same request shape, measures, and limits as trace aggregates. Filters follow
querySpans(), so counts match the spansquerySpans()returns. Spans can be grouped by name, span type, and their own, parent, or root entity. Grouping by model or provider is not supported yet. Token and cost measures count usage on the span that recorded it. Adapters without the feature reject the call as not implemented. -
Fixed
DurableAgent.observe(runId)when attaching to a run from another process. (#26906)- Restored the agent's configured output processors, request context, and
returnScorerDatasetting, including when the observing process also runs durable steps. - Preserved live call-time processors, request context, and scorer data when another wrapper observes the run within the same Mastra instance.
- Behavior change: Cross-process
DurableAgent.observe(runId)now throwsDURABLE_AGENT_OBSERVE_AGENT_MISMATCHwhen the persisted run belongs to an agent with a different ID, matchingresume()andrecover(). Use the same agent ID in the observing process as in the process that started the run. - Made durable stream
onChunkcallbacks run serially and in order. - Made
onFinishwait for pendingonChunkcallbacks to finish, without waiting for transport acknowledgements.
Before: An observing process could use a different agent ID to attach to a run started by
researcher:const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher-observer' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId);
After: Use the starting agent's ID in the observing process. The previous configuration now throws
DURABLE_AGENT_OBSERVE_AGENT_MISMATCH:const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId);
- Restored the agent's configured output processors, request context, and
-
Sandboxes can now expose their own disk through an optional
filesystemproperty onWorkspaceSandbox. A provider with a file API answers reads, writes, and listings in one request each instead of a shell command per step. Paths are absolute inside the sandbox, and the provider decides containment. Fall back toexecuteCommand()when the property is absent: (#27859)const pkg = sandbox.filesystem ? await sandbox.filesystem.readFile('/workspace/app/package.json', { encoding: 'utf-8' }) : (await sandbox.executeCommand?.('cat', ['/workspace/app/package.json']))?.stdout;
-
Added batch experiment ID filtering to observability metric queries, so you can get token usage and cost for many experiments in one request. (#27255)
const result = await client.getMetricBreakdown({ name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'], aggregation: 'sum', groupBy: ['experimentId', 'name'], filters: { experimentIds: ['experiment-1', 'experiment-2'] }, });
Also in: @mastra/clickhouse@1.26.0, @mastra/client-js@1.56.0, @mastra/duckdb@1.15.0, @mastra/pg@1.32.0, @mastra/spanner@1.10.0
-
session.thread.transferOwnershipnow accepts an optionalthreadId, so you can transfer a thread without switching the session to it first. WithoutthreadIdit still transfers the active thread. (#27043)await session.thread.transferOwnership({ toOwnerId: 'user-456', threadId: 'thread-123' });
-
Channel
resolveResourceIdandresolveThreadIdhooks now receive the per-messagerequestContext. Values a channel handler sets before callingdefaultHandler, such as a tenant id, can be read directly in the resolvers instead of being looked up a second time. (#27436)channels: { handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.requestContext.set('tenantId', await lookupTenant(thread.channelId)); await defaultHandler(thread, message); }, }, resolveResourceId: ({ requestContext }) => `tenant:${requestContext.get('tenantId')}`, }
requestContextis a required field onResolveResourceIdContextandResolveThreadIdContext. Code that builds these objects by hand, such as tests that call a resolver directly, now needs to pass one:// Before await resolveResourceId({ platform, thread, message, defaultResourceId }); // After await resolveResourceId({ platform, thread, message, defaultResourceId, requestContext: new RequestContext() });
-
Fixed workspace search and file listing ignoring
.gitignorefiles below the workspace root. Content search and the file tree now apply every.gitignorerelative to its own directory (for example, each repository in a multi-repo workspace) and skipnode_modulesunless the path points inside it. This keeps search from reading dependency files one by one on remote filesystems, and keeps file listings free of ignored and dependency files. (#27687)Filesystems that implement
walkcan now honor two new options:includeGitignoreFilesreturns.gitignorefiles even when hidden entries are excluded, andskipDirectoryNamesskips directories such asnode_modulesentirely.const entries = await filesystem.walk('.', { includeGitignoreFiles: true, skipDirectoryNames: ['node_modules'], });
-
Add opt-in
bundler.pnpmWorkspaceSettingsto write pnpm install-policy settings to generated deployment workspaces. Supported keys areallowBuilds,onlyBuiltDependencies,ignoredBuiltDependencies,minimumReleaseAge,minimumReleaseAgeExclude,trustPolicy,trustPolicyExclude,trustPolicyIgnoreAfter, andblockExoticSubdeps; explicit values override copied source settings and any other key is rejected before pnpm runs. Existing behavior is unchanged when omitted. (#27708)import { Mastra } from '@mastra/core/mastra'; export const mastra = new Mastra({ bundler: { pnpmWorkspaceSettings: { blockExoticSubdeps: false, }, }, });
Also in: @mastra/deployer@1.79.0
-
Added iteration-aware final output processing so processors receive the current feedback-continuation iteration as its own assistant message and can transform each iteration independently. (#26346)
Fixed feedback continuations with output processors so rewrites and redactions are preserved in
getFullOutput().textandsteps[].textinstead of returning raw earlier-iteration text.Custom output processors that persist transcripts should use
messageList.cloneForPersistence()to retain one assistant response with its original identity and order.// Before: may persist processor-visible response messages separately. const processor = { id: 'transcript-persister', processOutputResult: async ({ messages, messageList }) => { await saveMessages(messageList.get.response.db()); return messages; }, }; // After: preserves one assistant response with its original identity and order. const processor = { id: 'transcript-persister', processOutputResult: async ({ messages, messageList }) => { await saveMessages(messageList.cloneForPersistence().get.response.db()); return messages; }, };
-
Added thread rows with activity timestamps, trace counts, previews, root-agent model details, filtering and sorting. (#27652)
Breaking contract change: Thread-row requests use
timeRange.tracesortimeRange.threadsinstead oftraces.timeRange. Related-trace predicates are not supported in the new contract. Legacy identity requests remain supported for mixed package versions.// Before const before = { traces: { timeRange: { from, to } } }; // After const after = { timeRange: { traces: { from, to } } };
-
Added optional, experimental
getEventsInfo()andgetEventsInspector()members toMCPServerBase, plus the event info types (MCPServerEventInfo,MCPServerEventStreamFrame,MCPServerEventsInspector, …) andMCP_SERVER_EVENT_ERROR_IDS. Servers that declare MCP events use them to let Studio list, poll, stream and test-emit their event types. Servers without events don't need to implement them. (#27674)const info = server.getEventsInfo?.(); console.log(info?.events.map(event => event.name)); const inspector = server.getEventsInspector?.(); const { events, cursor } = await inspector!.poll('ticket.created', { arguments: { project: 'web' } });
-
Added an
embeddingDimensionoption to Memory. When you know your embedder's output dimension, setting it lets semantic recall skip the probe embedding call it otherwise makes once per Memory instance. Startup then makes no billed embedding call and doesn't fail when the embedding provider is unreachable. The vector index name stays the same, so existing indexes keep working. If the configured value doesn't match the length of the embeddings the embedder returns, Memory throws aMASTRA_MEMORY_EMBEDDING_DIMENSION_MISMATCHerror naming both values before creating or querying any vector index. (#27588)const memory = new Memory({ embedder: 'openai/text-embedding-3-small', embeddingDimension: 1536, options: { semanticRecall: true }, });
-
Added
timeToFirstChunkMsto trace and span queries.queryTraces()returns the root span's time to first chunk andquerySpans()returns each span's, bothnullwhen no chunk was streamed. Both accepttimeToFirstChunkMsfilters with the same operators asdurationMs. Stores that support it declare thetrace-query-time-to-first-chunkfeature. (#27277) -
Support embedding specification v4 models from AI SDK v7. Core accepts v4 embedding models and exports
embedV4; Memory and RAG use the matching SDK to generate embeddings while preserving v1, v2, and v3 behavior. (#27710)Also in: @mastra/memory@1.39.0, @mastra/rag@2.7.0
-
Added a
datasetIdsfilter to dataset listing, so you can fetch specific datasets by ID in one request. All matches come back on one page, up to 100 IDs. (#27255)const { datasets } = await client.listDatasets({ datasetIds: ['dataset-1', 'dataset-2'] });
Also in: @mastra/client-js@1.56.0, @mastra/libsql@1.27.0, @mastra/mongodb@1.24.0, @mastra/mysql@0.14.0, @mastra/pg@1.32.0, @mastra/server@1.79.0, @mastra/spanner@1.10.0
-
Added the root span's
nameandspanTypeas trace-scope fields for trace queries and trace aggregates. (#27321)parseTraceQueryRequest()andparseTraceAggregateRequest()acceptnameandspanTypein trace-scopewherepredicates.planTraceAggregate()accepts them ingroupByand ascountDistinct.name/countDistinct.spanType.- Trace-scope field discovery lists both with value suggestions, and the
aggregate.dimensionslist includes them.
Inside
spans.someandspans.none,nameandspanTypestill match any span in the trace.const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'workflow_run' } }, groupBy: ['name'], measures: ['count', 'errorRate'], }), );
Stores that support these fields declare the new
trace-query-root-span-fieldsstorage feature. -
Agent schedules can now send an idle wake to the worker that has claimed the thread. Set
ifIdle.requireClaimedOwner: trueon the schedule, or return it from thepreparehook. This works the same way asagent.sendSignal: the claim holder runs the turn with its own claim stream options. If no owner answers, the fire fails withoutcome: 'failed'. Before this change, the option was dropped, so whichever worker took the fire ran the turn itself. (#27896)await mastra.schedules.create({ agentId: 'support', cron: '0 * * * *', prompt: 'Check in', threadId, resourceId, ifIdle: { behavior: 'wake', requireClaimedOwner: true }, });
-
Added
querySpans()filters and response fields for span context and parent/root entity information. For example: (#27272)where: { op: 'eq', left: { path: 'threadId' }, right: { literal: 'thread-123' } }
Breaking:
querySpans()no longer acceptsmodelandproviderfilters or returnscost,inputTruncated,outputTruncatedanderrorTruncated. Model/provider values, name matching, duration filtering and previews remain available.The new filters require an updated storage adapter.
-
Trace and span queries can now sort by
timeToFirstChunkMs, in either direction. Missing values sort last, and pages stay stable when values tie. Stores that support it declare thetrace-query-time-to-first-chunk-sortfeature. (#27473)const page = await storage.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, orderBy: [{ field: 'timeToFirstChunkMs', direction: 'desc' }] }), ), );
-
Span records now carry
timeToFirstChunkMs, the milliseconds from span start to the first streamed chunk. The storage exporters fill it in, so storage adapters save the value without computing it. (#27140) -
Added
completionStartTimeto agent run and model step span attributes, agetTimeToFirstChunkMs()helper that derives time to first chunk for any span, and carried the first-chunk clock through durable and evented agent runs so their generation and agent run spans report it too. (#26998) -
Added
backgroundTasks.untilIdleMaxIdleMsto set the default idle timeout forstream({ untilIdle: true }). Before, calls that did not passmaxIdleMsalways closed after 5 minutes without new events. This included calls Mastra makes internally, such as waking an agent aftersendMessage. A background task that ran longer than that finished without a continuation turn. A per-calluntilIdle: { maxIdleMs }still takes precedence. (#27757)const mastra = new Mastra({ backgroundTasks: { enabled: true, untilIdleMaxIdleMs: 30 * 60_000, }, });
-
Added an opt-in
apply_patchworkspace tool that creates, updates, moves and deletes several files in one call. The tool checks the whole patch before it writes anything. If a write fails partway, it tries to undo the changes it already made. The tool is off by default. Enable it per tool: (#27913)new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), tools: { [WORKSPACE_TOOLS.FILESYSTEM.APPLY_PATCH]: { enabled: true } }, });
Setting the top-level
tools.enableddoes not turn it on, so existing agents keep usingwrite_fileandedit_file. Paths follow the same containment, read-before-write and write-lock rules as the other filesystem tools.
Patch Changes
-
Fixed agent runs that were aborted while a tool was running finishing with
finishReason: 'tool-calls'. When a tool stops because the run's abort signal fired, the run now ends withfinishReason: 'aborted', emits anabortchunk, and callsonAbort, the same as an abort during the model response. (#27748) -
Fixed agent runs that kept calling the model and tools after losing their thread lease. When lease renewal fails, the run is now aborted instead of running beside the run that took over the thread on another process. Applies to regular, durable and evented agents. (#27581)
-
The error for a model with an unrecognized
specificationVersionnow listsv4(AI SDK v7) among the supported versions. (#27516) -
Fixed session messages starting while another agent runs on the same thread. When an
AgentControlleruses an application's agent and has nopubsubof its own, its agents without their ownpubsubnow share the application's event bus. Session messages now wait for the other run to finish. Apubsubpassed to the controller still takes precedence. (#27631) -
Fixed two cases where an AgentController tool approval could get out of sync with its stored run: (#27371)
- A failed resume no longer loses the answer. If approving or declining a tool call fails while the run is still waiting on it, the approval prompt is raised again (
tool_approval_required) so the answer can be retried. Previously a retry returned{ accepted: false, reason: 'not_pending' }and the run stayed suspended. - Detaching no longer declines a pending approval.
session.thread.detachFromCurrent()now leaves a parked tool approval pending in storage, as documented, instead of declining it. It can be answered later, for example withrespondToPersistedToolApproval.
- A failed resume no longer loses the answer. If approving or declining a tool call fails while the run is still waiting on it, the approval prompt is raised again (
-
Update provider registry and model documentation with latest models and providers (
9b60f12) -
Fixed agent turns stopping early when an awaited background tool returns no result. The agent now sends
nullto the model and continues. (#27482) -
Fixed
untilIdlecontinuations losing a completed background task's result when newer messages push it outside the configured history window. (#27205) -
Fixed
workspace_grephanging for minutes on large workspaces. A directory search that runs on the host now has a 60-second budget. The budget covers filesystems without native grep, including the defaultLocalFilesystem, as well as the fallback used when a filesystem's native grep fails. Once the budget runs out or the agent run is aborted, the search stops starting new reads (reads already in progress finish) and returns the matches found so far. The summary line marks these results as incomplete and says how many files were scanned. The fallback log message now says why native grep failed (for example, a timeout) instead of always reporting an unsupported pattern. (#27464) -
Reduced durable agent snapshot size: tool results are no longer stored twice per step, so long-running durable agents with large tool outputs write much smaller snapshots.
stopWhenconditions still receive the full step content. (#27634) -
Added
bufferSizeoption toSystemPromptScrubberandModerationProcessor. SetbufferSize: 200to check per sentence instead of per chunk (fewer LLM calls, catches content split across chunks). Default unchanged; will become the default in a future major. (#27510)new SystemPromptScrubber({ model, bufferSize: 200 }); new ModerationProcessor({ model, bufferSize: 200 });
-
Fixed live scorers passed at call time (
agent.stream(..., { scorers })) or throughdefaultOptions.scorersnever running unless the same scorer was also registered on the agent or on Mastra. Such scorers now run and save their scores on every agent engine, including durable and evented agents. (#27714) -
Added
byUserto theapprovedchannel tool display event, matchingdenied. A customtoolDisplayfunction can now show who approved a tool call (for example "Approved by Alice"), not only who denied it. The default approved card also names the approving user.byUseris undefined in DMs. (#27584)toolDisplay: event => { if (event.kind === 'approved') { return { kind: 'post', message: `Approved by ${event.byUser ?? 'you'}` }; } };
-
Channel streaming: text the agent writes before its first tool call now posts as its own message in
toolDisplay: 'grouped'mode. An opening acknowledgement lands above the Thinking Steps widget instead of under it. (#27350)The plan widget in
'grouped'and'timeline'modes is quieter. The header is a fixed "Working…" label and no longer echoes the current tool. Each task row shows the tool'sdescriptionargument (first line only) when it has one, and falls back totoolName firstArgotherwise.Grouped mode is an adapter option on the channel provider:
import { Mastra } from '@mastra/core'; import { SlackProvider } from '@mastra/slack'; export const mastra = new Mastra({ channels: { slack: new SlackProvider({ refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN, toolDisplay: 'grouped', }), }, });
-
Fixed an agent run that had lost its thread lease running tools next to the run that took over the thread. Lease renewals run every third of the lease TTL, so a run whose lease was taken over could keep calling tools until its next renewal. A run that holds a thread lease now checks the lease owner before each tool call. If another run owns the lease, the run is aborted and the tool does not run. If the lease backend can't answer, the local lease deadline decides as before. (#27749)
-
Classifiers now accept
refusalanswers from decision models instead of throwing. Previously, when a decision model refused one question,decide()failed withAnswer '<question>' does not match its question type.and any workflow.classifier()step failed the run. (#27488)A refused question now returns
{ type: 'refusal' }while the other questions keep their answers.RefusalAnsweris exported and included in everyClassifierAnswertype. Refusals have nochoice,score, orprobability, so equality checks such asanswers.route.choice === 'billing'evaluate tofalse; checktype === 'refusal'before relying on numeric fields.const { answers } = await classifier.decide({ state }); if (answers.unsafe.type === 'refusal') { // Route to review, block, or apply a default. }
Classifier scorers fail with an error naming the refused question, and
ModelSelectionProcessorkeeps the configured model and reportsabstained: 'refused'. -
Classifier evaluation traces now record the classifier as their entity, so they show the classifier name on the Traces page instead of "(not set)" and can be filtered and grouped like agents, workflows and scorers.
EntityTypegains aclassifiervalue. (#27756)Also in: @mastra/client-js@1.56.0
-
Fixed
Classifier.decide()crashing withstate.map is not a functionon decision providers built for@ai-sdk/provider4.0.25 and newer, such as@ai-sdk/typesafe-ai3.0.16+. String and JSON state are now converted totextandjsonparts before the provider is called, matching the AI SDK. Arrays are passed through only when every element is already a state part; other JSON arrays are wrapped in ajsonpart. (#27298) -
Fixed
autoResumeSuspendedToolsleaving a resumed plain tool marked as suspended. This happened when the model resumed a regular (non-agent, non-workflow) tool under a new tool call id. The original suspension was never cleared. Every later turn asked the model to resume the tool again. Reloaded clients also showed the call as still pending. The original suspension is now cleared, and atool-call-resumedchunk is emitted for it. This applies to the standard, evented and durable agents. (#27784) -
Fixed approval after a host disconnect without cancelling the parked agent run. (#27907)
-
Fixed workspace search and file listing so a deeper
.gitignorecan re-include what a parent ignores (for example!keep.login a subdirectory overriding*.log), matching git. Search results from a filesystem's nativegrepnow also apply.gitignorefiles in subdirectories of the search path. (#27695) -
Classifier evaluation spans now show what the classifier was asked and what it decided. The span input holds the
stateand the questions, and the span output holds the answers, so routing decisions are visible in traces. SethideInputandhideOutputintracingOptionsto keep them out of exported traces. (#27243) -
Fixed durable and evented agents losing the conversation when a client tool is approved or declined on an agent without memory. The finish chunk now carries the same
messageshistory as the default agent, so the follow-up model request keeps the original user prompt, the tool call and the tool result. The client tool also runs once: the first stream of a durable run stays open after suspension, and it no longer re-runs a tool the approve or decline request can execute. A server-side approve, which sends no client tools, still runs the tool on that open stream. (#27874)Also in: @mastra/client-js@1.56.0
-
Fixed durable agents not forwarding a declined sub-agent tool approval to the sub-agent. When a supervisor declines a tool approval raised by a sub-agent, the decline and its reason now reach the suspended sub-agent run, matching the approve path and the regular
Agent. Declining the supervisor's own tool approval still denies the call locally. (#27374) -
Fixed durable and evented agents placing
isTaskCompletefeedback after the model's next tool call and its result. The feedback now comes right before the model's retry, as it does with regular agents, so the model reads it as the reason for continuing. Fixes #27548. (#27743) -
Fixed durable and evented agents ignoring per-model
modelSettingsin a model list. When an agent is configured withmodel: [{ model, modelSettings: { temperature: 0.1 } }, ...], each entry's settings now reach the provider on every attempt, including retries and fallbacks, instead of falling back to call-time settings. (#27727) -
Fixed DurableAgent ignoring the reserved
mastra__threadIdandmastra__resourceIdrequest-context keys. Durable and evented agents now route memory to the thread and resource set on the request context, as the regular Agent does, instead of to the thread or resource named in the call'smemoryoptions. (#27575) -
Fixed durable agent resumes that lose a resume race or arrive after the run finished (#27365). (#27379)
- A second approval of the same tool call fails right away with
AGENT_RESUME_ALREADY_CLAIMEDwhile the first resume runs in the same process, including when both approvals arrive at the same moment. Before, it waited for the whole resumed turn or about 5 s and then failed withDURABLE_AGENT_EXECUTION_CONFLICT. - A resume that loses the race reports the error only on its own stream. Clients following the run no longer get an error event or a span ended with an error while the winning resume completes.
- Resuming a run whose snapshot was already deleted (for example because another instance finished it) now fails with
AGENT_RESUME_NO_SNAPSHOT_FOUNDand no longer writes a newpendingworkflow row. - A resume that loses the race no longer blocks the winning run's memory writes.
- The durable execution lease now follows
MASTRA_AGENT_THREAD_LEASE_TTL_MS(renewed every third of it). The default TTL drops from 30 s to 15 s, so a run left by a crashed process becomes recoverable sooner.
- A second approval of the same tool call fails right away with
-
You can now resume durable agents (
createDurableAgent,createInngestAgent) with the sameresumeGenerate(resumeData, { runId })call you use withAgent. (#27299)// Before (durable-only order, still supported but deprecated) await durableAgent.resumeGenerate(runId, { approved: true }); // After (same as Agent) await durableAgent.resumeGenerate({ approved: true }, { runId });
One deprecated call shape now throws instead of guessing: a string run ID followed by resume data that itself contains a string
runId, with no third argument, for exampleresumeGenerate('run-A', { runId: 'x', approved: true }). To keep using the deprecated order, pass an explicit options argument:resumeGenerate('run-A', { runId: 'x', approved: true }, {}).Also in: @mastra/inngest@1.10.7
-
Fixed durable and evented agents dropping earlier tool calls from the prompt on a second output processor retry. When a processor called
abort(reason, { retry: true })more than once in a run that had already made tool calls, the next request lost the earlier tool call and its result, which broke the provider prompt cache. Durable and evented agents now keep the full history across retries, matching the standard agent loop (#27561). (#27758) -
Fixed a durable
AgentControllersession hanging after a prompt left unanswered for longer thanMASTRA_SUSPENDED_RUN_TTL_MS. Later messages now run and finish as usual. The dropped prompt can still be answered. (#27632) -
Fixed
stopWhenon durable agents (DurableAgent, evented and Inngest) receiving steps withoutcontent. Predicates such assteps.at(-1)?.content.some(...)used to throw and end the run with a stream error. They now see the same step content thatstream.stepsreports. (#27524) -
Fixed
resumeGenerate()andgetFullOutput()on durable agents sometimes never completing when a resumed run suspends again. (#27384) -
Fixed durable and evented agents reporting no token usage for a step that suspends on a tool call.
generate()results andtool-call-approval/tool-call-suspendedchunks now include that step's usage, matching the regular agent. (#27493) -
Fixed durable and evented agents ignoring
generateTitle.emitEventandonTitleGenerated. Streams from these agents now include the transientdata-thread-titlechunk beforefinish, andmemory.onTitleGeneratedis called with the saved title, matching the standard agent. (#27606) -
Fixed durable and evented agents returning an empty
response.messageswhen aprocessOutputResultprocessor rejects the final answer.response.messagesnow contains the assistant's draft, matchingtext,stepsand the normal loop, so apps can still show the rejected reply to a reviewer. (#27737) -
Fixed evented workflow restarts replaying completed steps and ensured recovered durable-agent streams finish without repeating saved effects. (#27852)
-
Fixed
run.stream()on the evented workflow engine reportingworkflow-finishwithworkflowStatus: 'success'when the run failed. The evented engine now emits the failed step'sworkflow-step-resultbefore finishing, matching the default engine, so stream consumers and UIs seefailed. (#27559) -
Fixed evented
.foreach()runs where more than one iteration fails. The run now keeps the first iteration's error instead of the last one, and fails exactly once. Previously, an iteration failing after the run had already failed replaced the stored error and published a secondworkflow.fail. Time-travel replays that hit a failure again also failed twice. (#26651) -
Server model usage telemetry now counts tokens as model calls finish and reports them every 15 minutes and on shutdown as a new
mastra_model_token_usage_v2event. Eachmastra_model_token_usage_v2event contains only tokens the reporting process used, so redeploys, replicas, and lost home directories can't make it resend earlier usage. The startupmastra_model_token_usageevent is still sent, unchanged, for compatibility, and can still resend earlier usage in those cases. SetMASTRA_TELEMETRY_DISABLED=1to turn off telemetry. (#26986) -
Fixed durable agent startup recovery so unrelated runs can recover concurrently. Timed-out attempts are checked again after settlement without starting duplicate executions or releasing live ownership. (#27768)
-
xAI models now accept
xhighreasoning effort. Groq, xAI, Together AI, DeepInfra, Cerebras and Alibaba models now run on the AI SDK v7 provider packages, like DeepSeek. (#26530)await agent.generate('Prove that there are infinitely many primes.', { providerOptions: { xai: { reasoningEffort: 'xhigh' } }, });
-
Fixed
fetchWithRetryleaving the bodies of failed HTTP responses open when it retried or gave up, which could keep connections busy until garbage collection. Failed responses are now cancelled before retrying or throwing; successful responses are still returned unread. (#27439) -
Agent.reorderModels()andAgent.updateModelInModelList()now returntruewhen the edit was applied andfalsewhen it was not (no model list, unknown id, or a function-valuedmodel). Function-valued models are left untouched so per-request model selection keeps working. (#27192)if (!agent.updateModelInModelList({ id, enabled: false })) { // edit was not applied }
-
Fixed output stream processors dropping text they were still holding when a model stream ended. A reply shorter than one
BatchPartsProcessorbatch (for example withemitOnNonText: false) used to stream and save as empty; held parts are now released at the end of each model stream and passed through the processors that come after the one that held them. (#27565) -
Fixed a Session that follows a thread keeping a parked tool call after another Session aborted the run, or after the resumed run ended with a non-success finish such as
content-filter. The follower now ends the run and retracts the call withtool_suspension_cancelled, sosuspensions.hasPending()returns false. This also works when the follower runs in a different Mastra instance that shares the same pubsub (#26997). (#27786) -
Fixed a Session that follows a thread keeping a tool call marked as pending after the run was resumed on another instance and completed. The follower now drops the answered call, so
suspensions.hasPending()returns false and aborting no longer emits a spurioustool_suspension_cancelledevent. (#27226) -
Fixed durable, evented, and Inngest agent streams to reject when
stopWhenthrows, matching plain agents. (#26921)Previously, these stream consumers could handle this failure as an
errorchunk. Catch thefullStreamrejection instead:try { for await (const chunk of output.fullStream) { // Handle streamed chunks. } } catch (error) { // Handle the error thrown by stopWhen. }
Durable transports reconstruct the error from its serialized name and message. The original stack, cause, and subclass identity do not survive a cross-process boundary. Core durable and evented workflows classify this failure with the reserved internal
DURABLE_AGENT_STOP_WHEN_FAILEDidentifier. Inngest's serve transport uses the memoized step'sstop-when-id prefix because itsStepErrorserialization does not retain that identifier or its details. Any failure inside that Inngest step is therefore classified as astopWhenfailure.Also in: @mastra/inngest@1.10.7
-
Fixed Gemini 3 agents with working memory failing with "Function call is missing a thought_signature" when continuing a turn (for example after a client tool result). Gemini signs only the first tool call in a parallel step; when that call was
updateWorkingMemory, removing it from saved history left the step without a signed first call. The next tool call in the step is now saved with Google's documentedskip_thought_signature_validatorsignature, so the step replays on any Gemini client. (#27787)Also in: @mastra/memory@1.39.0
-
Fixed the goal judge reading the
TrailingAssistantGuard's syntheticContinue.turn as the latest user message. On models that reject a trailing assistant turn (Claude 4.6+, Gemini 3+), the judge was told the user had saidContinue.when they had not, hiding the real latest user message and miscounting assistant steps since it. The guard now marks its continuation, and the goal judge skips it. (#27503) -
Simplified Azure gateway defaults and synchronous stream handling without changing runtime behavior. (#27646)
-
Fixed replies being saved as a separate assistant message on Gemini 3+ (and Anthropic 4.6+ with structured output) when the input ends on an assistant message. The reply is now saved into that assistant message, the same as with other providers. The synthetic user turn ("Continue." or "Generate the structured response.") Mastra sends to these providers is also no longer kept in the run's message list after the request is built. (#27503)
-
Fixed output stream processors that hold parts (such as
BatchPartsProcessorandPIIDetector) re-emitting or leaking text when a model call failed. When a call is retried, the dropped attempt's text is no longer repeated before the retry's reply. When a call errors or is aborted, the text it was holding is dropped instead of being sent, and the error or abort still reaches the caller. Parts a processor holds back and sends again now reach the stream instead of getting stuck, so the reply'stext-endis no longer lost. (#27615) -
Fixed live DurableAgent observers attaching to runs owned by a different agent ID while preserving same-ID observation. Prepared runs now retain the DurableAgent's effective ID and name so ownership remains consistent after a restart. (#27353)
-
Stop an agent run as soon as another replica asks to abort it, even after the run has lost its thread lease. Previously the abort request was ignored once the lease was gone, so the run kept calling tools until its own lease renewal noticed the loss. (#27738)
-
Fixed token usage being counted twice after a tool call suspends and the run resumes. Spans and metrics for the resumed run now report only the tokens used after resuming, including when the resumed run suspends again. (#27634)
-
Fixed automatic durable agent recovery stopping after temporary storage or pubsub errors. Skipped runs are retried when the backend becomes available, without another restart or manual recovery. (#27354)
-
Fixed slow provider listing in
mastra dev. Opening the model picker in Studio (GET /api/agents/providers) no longer re-reads the shared provider cache from disk for every provider, so the request drops from seconds to well under a second. (#26711) -
Failed turns on the default Agent now save the same history as durable agents. (#27571)
- A reply that fails mid-stream keeps the text streamed so far, followed by the error, instead of only the error.
- When an input processor throws, the user message and the error are saved to memory, and the stream ends with an
errorchunk instead of the run failing with nothing saved.
-
Fixed live scorers passed to
stream()/generate()viascorers, or set in an agent'sdefaultOptions.scorers, being skipped on the Inngest engine when they are not registered on the agent or on Mastra. They now run after the call, as they already do on the default and evented engines. (#27719)Also in: @mastra/inngest@1.10.7
-
Fixed tool calls disappearing from conversation history when a message or reminder arrived while the tool was still running. The call and its result now stay in history, so the model sees its own completed work on the next turn. (#26963)
-
Fixed a thread that stopped starting runs after a run waiting on a tool prompt was followed by another run on the same thread. Once that other run finished, every new message, signal or notification was reported delivered but never ran until the process restarted. The waiting run now gives the thread up when another run takes it over, and it can still be resumed with the user's answer. A run that is still waiting on its prompt keeps holding the thread as before. (#27545)
-
Fixed
LocalSandboxcommands that could hang past their timeout. When a command times out, it is asked to stop and then force-killed two seconds later if it is still running, including child processes that keep its output open. Previously a command that ignored the stop request kept the tool call waiting forever. (#26879) -
Stop an agent run whose thread lease can no longer be confirmed. Previously, if lease renewals failed with an error or never answered (for example during a Redis outage on one process), the run kept calling the model and tools after its lease expired, alongside the successor run that took over the thread. Each run now aborts once one lease TTL has passed since its last confirmed acquire or renewal, the same way it already aborts when a renewal reports the lease lost. (#27731)
-
Fixed assistant message merging slowing down quadratically with the number of message parts. A step with thousands of reasoning items (common with OpenAI Responses reasoning) could previously block the event loop for tens of seconds; merging is now linear and produces the same merged messages. (#27912)
-
Fixed evented
foreachrestarts hanging when the next iteration had not started and nested workflow iterations re-running already completed child steps. (#26368) -
Fixed MCP tool results containing images or audio being dropped by AI SDK v5 models and rejected by AI SDK v7 models such as Amazon Bedrock (
unsupported tool content part type: image-data). MCP media content is now passed to every model in the format it supports. (#26662) -
Fixed a mode's
availableToolsbeing dropped after the run paused for the user. Approving or declining a tool call, answeringask_userorrequest_access, or rejecting a plan used to continue the run with every tool the agent has, including tools the mode excludes. The continuation now gets the same restricted tool set as the first model call. (#27864) -
Fixed notification dispatch stalling behind notifications it leaves pending. When a delivery policy holds notifications for another process's dispatcher (or they wait on an active run),
dispatchDueNotificationsnow reads past them instead of re-reading the same first page every pass, so notifications behind them are still delivered.limitnow counts only the notifications a pass delivers or fails. (#27426) -
Fixed notifications that ran out of delivery attempts disappearing from the inbox. The
notification-inboxtool's defaultlistnow includesfailednotifications, reporting them asfailedwith their last delivery error, and marks them seen so they show up once rather than on every list. (#27453) -
Fixed
observe()hanging forever on a durable or evented agent run that was aborted while waiting for tool approval. Once the rejected approval or resume releases the run, observers now receive theabortandfinishchunks and close. (#27923) -
Fixed agent runs stopping when background observational memory buffering fails. The failure is still reported as an
om_buffering_failedevent, and the turn keeps going. A failed synchronous observation still follows the configured failure policy. (#27438) -
Fixed a prompt waiting on the user disappearing from the session's display when another run on the same thread finished. The prompt could still be answered, but nothing showed it. Only the ending run's own prompts are now cleared. (#27555)
-
Fixed
get_process_outputwithwait: truehanging after a background command had exited. When the command left a child running that still held its output open (cmd &, or a child oftimeout), the wait never returned. It now returns once the command exits and its output has had a moment to drain, and says when a process the command started is still running. That process can still be stopped withkill_process, and is still stopped with the sandbox. A foreground command that starts a process in the background (cmd &) now returns once its shell exits rather than at its timeout, and the process it left behind is stopped when it returns. Usebackground: truefor processes that should keep running. (#27578) -
Fixed notifications that failed on their first delivery attempt. Delivery now retries automatically, up to five attempts, instead of staying pending forever. Retries preserve the claimed-owner requirement. (#25837)
-
Fixed
Agent.stream()ending with afinishchunk (reasonstop) when an output processor aborts inprocessOutputResult. The stream now ends with atripwirechunk andstatusis'tripwire', matching durable agents and other processor tripwires. (#27377) -
Fixed resumed agent spans to remain children of the initial agent run across repeated suspensions, matching durable agent tracing. (#27149)
-
respondToPersistedToolApprovalnow acceptsalwaysAllowCategory. Approving a tool call restored after a restart with "always allow" grants the tool's category to the run's thread, matching a live approval. (#27222)await session.respondToPersistedToolApproval({ toolCallId, approved: true, alwaysAllowCategory: true });
-
Fixed trajectory scorers in
runEvalsand dataset experiments getting a flat list of tool calls instead of the full trace when storage is configured.runEvalsalso no longer waits up to 6 seconds per item before scoring. (#27271) -
Fixed a TypeScript error (TS2345) when chaining processor steps with
.then()or.parallel(). Steps built withcreateStep(processor)now accept the output of a previous processor step, so the output guardrails workflow from the docs type-checks: (#27764)const workflow = createWorkflow({ id: 'output-guardrails', inputSchema: ProcessorStepSchema, outputSchema: ProcessorStepSchema, }) .then(createStep(new TokenLimiterProcessor({ limit: 1000 }))) .then(createStep(new BatchPartsProcessor())) .commit();
Also in: @mastra/inngest@1.10.7
-
Fixed approved tools added by an input processor running with the suspended run's request context. When you approve a tool call with
approveToolCall({ requestContext }), a tool that an input processor added now sees the request context you passed, just like a tool defined on the agent. (#27570) -
Fixed experiment comparisons when item, scorer, or experiment IDs match built-in object property names such as
constructor,toString, or__proto__. Comparisons previously threw or dropped those entries; they now group and report them like any other ID. (#27718) -
Fixed a misleading "No memory is configured but resourceId and threadId were passed in args" warning logged every time Observational Memory ran its Observer or Reflector. The warning now fires only when
memory.threadandmemory.resourceare passed to an agent that has no memory, not when the ids come from the request context (auth middleware, AgentController), which every agent in the request inherits. (#27865) -
Fixed several ways a suspended agent run could be resumed twice, never, or after it was aborted. (#27791)
- Approving after abort: approving, declining, or resuming a run after
abortRunStream()/abortThreadStream()hit its suspension now fails withAGENT_RESUME_ABORTED_RUNinstead of running the tool or starting another model request. Fixes #27617. listSuspendedRuns()after resume:DurableAgentnow marks a resumed run as running as soon as it resumes, so it drops out oflistSuspendedRuns()right away, including in other processes. A second resume of the same run fails withAGENT_RESUME_ALREADY_CLAIMEDinstead of running twice. Fixes #27619.- Resumer process dies: a running resume keeps renewing its claim. If the process dies, a later resume or approval fails with
AGENT_RESUME_ALREADY_CLAIMEDuntil the claim lapses, then takes the run over (or fails withAGENT_RESUME_RUN_FINISHEDwhen recovery already completed it). Fixes #27366.
Also in: @mastra/inngest@1.10.7
- Approving after abort: approving, declining, or resuming a run after
-
Fixed durable agent runs saving every tool output a second time in their final snapshot, which made the last snapshot write noticeably larger than the earlier ones. (#27672)
-
Fix experiment comparisons to average repeated trial scores per item instead of keeping only the last score. Items retain equal weight when experiments have different numbers of trials. (#27705)
-
Fixed notifications getting stuck when the delivery policy throws while the notification is being sent. The notification now records a failed delivery attempt and is retried by the notification dispatcher, up to the attempt limit, instead of staying pending forever. (#27736)
-
Fixed the 30-second default timeout for
LocalSandboxforeground commands. A command run without a timeout could previously wait forever. Configured defaults and per-command timeouts are honored, including0to disable the timeout, and cloned sandboxes keep the configured default. Background processes keep their existing lifetime. (#26878) -
Fixed
respondToToolSuspension()hanging when answering a tool call (such asask_user) that was parked during a run you aborted. The resumed run now streams to the Session and completes normally, instead of being silently dropped. (#26899) -
Fixed system messages added by input processors (such as the
TaskSignalProvidertask-list instruction) going missing when anAgentresumes after tool approval. Fixes #24844. (#27372)Resumed runs now reuse the system prompt from the suspended run, matching durable agents. Instructions or system messages passed only at resume time no longer replace it.
-
Fixed
Agent.resumeGenerate()andAgent.resumeStream()droppingreturnScorerDataon a plain Agent. A run started withreturnScorerData: truenow returns scoring data after a tool-approval or suspend resume, including when the resume happens in a different process ordefaultOptionsdisable it. PassingreturnScorerDatato the resume call still overrides the run's value. (#27747) -
Fixed lost messages when a per-step save fails. With
savePerStepenabled, a failed write used to drop the drained messages from the unsaved set, so no later save wrote them and the user's message or the step's output disappeared from the thread. Failed saves now mark those messages unsaved again, and the next save writes them. (#27556) -
Clarified that schedule
onErrorandonAborthooks only observe failures and aborts the schedule worker sees directly. Threaded schedules report failures after a run starts on the thread's own run, not through these hooks. (#27812) -
Scorer run and scorer step spans now record the scorer name as
entityName, falling back to the scorer ID. Previously scorer traces had no entity name, so filtering or grouping traces by entity name left them out. (#27684) -
Fixed authenticated agent controller sessions failing with
AGENT_MEMORY_THREAD_RESOURCE_MISMATCHwhen auth usesmapUserToResourceId. The controller now sets the reserved memory resource and thread keys to the session's own resource and thread. Other auth values in the request context are kept. (#27304) -
Fixed channel conversations (Slack, Discord, Telegram and others) never getting an automatic title. New channel threads now start without a title, like any other thread, so
generateTitlenames them from the conversation. (#27251)Before, every new channel thread was saved with a generic label such as
slack conversation, which counted as an existing title and blocked title generation. WithgenerateTitleturned off, new channel threads now appear untitled in Studio, which lists them by creation date, instead of showing that label. Existing threads keep their current title. -
Fixed trace query validation errors for an oversized
in/notInset pointing at the wholewherepredicate instead of itsset. Zod 4.6.7 stopped reporting which union branch failed in this case. (#27699) -
Bump for npm (#27221)
Also in: @mastra/acp@0.4.3, @mastra/agent-browser@0.5.4, @mastra/agent-builder@1.1.30, @mastra/agentcore@0.5.2, @mastra/agentfs@0.2.2, @mastra/ai-sdk@1.11.2, @mastra/apple-container@0.5.1, @mastra/archil@0.3.1, @mastra/arize@1.3.26, @mastra/arthur@0.4.26, @mastra/astra@1.1.2, @mastra/auth@1.1.4, @mastra/auth-auth0@1.2.5, @mastra/auth-better-auth@1.1.8, @mastra/auth-clerk@1.3.1, @mastra/auth-cloud@1.2.7, @mastra/auth-firebase@1.1.4, @mastra/auth-google@0.1.6, @mastra/auth-neon@0.3.4, @mastra/auth-okta@0.2.5, @mastra/auth-studio@1.3.10, @mastra/auth-supabase@1.1.5, @mastra/auth-workos@1.6.7, @mastra/azure@0.3.2, @mastra/azure-ai-search@0.1.1, @mastra/blaxel@0.10.1, @mastra/braintrust@1.3.23, @mastra/brightdata@0.3.2, @mastra/browser-firecrawl@0.2.4, @mastra/browser-viewer@0.2.5, @mastra/chroma@1.1.4, @mastra/claude@0.3.3, @mastra/clickhouse@1.26.0, @mastra/client-js@1.56.0, @mastra/cloudflare@1.7.5, @mastra/cloudflare-d1@1.4.4, @mastra/cloudflare-sandbox@0.5.3, @mastra/code-sdk@1.15.0, @mastra/codemod@1.1.7, @mastra/connect@1.1.0, @mastra/convex@1.7.5, @mastra/couchbase@1.1.3, @mastra/cursor@0.3.3, @mastra/datadog@1.4.18, @mastra/daytona@0.11.3, @mastra/deepeval@0.1.20, @mastra/deployer@1.79.0, @mastra/deployer-cloud@1.79.0, @mastra/deployer-cloudflare@1.2.38, @mastra/deployer-netlify@1.2.38, @mastra/deployer-sandbox@0.3.23, @mastra/deployer-vercel@1.2.38, @mastra/discord@1.3.1, @mastra/docker@0.12.0, @mastra/dsql@1.5.4, @mastra/duckdb@1.15.0, @mastra/dynamodb@1.4.4, @mastra/e2b@0.15.0, @mastra/e2b-desktop@0.1.9, @mastra/editor@0.16.4, @mastra/elasticsearch@1.4.5, @mastra/elysia@0.1.19, @mastra/evals@1.10.8, @mastra/express@1.5.23, @mastra/factory@0.24.0, @mastra/fastembed@1.3.4, @mastra/fastify@1.5.23, @mastra/files-sdk@0.3.2, @mastra/gcs@0.3.4, @mastra/github-signals@0.5.2, @mastra/google-cloud-pubsub@1.1.4, @mastra/google-drive@0.2.3, @mastra/hono@1.7.21, @mastra/inngest@1.10.7, @mastra/isolated-vm@0.1.3, @mastra/koa@1.7.23, @mastra/laminar@1.3.28, @mastra/lance@1.5.1, @mastra/langfuse@1.6.2, @mastra/langsmith@1.3.28, @mastra/libsql@1.27.0, @mastra/livekit@0.5.1, @mastra/loggers@1.3.9, @mastra/longmemeval@1.1.38, @mastra/mcp@2.5.0, @mastra/mcp-docs-server@1.3.9, @mastra/mcp-registry-registry@1.1.6, @mastra/memory@1.39.0, @mastra/mesa@0.3.2, @mastra/modal@0.7.2, @mastra/mongodb@1.24.0, @mastra/mssql@1.10.5, @mastra/mysql@0.14.0, @mastra/nestjs@0.2.38, @mastra/next@0.2.37, @mastra/observability@1.21.0, @mastra/openai@1.1.3, @mastra/opencode@0.1.38, @mastra/opensearch@1.1.3, @mastra/oracledb@0.5.4, @mastra/otel-bridge@1.5.18, @mastra/otel-exporter@1.4.9, @mastra/parallel@0.1.3, @mastra/perplexity@0.2.2, @mastra/pg@1.32.0, @mastra/pinecone@1.1.2, @mastra/platform-workspace@1.9.0, @mastra/playground-ui@65.0.0, @mastra/posthog@1.4.3, @mastra/qdrant@1.1.5, @mastra/quickjs@0.1.4, @mastra/rag@2.7.0, @mastra/railway@0.10.1, @mastra/react@1.12.0, @mastra/redis@1.4.11, @mastra/redis-streams@0.6.4, @mastra/s3@0.6.4, @mastra/s3vectors@1.1.4, @mastra/schema-compat@1.3.16, @mastra/sentry@1.2.28, @mastra/server@1.79.0, @mastra/server-adapters-test-suite@0.1.3, @mastra/slack@1.7.3, @mastra/spanner@1.10.0, @mastra/sprites@0.1.1, @mastra/stagehand@0.3.6, @mastra/tanstack-start@0.2.37, @mastra/tavily@1.1.3, @mastra/teams@0.1.2, @mastra/telegram@0.2.3, @mastra/temporal@0.4.17, @mastra/turbopuffer@1.2.2, @mastra/turso@0.1.17, @mastra/upstash@1.5.4, @mastra/valkey@0.2.7, @mastra/valkey-streams@0.5.6, @mastra/vectorize@1.1.3, @mastra/vercel@1.6.1, @mastra/voice-aws-nova-sonic@0.2.5, @mastra/voice-azure@0.12.2, @mastra/voice-cloudflare@0.13.3, @mastra/voice-deepgram@0.13.2, @mastra/voice-elevenlabs@0.13.2, @mastra/voice-gladia@0.13.2, @mastra/voice-google@0.14.3, @mastra/voice-google-gemini-live@0.14.17, @mastra/voice-inworld@0.4.5, @mastra/voice-mistral@0.1.2, @mastra/voice-modelslab@0.2.2, @mastra/voice-murf@0.13.2, @mastra/voice-openai@0.13.3, @mastra/voice-openai-realtime@0.14.7, @mastra/voice-playai@0.13.2, @mastra/voice-sarvam@1.1.3, @mastra/voice-speechify@0.14.2, @mastra/voice-xai-realtime@0.2.16, @mastra/voyageai@0.4.2, @mastra/weaviate@0.1.3
-
Fixed
System message must be at the beginningerrors from models served through Chat Completions APIs (e.g. Qwen3.5/3.6 or Gemma 3 on vLLM). Multiple leading system messages are now merged into one for Chat Completions providers. Prompts are left unchanged when an earlier system message carries its own provider options (such as a cache breakpoint). Providers with a dedicated system field (Anthropic, Google, Bedrock) and the OpenAI Responses API are unchanged. (#27307) -
Fixed skill tools (
skill,skill_read,skill_search) pausing for approval whenrequireToolApprovalis enabled globally. Previously every skill load in an approval-enabled agent (including durable agents) suspended for an approval nobody needed. (#27296) -
Fixed stored skills ignoring the
metadatafilter.skills.list({ metadata })andskills.listResolved({ metadata })now return only skills whose resolved version metadata matches every key/value pair, with correcttotaland pagination. (#27462)const skills = await storage.getStore('skills'); await skills.listResolved({ metadata: { publishedBy: 'user-123' } });
Also in: @mastra/libsql@1.27.0, @mastra/pg@1.32.0
-
Fixed Slack channel messages so agents receive the text of Block Kit layout blocks (header, section, context, markdown), not just the notification fallback. Block-only messages now reach the agent. Thread history includes the block text too. (#27393)
-
Fixed input processor rejections being ignored by agents running in separate worker processes. Upgrade both API and worker processes to apply the fix. (#27730)
-
Fixed evented workflows losing recoverable snapshots when terminal notifications fail. (#27355)
-
Fixed durable agent startup recovery getting stuck behind a stalled run. Each run's recovery wait is limited to 30 seconds without cancelling its execution, and recovery stops starting new work during shutdown. (#27357)
-
Removed the
gray-matterdependency. SKILL.md frontmatter is now parsed with theyamlpackage directly, which drops the vulnerablesprintf-jspackage (GHSA-hp3w-g68c-fv3c, no patched release available) from the production dependency tree. YAML frontmatter that worked before keeps working; JavaScript frontmatter (---js) is still rejected. (#27198) -
Fixed durable agent runs losing user input when a worker stops before the run finishes. (#27835)
Durable runs now stop before calling the model when the accepted input cannot be saved — for example when the storage write fails or the caller has no write permission on the thread — instead of answering without persisting it. Memory configured with
readOnlystill runs without writing. -
Agent controller session routes no longer start a session as a side effect. Only
POST /agent-controller/:controllerId/sessionsstarts one. Before, a message, model switch or state write that reached the server first created the session on a new random thread. The write then landed on a thread the client never read, so it looked lost. (#27474)Every other session route now acts on a started session. After a server restart it rebuilds the session from its stored thread. If the session was never started, the route returns
404and creates nothing.const session = client.getAgentController('code').session(resourceId); await session.create({ threadId }); // start first await session.sendMessage('Hello'); // 404 if the session was never started
AgentController.createSessionacceptsresumeOnly: trueto get this behavior. It throwsSessionNotStartedErrorwhen no stored thread matches. Factory now starts the session on its own thread when the session is created, so the first prompt can be sent right away.Also in: @mastra/factory@0.24.0, @mastra/server@1.79.0
-
Fixed paused tool calls picking up a different mode's tools when they resume. Approving, declining, or answering a paused tool call now uses the tools of the mode the run was paused in. Before, switching modes while a call waited gave the continuation the new mode's tools. A switch to an unrestricted mode could expose every tool to a run that started in a restricted one. The same applied when answering a suspension from another thread. Approving a plan still moves the run to the plan's transition mode. Fixes #27873. (#27911)
-
Fixed
stopWhenreceiving the wrong step content whensavePerStepis enabled. After a run was resumed (for example after a tool suspended to ask the user a question), a step could show up with no tool results, so conditions such as "stop after N failed tool calls" counted wrong and fired late. (#27521) -
Calling
abort()with no reason from an output processor'sprocessOutputStreamnow reportsTripwire triggered by <processorId>on durable and evented agents, the same asAgent.stream()and every other processor hook. Before this fix those engines reportedStream part blocked by <processorId>. (#27567) -
Fixed tool payload transforms not being applied to streamed tool calls. When a model streamed tool-call arguments, the
tool-callchunk sent to the stream had no transform metadata, so UIs showed the raw arguments instead of the redacted display version. The stream now emits the model's final tool call, which carries the transform. (#27501)A tool call ID of
__proto__no longer breaks the stream.A later step that reuses a tool call ID is now kept as its own call in
toolCallsinstead of being dropped. -
Fixed missing token usage when a tool call suspends or waits for approval. Suspended
stream()andgenerate()results now include the step's tokens. After resuming,totalUsagestays cumulative and token metrics count those tokens once. See #27232. (#27425) -
Fixed messages being lost when sent to a thread shortly after the same instance cleaned up the thread's abandoned suspended run. Such a send used to return
deliverwhile nothing received the message; it now returnsblocked. Retry the send, and once the old run's lease expires (15 seconds by default) it starts a new run. (#27389) -
Added
AgentController.setThreadModelto change a session thread's model without recreating the session. A running session bound to the thread switches immediately; otherwise the model is saved for its next run. (#27591)const { live } = await controller.setThreadModel({ resourceId: 'session-1', threadId: 'thread-1', modelId: 'openai/gpt-5.6', });
-
Fixed
isBadRequestErrorretrying invalid API key errors. Newer OpenAI provider versions reportinvalid_api_keystream errors with status 400, so they were retried as bad requests instead of failing right away. Authorization errors now stay terminal. (#27788) -
Fixed trace, span and thread query validation errors for oversized membership sets. An
in/notInpredicate with more than 100 values points at the set again (for examplewhere.setorwhere.spans.some.set). Since the zod 4.6.7 upgrade, the error pointed only atwhere. (#27782) -
Improved cancellation of durable and evented agent tool and model calls after execution ownership is lost. Cooperative tools receive an abort signal so superseded work can stop sooner without canceling the recovering run. Recovery may run a tool again, so tools must be safe to repeat. Cancellation cannot undo work already completed. (#27490)
Pass the tool's abort signal to operations that support cancellation:
import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const fetchText = createTool({ id: 'fetch-text', description: 'Fetch text from a URL.', inputSchema: z.object({ url: z.string().url() }), outputSchema: z.string(), execute: async ({ url }, context) => { // Without the signal, the request keeps running after ownership is lost. const response = await fetch(url, { signal: context?.abortSignal }); return response.text(); }, });
-
Fixed
timeTravel()running the same steps twice when called concurrently on one run.timeTravel()now atomically claims the run before executing, likeresume()does. A losing call throwsWORKFLOW_TIME_TRAVEL_ALREADY_CLAIMED(orWORKFLOW_TIME_TRAVEL_RUN_RUNNINGif the run is already executing) andPOST /api/workflows/:workflowId/time-travel-asyncreturns409 Conflict. The claim isn't enforced with storage adapters that don't support concurrent updates, whenshouldPersistSnapshotexcludes therunningstatus, or in the Inngest runner; concurrent calls there can still run steps twice. Fixes #27279. (#27313)Also in: @mastra/server@1.79.0
-
Fixed agents crashing with
Class constructor ... cannot be invoked without 'new'when a tool's schema is a class-based Standard Schema, such as an EffectSchema.Classadapted withSchema.toStandardSchemaV1. Tools can now use class-based schemas the same way asSchema.Struct. (#27317) -
Fixed agents stopping after a server tool whose
executereturnsundefined. The tool now records anullresult, and the agent includes the tool call and result in the next turn. (#27392) -
Fixed trace scoring so
type: 'agent'scorers receive the tool calls an agent made. Scoring a stored agent trace (for example withscoreTraceBatchorPOST /api/observability/traces/score) missed tool calls recorded under a model step. Tool calls and MCP tool calls are now included in the order they started. Calls made inside sub-agents or inside a tool are not credited to the parent agent. (#27390) -
Fixed
UnixSocketPubSubintermittently failing to start a broker when the socket path is a bare relative name likeevents.sockor./events.sock. The broker's random private socket name could look like a number (for example.28381e422682), which Node treated as a TCP port and rejectedsubscribe()withERR_SOCKET_BAD_PORTorENOENT. The private socket path now always keeps its./prefix. (#27894) -
Fixed durable and evented agents losing a suspended run when an
untilIdlestream is cancelled. Breaking out ofstreamUntilIdle()(or the request disconnecting) while the run waits for a tool approval or asuspend()resume now only stops reading, soresumeStreamUntilIdle()andapproveToolCall()work afterwards instead of failing with "No registry entry found for run … Cannot resume". Cancelling while a tool is still running still aborts the run, andresult.abort()still releases a suspended run. (#27563) -
Tools that require approval now validate their input before asking for approval. Previously an agent asked a person to approve a call whose arguments did not match the tool's
inputSchema, and the call then failed validation after it was approved. Now the validation error goes straight back to the model so it can retry, and notool-call-approvalis emitted. This applies toAgent, durable agents, and agent networks. Fixes #26771. (#26973) -
Security: bump the internal
@ai-sdk/provider-utils-v6alias from 4.0.40 to 4.0.56 to pick up the fix for AIKIDO-2026-526145. No public API changes. (#27023) -
Fixed deferred notifications running with no request context when the agent's delivery policy throws at delivery time. The delivery attempt now fails with the policy's error and is retried up to the attempt limit, instead of falling back to a bare wake that runs without a user. (#27729)
Fixed low-priority notification summaries failing to save when the agent's memory depends on who owns the thread. The summary is still saved without starting a run, but now carries the delivery policy's request context.
-
Fixed observation of unknown and completed durable agent runs so streams close promptly after replaying retained events, including when the requested offset skips the final event or a completed workflow snapshot remains after cleanup. Observers stay attached when lifecycle state cannot be read. (#27358)
-
Fixed a message sent from an
AgentControllersession being dropped when another agent was mid-run on the session's thread. The message now waits for that run to end and then runs. Pressing Stop while it waits drops it. If that run is waiting for an answer to its own prompt, the message isn't sent and the session shows an error saying so. A message sent while the thread is still held by a waiting run that was dropped from memory now waits for that hold to expire, then runs. Signals sent straight to the agent still report the thread as blocked. (#27624) -
Fixed goal judges and subagents hanging inside an Agent Controller session. Previously they inherited the session's memory thread and waited on it forever. Goal judges now use their own goal-specific thread. Forked subagents use the cloned thread. Non-forked subagents no longer inherit the session's thread and resource IDs. (#27323)
-
Boot-time workflow recovery no longer re-runs a workflow run that another live server instance is still executing. Previously, when one replica started while another was mid-run,
restartAllActiveWorkflowRuns()restarted everyrunningrun and its steps executed twice. (#27508)Workflow runs now hold the existing storage run-ownership lease (the one durable agents use) while they execute.
run.restart()throws aWORKFLOW_RESTART_RUN_OWNEDerror instead of taking over a live run. The boot sweep skips those runs and checks them again once the lease could have expired, so runs left behind by a crashed process are still recovered. Storage adapters without run-ownership support keep the previous behavior.
@mastra/acp@0.4.3
Patch Changes
- Fixed ACP prompts hanging when the agent stops with
max_tokens,cancelled,max_turn_requestsorrefusal.prompt(),AcpAgent.generate()and ACP tools now reject withACP prompt stopped before completing: <reason>.AcpAgent.stream()still returns a result, but itstextpromise rejects and readingfullStreamerrors with the same message. Reading onlyfullStreamno longer leaves an unhandled rejection. (#27927)
@mastra/astra@1.1.2
Patch Changes
- Simplified vector inclusion options while preserving boolean request values. (#27646)
@mastra/clickhouse@1.26.0
Minor Changes
-
Added
timeToFirstChunkMsto ClickHouse trace and span queries.queryTraces()returns the root span's time to first chunk andquerySpans()returns each span's, bothnullwhen no chunk was streamed. You can also filter on it: (#27277)where: { op: 'gt', left: { path: 'timeToFirstChunkMs' }, right: { literal: 1000 } }
-
ClickHouse trace and span queries can now sort by
timeToFirstChunkMs, slowest or fastest first. Traces and spans without a value come last. (#27473)orderBy: [{ field: 'timeToFirstChunkMs', direction: 'desc' }];
-
Added thread rows with previews, trace counts, activity timestamps, filters and cursor pagination. Legacy thread identity queries remain supported. (#27652)
Broad thread queries exceeding the retained-history limit return a resource-limit error. Narrower time ranges can still succeed.
import { planThreadRowsQuery } from '@mastra/core/storage'; await storage.queryThreads( planThreadRowsQuery({ timeRange: { traces: { from, to } }, }), );
-
Spans now store their time to first chunk in a new
timeToFirstChunkMscolumn, ready for filtering and sorting by how fast a model started responding. Existing databases get the column automatically when the store starts. It stays empty for spans written before the upgrade or with an older@mastra/core. (#27140)Also in: @mastra/duckdb@1.15.0, @mastra/pg@1.32.0
-
Added
querySpans()support for context and parent/root entity fields, including thread filtering: (#27272)where: { op: 'eq', left: { path: 'threadId' }, right: { literal: 'thread-123' } }
Breaking with the updated core contract:
querySpans()no longer acceptsmodelandproviderfilters or returnscost,inputTruncated,outputTruncatedanderrorTruncated.Reduced memory usage when querying spans and displaying previews for older data.
Patch Changes
-
Fixed ClickHouse trace queries counting or grouping a trace by an outdated copy of its root span. When a run was exported more than once (for example after resuming),
aggregateTraces()could count the same trace under bothstatus: errorandstatus: success, andqueryTraces()could list a trace by a filter that only its older copy matched. Every trace read now uses the root copy that ended last, before applying filters. (#27679) -
Added support for grouping and filtering by the root span's
nameandspanTypein the ClickHouse observability store.aggregateTraces()can group by both and count their distinct values, andqueryTraces(),aggregateTraces(), and trace-query value discovery can filter on or suggest them. Values come from each trace's current completed root span. The store declares thetrace-query-root-span-fieldsstorage feature. (#27321)await mastraClient.aggregateTraces({ timeRange, groupBy: ['spanType', 'name'], measures: ['count'] });
@mastra/client-js@1.56.0
Minor Changes
-
Breaking:
controller.session()takes a session id, and the resource moves tocreate(). Thescopeargument is removed. (#27336)// Before const session = client.getAgentController('code').session('repo-1', 'run-a'); await session.create(); // After const session = client.getAgentController('code').session('run-a'); await session.create({ resourceId: 'repo-1' });
create()without aresourceIdresumes the session on the resource it already has, or uses the session id as the resource for a new session, sosession('user-1').create()behaves as before. -
Added experimental methods for MCP server events: (#27674)
const { events } = await client.getMcpServerEvents('tickets'); const { events: occurrences, cursor } = await client.pollMcpServerEvent('tickets', 'ticket.created', { arguments: { project: 'web' }, }); const controller = new AbortController(); for await (const frame of client.streamMcpServerEvent( 'tickets', 'ticket.created', { cursor }, { signal: controller.signal }, )) { if (frame.type === 'event') console.log(frame.occurrence.data); } // Under `mastra dev` only await client.emitMcpServerEvent('tickets', 'ticket.created', { title: 'Test ticket' });
-
Added thread rows, activity sorting and direct filters to
queryTraceThreads(). (#27652)Breaking type change:
QueryTraceThreadsResultnow describes full thread rows. UseQueryTraceThreadsResult<typeof request>for legacy identity results.// Before await client.queryTraceThreads({ traces: { timeRange: { from, to } } }); // After await client.queryTraceThreads({ timeRange: { traces: { from, to } } });
-
Added context and parent/root entity fields to
querySpans()response types. (#27272)Breaking:
querySpans()response types no longer includecost,inputTruncated,outputTruncatedanderrorTruncated.
Patch Changes
-
Fixed client tools failing with
ToolNotFoundErrorafter a tool approval.approveToolCall,declineToolCall,approveToolCallGenerateanddeclineToolCallGeneratenow acceptclientTools(andclientToolsResolver) and send them to the server, so the model can call a client tool after the approval decision.approveToolCallanddeclineToolCallalso acceptmemory; pass the same thread and resource as the original call so client-tool results and the final answer are saved to that thread. (#27744)const stream = await agent.approveToolCall({ runId, toolCallId, clientTools, memory: { thread: threadId, resource: resourceId }, });
Also in: @mastra/server@1.79.0
-
Added typed support for the new agent controller thread ownership route (
POST /agent-controller/:controllerId/sessions/:resourceId/threads/:threadId/owner). (#27043)POST /agent-controller/code/sessions/resource-1/threads/thread-1/owner → { "ok": true, "ownerId": "user-123" }
-
Fixed a security alert when you install
@mastra/client-js. The package no longer depends on@ai-sdk/ui-utils, which included a vulnerable version of@ai-sdk/provider-utils. Streaming works the same as before. (#27297) -
Fixed
approveToolCall,declineToolCall, andresumeStreamloggingtool_result must be preceded by a tool_callon every resume. Client tools the model calls after a resumed tool call now run as expected. (#27207) -
Span types returned by the trace and span endpoints now include
timeToFirstChunkMs, the milliseconds from span start to the first streamed chunk. (#27140) -
Agent controller session subscriptions now detect silent connections instead of hanging forever. When a stream receives no data (including server heartbeats) for
idleTimeoutMs(default 60 seconds), it's treated as dropped, soreconnectandonReconnectre-establish it, oronErrorfires when reconnect is off. Opening a stream that never responds is bounded by the same limit. SetidleTimeoutMs: 0to disable. (#26904)const subscription = await session.subscribe({ onEvent: handleEvent, onReconnect: resync, reconnect: true, idleTimeoutMs: 60_000, });
-
Trace query requests (
queryTraces,aggregateTraces,querySpans,queryTraceThreads) no longer retry when the server reports a query execution timeout (504 with codeTRACE_QUERY_EXECUTION_TIMEOUT). Retrying re-ran the same expensive query against the same timeout and could make a failing call take over a minute. Other 5xx errors, including 504s from proxies or gateways, are still retried. (#27385) -
Added
modelListEditableto agent details so clients can disable model-list editing when an agent resolves its models dynamically. Studio now shows function-valued model lists as read-only while keeping static model lists editable. (#27192)const agent = client.getAgent('weather-agent'); const details = await agent.details(); if (details.modelListEditable === false) { // Hide or disable model-list editing controls. }
Also in: @mastra/server@1.79.0
-
Trace queries, trace aggregates, and thread queries now return a 422 when they use the root span's
entityIdat trace scope and the configured observability store does not support it, instead of failing with a 500. For those stores, trace-query field discovery also omits trace-scopeentityIdfrom its field list andaggregate.dimensions. A newtraceQueryRootEntityIdcapability reports whether the store supports it. Span-scopeentityIdinsidespans.somekeeps working on every store. (#27684)const { capabilities } = await mastraClient.getObservabilityCapabilities(); if (capabilities.traceQueryRootEntityId) { await mastraClient.aggregateTraces({ timeRange, groupBy: ['entityId'], measures: ['count'] }); }
Also in: @mastra/server@1.79.0
-
Trace queries, trace aggregates, and thread queries now return a 422 when they use the root span's
nameorspanTypeand the configured observability store does not support them, instead of failing with a 500. For those stores, trace-query field discovery also omits both fields from its field list andaggregate.dimensions. A newtraceQueryRootSpanFieldscapability reports whether the store supports them. (#27321)const { capabilities } = await mastraClient.getObservabilityCapabilities(); if (capabilities.traceQueryRootSpanFields) { await mastraClient.aggregateTraces({ timeRange, groupBy: ['name'], measures: ['count'] }); }
Also in: @mastra/server@1.79.0
-
The schedules HTTP API now accepts and returns
ifIdle.requireClaimedOwneron agent schedules. Before this,POST /schedulesandPATCH /schedules/:scheduleIddropped the flag, so a client that read a schedule and sent itsifIdleback cleared it. (#27896)await client.createSchedule({ agentId: 'support', cron: '0 9 * * *', prompt: 'Morning check-in', threadId: 'thread-1', resourceId: 'user-1', ifIdle: { behavior: 'wake', requireClaimedOwner: true }, });
Also in: @mastra/server@1.79.0
-
Fixed client tools that run after
approveToolCall/declineToolCallon an agent without memory. The follow-up model call now sees the whole conversation: the original user message, the approved (or declined) tool call and its result. Before, it saw only the client tool call and its result. (#27772)approveToolCallGenerateanddeclineToolCallGeneratenow run executable client tools the model calls after the decision, and then continue the conversation, the same waygeneratedoes. Before, they returned withfinishReason: 'tool-calls'and never ran the client tool.Pass
memorytoapproveToolCallGenerate/declineToolCallGenerateto save that continuation to the thread:await agent.approveToolCallGenerate({ runId, toolCallId, clientTools, memory: { thread: threadId, resource: resourceId }, });
-
The Agent Controller tool-approval endpoint now accepts
alwaysAllowCategory: true, which approves the call and grants its tool category to the call's thread, the same way "always allow" works in an in-process session. Pass it from@mastra/client-jswithsession.approveTool(toolCallId, true, { alwaysAllowCategory: true }). (#27222)Also in: @mastra/server@1.79.0
-
Added
timeToFirstChunkMsto thequeryTraces()andquerySpans()result types, and thetraceQueryTimeToFirstChunkflag togetObservabilityCapabilities(). (#27277)const { capabilities } = await mastraClient.getObservabilityCapabilities(); if (capabilities.traceQueryTimeToFirstChunk) { const { spans } = await mastraClient.querySpans({ timeRange, where: { op: 'gt', left: { path: 'timeToFirstChunkMs' }, right: { literal: 2000 } }, }); }
-
Added
spanQueryContextFieldsto observability capability response types. (#27807) -
queryTraces()andquerySpans()now accepttimeToFirstChunkMsinorderBy, andgetObservabilityCapabilities()returns thetraceQueryTimeToFirstChunkSortflag. (#27473)const { capabilities } = await mastraClient.getObservabilityCapabilities(); if (capabilities.traceQueryTimeToFirstChunkSort) { const { spans } = await mastraClient.querySpans({ timeRange, orderBy: [{ field: 'timeToFirstChunkMs', direction: 'desc' }], }); }
@mastra/code-sdk@1.15.0
Minor Changes
-
Added an
outputProcessorsoption tocreateMastraCode. Output processors you pass run after Mastra Code's built-in ones and before memory saves the reply, so changes they make to response messages are persisted. (#27162)const mastraCode = await createMastraCode({ outputProcessors: ({ requestContext }) => [new ReplyLabelProcessor(requestContext)], });
-
Added a Factory client for driving a remote Factory session.
RemoteSessionandRemoteControllermirror a Factory session's state over its event stream and forward messages, approvals, and mode/model switches over HTTP. Platform helpers sign in to Mastra Platform and discover the organizations, projects, and environments to connect to. (#27222)import { createFactoryClient } from '@mastra/code-sdk/factory/client'; import { RemoteSession } from '@mastra/code-sdk/factory/remote-session'; import { getPlatformSession } from '@mastra/code-sdk/platform/index'; const session = await getPlatformSession(); const client = createFactoryClient({ url: 'https://my-factory.factory.mastra.cloud', session });
Requests that fail with an expired Platform token refresh it once and retry.
-
SandboxFilesystemnow uses the sandbox's nativefilesystemwhen it has one. File reads, writes, stats, and listings take one request instead of two or three shell commands, and every file operation goes through it.grepand file walks still run as shell commands. Paths with..are still rejected. (#27859)On the native path, the in-sandbox symlink check is skipped by default, since an agent with a shell can read any file anyway. Set
verifySymlinks: truewhen you serve files to people who can't run commands; each path is then checked once per instance:const fs = new SandboxFilesystem({ sandbox, workdir: '/workspace/repo', verifySymlinks: true });
-
Added
resolveDefaultThinkingLevelso a host can supply the thinking level for requests whose session has not picked one. When set, it replaces the settings.json defaults; a session's own level still wins. It runs on every model resolution, including account-rotation hops, so a changed default applies to the next request. (#27216)const { controller } = await createMastraCodeAgentController({ resolveDefaultThinkingLevel: async ({ requestContext }) => lookUpTeamThinkingLevel(requestContext), });
Patch Changes
-
Fixed ACP turns that an internal error ended returning
stopReason: "cancelled"when the client never cancelled.session/promptnow returns a JSON-RPC error with the underlying message, so clients can tell a failure apart from a cancel. (#27438) -
Background task completions and notification wake-ups now find their session by session id, so they keep reaching the right session when several sessions share one resource. (#27336)
-
Fixed the local trace store (
observability.duckdb, used when/observability local on) growing without limit. Each session now deletes traces, logs and metrics older than 14 days and scores and feedback older than 30 days in the background, capped per session so startup is never blocked. (#27506) -
Fixed sandbox workspace search staying on the slow fallback for the whole session when its first search ran before the sandbox was ready. A failed search-tool check is now retried on the next search. (#27695)
-
Credential resolution failures (for example a run with no signed-in user) are now logged as errors through the Mastra instance's logger, so they appear in deployed logs. The missing-user error also includes the session and thread ids and the names of the request context entries, so the code path that started the run can be identified. (#27376)
-
Factory sessions now always use thread-scoped observational memory. Each run and chat keeps its own observations, and recall search still reaches every thread under the Factory or user resource. Session state, the
MASTRA_OM_SCOPEenvironment variable, and.mastracode/database.jsonno longer change the scope for Factory sessions. Mastra Code outside Factory resolves the scope as before. (#27337) -
Fixed notifications for the thread you have open being treated as belonging to another project when the thread's recorded project path differs from the current checkout. Such notifications now run in your session instead of failing with "No model available". If a Mastra Code process for the thread's own project is also running, only one of the two delivers each notification. (#27634)
-
Fixed "build a workflow" requests never finishing in Mastra Code. The workflow builder no longer inherits the chat session's memory thread, so it stops waiting on the session run that is waiting on it. (#27323)
-
Added
normalizeLegacyProviderIdto@mastra/code-sdk/providers/model-ids. It maps a provider id Mastra Code once defined itself to the catalog id models.dev uses, so a reply's provider can be matched against the catalog. (#27162)import { normalizeLegacyProviderId } from '@mastra/code-sdk/providers/model-ids'; normalizeLegacyProviderId('kimi-for-coding'); // 'kimi-code-plan-cn' normalizeLegacyProviderId('anthropic'); // 'anthropic'
-
Fixed headless
--output jsonlruns writing gigabytes of repeated data. The stream included adisplay_state_changedevent after almost every other event, and each one carried a full copy of the session's display state, including every large tool result and all shell output so far. Long runs could fill the disk. (#26881)--output jsonlno longer writesdisplay_state_changedevents. Every other event, and the finalresultline, is written as before. The omitted snapshots only repeated what the other events already carry. Consumers that rebuilt UI state from these snapshots should fold the remaining events instead.--output json,--output human, and programmaticrunMC()iteration are unchanged. -
Fixed a
prune() failed for domain "observability"error being logged the first time local tracing opened a newobservability.duckdbfile. Mastra Code now skips the startup prune until the trace tables exist; the next session prunes as usual. (#27517) -
Auto memory now picks Gemini only when Mastra Code can use a Google credential for the request: a stored key or sign-in, or locally a
GOOGLE_GENERATIVE_AI_API_KEYorGOOGLE_API_KEYenvironment variable. Deployed Factory accounts never use the server's environment keys, so they get a model from a provider they connected. (#26666) -
/omnow shows the same Auto memory model that runs use. Before, the preview only checkedGOOGLE_GENERATIVE_AI_API_KEY, so withGOOGLE_API_KEYor a Google key saved withmastracode loginit showed Claude Haiku while runs used Gemini Flash. (#27449) -
Fixed foreground commands in Mastra Code to stop after 30 seconds unless the command specifies a timeout. Set timeout to 0 to disable the limit. Background processes are unaffected. (#26878)
-
Improved content search in sandbox workspaces without ripgrep. When the sandbox's GNU grep and git support PCRE, patterns run with
grep -Pandgit grep -P, so\d,\w,\s,\b, and lookarounds are searched in one command instead of falling back to reading files one by one. (#27698) -
Fixed headless runs reporting success while a tool was still waiting for an answer. When a tool such as
request_accesspaused the run,runMCstopped withstatus: 'completed'and exit code0, so the answer from the permission policy was never applied. Headless runs now wait for the paused run to stop, send the policy's answer, and keep running until the resumed run finishes. If a paused tool can't be resumed, the run ends withstatus: 'error'and exit code1instead of reporting success. (#26883) -
Fixed headless runs cutting off their output when stdout is a pipe. The CLI exited before Node finished writing, so a large final result could reach the reader incomplete while the process still exited with code 0. Headless mode now waits for stdout and stderr to flush before exiting. If the output can't be delivered because the reader closed the pipe, a run that would otherwise succeed exits with code 1 instead of 0. A run that already failed keeps its exit code. (#26880)
-
Improved sandbox grep reliability and diagnostics. When
rgorgrepfails inside the sandbox, the error now says why, for example a timeout or an exit code with the first stderr line. Previously every failure was reported as an unsupported pattern. The POSIXgrepfallback now skips.git, and it keeps matches when only some files could not be read. (#27464) -
Scheduled prompts that fire into a session no longer held live in this process now run as that session's owner. Hosts that resume sessions (
resumeWakeSession) bring the owner's session back first; if no owner can be found, the fire fails withNo session owns resource … to run its wake on thread …before any run starts, instead of starting a run with no signed-in user that failed with "Tenant model credential policy could not be resolved: the request has no authenticated user." Only hosts with schedule tools enabled are affected. (#27780) -
Made content search in sandbox workspaces much faster when ripgrep isn't installed. Searches now run as a single GNU grep command with context lines, using git to respect every repository's
.gitignore, instead of reading files one by one. With ripgrep, searches also respect.gitignorefiles now. Both skipnode_modulesunless the search path points inside it. File listings in sandbox workspaces also skipnode_modulesinside the sandbox instead of returning it to be filtered out. (#27687) -
Fixed the cross-agent signal sender telling the model to resend a signal that failed direct delivery. The signal waits in the recipient's inbox and is retried automatically, so the sender now tells the model not to resend it. A resend while that signal is still pending merges into it; a resend after it is delivered sends the recipient a second copy. (#27347)
-
Added Extra high thinking to xAI models that list it, such as Grok 4.6. (#26530)
import { getModelReasoningOptions } from '@mastra/core/llm'; import { getAvailableThinkingLevelsForModel } from '@mastra/code-sdk/thinking'; getAvailableThinkingLevelsForModel('xai/grok-4.6', getModelReasoningOptions('xai/grok-4.6')); // ['off', 'low', 'medium', 'high', 'xhigh']
-
Mastra Code sessions no longer load web search providers, the Postgres store, the local trace store (DuckDB), the MCP client, local embeddings or voice SDKs until a feature needs them, which cuts startup memory by about 90 MB. (#27680)
-
Added a
resumeWakeSessionoption tocreateMastraCode. It runs when a notification is due on a thread whose session isn't open in this process, so the host can bring that session back online as its owner before the wake runs. If no session owns the thread afterwards, or the hook throws, the delivery fails with that error instead of waking with no user. (#27729)Pair it with
prepareWakeRequestContext, which attaches the owner's identity to the wake itself:const code = await createMastraCode({ resumeWakeSession: async ({ resourceId, threadId }) => { const owner = await lookupSessionOwner(resourceId); if (!owner) throw new Error(`Session ${resourceId} has no known owner`); const requestContext = new RequestContext([['user', owner]]); await code.controller.createSession({ id: resourceId, resourceId, ownerId: owner.id, threadId, requestContext }); }, prepareWakeRequestContext: async ({ requestContext, resourceId }) => { requestContext.set('user', await lookupSessionOwner(resourceId)); }, });
-
Fixed woken notifications running in the wrong project when Mastra Code processes in different directories share one resource id. A due notification now runs as the session whose project the thread belongs to, and is delivered by a process running in that project: each project gets its own notification dispatch turn on the shared resource, so a process no longer runs another project's thread with its own workspace and tools, and no longer leaves it waiting until the process holding the resource exits. (#27426)
@mastra/connect@1.1.0
Minor Changes
-
Added Microsoft Teams as a supported Connect channel. When MASTRA_ENCRYPTION_KEY is unset, the Teams install store is encrypted with a key derived from the platform access token and a warning is logged; projects without a Teams connection are unaffected. (#27094)
-
Added end-user (per-resource) connections to @mastra/connect. (#26923)
Scope tools to an end user
tools()accepts a newresourceIdoption — a static key or a callback derived per request — so an agent resolves the shared global connections plus that end user's own connections. Every platform call made under a resource scope asserts the key, so one user's connection can never serve another user's request.import { tools } from '@mastra/connect'; // Per-request scoping on an agent: new Agent({ tools: ctx => tools({ resourceId: myUserIdFrom(ctx) }) }); // Static/global usage is unchanged: new Agent({ tools: tools({ providers: ['linear'] }) });
Calling
tools()per request is cheap: identical options share one internal snapshot/MCP-client state, with per-resource caching and eviction. The returned resolver is also awaitable, so a dynamictoolsargument can return it directly.Let end users connect their own accounts
A new
userConnectRoutes()export returns mountable API routes (provider catalog, the caller's connections, connect sessions, status polling, disconnect, and a hosted connect page) gated on the authenticated end user's resource key fromserver.auth.mapUserToResourceId.import { Mastra } from '@mastra/core'; import { userConnectRoutes } from '@mastra/connect'; export const mastra = new Mastra({ server: { auth: { authenticateToken: async token => verifyToken(token), mapUserToResourceId: user => user.id, }, apiRoutes: [...userConnectRoutes({ providers: ['linear', 'slack'] })], }, });
credential()also gains an optionalresourceIdoption for asserting a resource-scoped connection's key. -
Added a callback form to tools() for per-request options. Pass a function that reads the request context and returns ToolsOptions to vary tool filters and approval policies per request (for example by user role), without losing the snapshot cache: resolutions producing the same options share one cached snapshot with the usual TTL semantics. (#27160)
Before (per-request resolver, re-fetches every request):
tools: async ctx => { const role = ctx.requestContext?.get('role'); return tools({ providers: { linear: role === 'viewer' ? { allowTools: ['linear_list_*'] } : { requireApproval: ['linear_delete_*'] }, }, })(ctx); };
After (one resolver, cache intact):
tools: tools(ctx => ({ providers: { linear: ctx?.requestContext?.get('role') === 'viewer' ? { allowTools: ['linear_list_*'] } : { requireApproval: ['linear_delete_*'] }, }, }));
Patch Changes
-
Fixed project-level Connect activity showing empty on the Mastra platform. @mastra/connect now sends its configured project id (MASTRA_PROJECT_ID or the projectId option) with every proxied provider request, so the platform can attribute tool and channel activity to the right project. (#26932)
import { tools } from '@mastra/connect'; // Uses MASTRA_PROJECT_ID by default; pass projectId to override. const agentTools = tools({ projectId: 'proj_123' });
-
Google Drive (#27156)
- Fixed
google_drive_create_folder,google_drive_upload_document, andgoogle_drive_move_filefailing with 404 errors on shared drives. Agents can now write to team shared drives. - Added
google_drive_export_filefor exporting Docs, Sheets, and Slides to PDF and other formats. - Added
google_drive_list_revisionsfor listing a file's revision history.
const toolset = await tools({ providers: ['google-drive'] })(); await toolset.google_drive_export_file.execute({ fileId: '1Kj3d86Z-Sfd56YP4dImQ-ggMRyP2QZ_BRO33zOO224c', mimeType: 'application/pdf', });
Fireflies
- Fixed
fireflies_get_transcriptfailing validation on every transcript. Fireflies returns numericspeaker_id,start_time, andend_timevalues that the schema only accepted as strings; the tool now accepts and normalizes them.
- Fixed
-
@mastra/mcpand@mastra/connectnow require@mastra/core1.79.0 or later. Upgrade@mastra/coretogether with them: (#27674)# Before: @mastra/core 1.78.x with an older @mastra/mcp npm install @mastra/mcp@latest # peer dependency error # After npm install @mastra/core@latest @mastra/mcp@latest @mastra/connect@latest
Also in: @mastra/mcp@2.5.0
@mastra/convex@1.7.5
Patch Changes
- Fixed restarted
foreachruns with nested workflows re-running already completed child steps when using Convex storage. (#26368)
@mastra/datadog@1.4.18
Patch Changes
- Simplified agentless-mode defaults while preserving configuration and environment handling. (#27646)
@mastra/deployer@1.79.0
Patch Changes
-
Fixed generated servers exiting immediately instead of shutting down gracefully on Ctrl-C under
mastra dev,mastra start, or another tool that forwards the signal. The server receives Ctrl-C twice in a few milliseconds, once from the terminal and once from the parent, and treated the second as a request to force-quit. A second signal within 250ms of the first is now ignored, so in-flight requests drain and Mastra shuts down cleanly. Pressing Ctrl-C again after that still exits immediately. (#26986) -
Fixed
mastra buildandmastra devfailing in monorepos where multiple workspace packages share code. (#27204) -
Servers built with
mastra buildnow start model usage telemetry before serving requests, so shutdown reports include every model call the server handled. (#26986) -
Fixed dev and build failures when projects use TypeScript 7. (#27840)
-
Removed the
find-workspacesandgray-matterdependencies. Monorepo workspace discovery now usestinyglobbyand markdown frontmatter is parsed by a helper shared with@mastra/core. This removes the vulnerablebraces(GHSA-vfj7-8cjw-p6xm) andsprintf-js(GHSA-hp3w-g68c-fv3c) packages, neither of which has a patched release, from the production dependency tree of@mastra/deployer.npm auditno longer reports them for this package. (#27198) -
Fixed
mastra devfailing when aserver.apiRouteshandler orbundler/deployeroption imports a workspace package that ships raw TypeScript. These packages are now inlined into the extracted config instead of being left as imports that Node cannot load. Their own dependencies still resolve from the workspace package, so dependencies installed only for that package (for example with pnpm) keep working. (#27303) -
Agent browser streaming now requires authentication. The browser WebSocket stream (
/browser/:agentId/stream), the session probe, and the close endpoint previously accepted unauthenticated requests; they now require the same authentication as the rest of the server. (#26112)Requests without a valid credential for the server auth provider configured on
Mastraare denied with401.Browser WebSocket upgrades authenticate with the session cookie that same-origin requests already send, so signed-in Studio sessions keep working. A WebSocket handshake cannot set an
Authorizationheader, so a client that authenticates with a header first exchanges its credential for a single-use upgrade ticket over an authenticated HTTP request:const response = await fetch(`${apiUrl}/agents/my-agent/browser/stream-ticket`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }), }); const { ticket } = await response.json(); const ws = new WebSocket(`wss://example.com/browser/my-agent/stream?threadId=${threadId}&ticket=${ticket}`);
The ticket is opaque, expires after 30 seconds, is bound to the agent and thread it was issued for, and is consumed by the first upgrade it authenticates. The credential is only ever presented to the mint endpoint, so it never reaches a proxy or an access log, and each connection attempt mints a fresh ticket.
Issuance is bounded so the mint endpoint cannot be used to exhaust the server: each agent may request a limited number of tickets per minute and the process holds a capped number of unspent tickets, beyond which the endpoint answers
429instead of allocating more.For clients that cannot call the mint endpoint,
?apiKey=remains available as a handshake-only fallback:const ws = new WebSocket(`wss://example.com/browser/my-agent/stream?threadId=${threadId}&apiKey=${token}`);
A token in a URL can be retained in proxy and access logs, so prefer the ticket flow, and pass a short-lived token if you use this fallback. The mint endpoint, the session probe, and the close endpoint also accept the token through the
Authorizationheader.Studio's browser view follows the same rule: when Studio is configured with an
Authorizationheader it requests an upgrade ticket for each connection, so a deployment that authenticates with a header keeps its live view instead of losing it to a401. Deployments that sign users in with a session cookie are unaffected.setupBrowserStreamalso accepts an optionalallowedOriginsallowlist, in the same shape as Hono'scors.origin, and refuses an upgrade from any other origin with403before authentication runs. Upgrades from the server's own origin are always allowed, since a CORS allowlist names other origins and Studio is often served by the same server; this check compares the host and not the scheme. The deployer forwardsserver.cors.originwhen it is explicitly configured.Direct callers of
setupBrowserStream: the config now requires themastrainstance so the browser routes can be authenticated.Before:
await setupBrowserStream(app, { getToolset });
After:
await setupBrowserStream(app, { mastra, getToolset });
Also in: @mastra/hono@1.7.21
-
Fixed
mastra buildfailing with"<export>" cannot be exported from ".mastra/.build/<pkg>.mjs" as it is a reexport that references itselfwhen the app only reaches a workspace package through another workspace package, and those packages don't declare aversion. (#27739) -
mastra buildnow succeeds when your app imports a private workspace package that has noversionfield. (#27057)
@mastra/deployer-netlify@1.2.38
Patch Changes
- Netlify installs now apply
bundler.pnpmWorkspaceSettingsand packed workspace dependencypnpmOverridesto the generated pnpm workspace, for both edge and serverless targets. (#27708)
@mastra/docker@0.12.0
Minor Changes
-
Added build progress reporting to
DockerTemplate.build(). PassonProgressto receive step starts and completions (including cache hits), raw command output, and areusedevent when an existing image is used. Failed results now includefailedStep, the most recentoutput(capped bymaxOutputBytes, default 64 KiB), andoutputTruncated.DockerSandboxacceptsonTemplateBuildProgressto follow template builds triggered bystart(). (#26682)const result = await template.build({ onProgress: event => { if (event.type === 'step-start') console.log(event.step.name, event.step.cached ? '(cached)' : ''); }, }); if (result.status === 'failed') console.error(result.failedStep?.name, result.output);
Patch Changes
- Fixed
kill()on Podman. Before,kill()returnedfalseand the process group kept running, so command timeouts, aborts and background process kills had no effect. The kill helper now attaches its output streams, which Podman's Docker-compatible API requires. Kills now stop the process group on both Docker and Podman. (#27292)
@mastra/duckdb@1.15.0
Minor Changes
-
Added
querySpans()support for additional context filters and response fields, including thread filtering: (#27272)where: { op: 'eq', left: { path: 'threadId' }, right: { literal: 'thread-123' } }
Breaking with the updated core contract:
querySpans()no longer acceptsmodelandproviderfilters or returnscost,inputTruncated,outputTruncatedanderrorTruncated.Parent/root entity type, ID and name, and
executionSourcereturnnull. Equality filters on these fields match no spans. -
Added thread rows with previews, trace counts, activity timestamps, filters and cursor pagination. Legacy thread identity queries remain supported. (#27652)
import { planThreadRowsQuery } from '@mastra/core/storage'; await storage.queryThreads( planThreadRowsQuery({ timeRange: { traces: { from, to } }, }), );
Also in: @mastra/pg@1.32.0
-
Added time to first chunk to DuckDB trace and span queries.
queryTraces()returns the root span'stimeToFirstChunkMsandquerySpans()returns each span's, ornullwhen no chunk was streamed. Both accepttimeToFirstChunkMsfilters. (#27277)where: { op: 'gt', left: { path: 'timeToFirstChunkMs' }, right: { literal: 2000 } }
-
DuckDB trace and span queries can now sort by
timeToFirstChunkMs, slowest or fastest first. Traces and spans without a value come last. (#27473)orderBy: [{ field: 'timeToFirstChunkMs', direction: 'desc' }];
Patch Changes
-
Added support for grouping and filtering by the root span's
nameandspanTypein the DuckDB observability store.aggregateTraces()can group by both and count their distinct values, andqueryTraces(),aggregateTraces(), and trace-query value discovery can filter on or suggest them. Values come from each trace's current completed root span. The store declares thetrace-query-root-span-fieldsstorage feature. (#27321)await mastraClient.aggregateTraces({ timeRange, groupBy: ['spanType', 'name'], measures: ['count'] });
@mastra/e2b@0.15.0
Minor Changes
-
E2BSandbox.filesystemreads and writes the sandbox's disk through E2B's files API. Each operation starts the sandbox if needed and retries once when E2B paused or killed it server-side. (#27859)const sandbox = new E2BSandbox(); await sandbox.filesystem.writeFile('/home/user/notes.md', '# Notes'); const entries = await sandbox.filesystem.readdir('/home/user');
Copies, appends, and writes or moves with
overwrite: falserun as one command inside the VM, so they're atomic and file contents don't travel through your server.
@mastra/elysia@0.1.19
Patch Changes
-
Fixed
/api/openapi.jsoncontaining$refs that point nowhere. Recursive (z.lazy) schemas and schemas tagged with.meta({ id })used to emit#/definitions/...or#/$defs/...refs that broke once inlined into the document. Those definitions now go intocomponents.schemasand the refs point there, so tools like Swagger UI, swagger-parser, and OpenAPI client generators can load the spec. This covers both built-in and custom routes. When two different schemas share an id, the second one gets a suffixed name (for exampleInvoice_2) and neither overwrites the other. (#27745)Also in: @mastra/server@1.79.0
@mastra/evals@1.10.8
Patch Changes
-
Fixed the code tool-call accuracy scorer returning 0 in non-strict mode when
expectedToolOrdercontains the same tool more than once (for examplesearch → inspect → search). Each expected tool now matches a separate call, and extra calls between matches are still allowed. (#27483) -
Fixed
noRedundantCallson trajectories built from traces. Two identical tool calls in a row are now flagged even whenprocessor_runormodel_generationsteps sit between them, so scoring a run from its trace gives the same result as scoring it from its messages. (#27316) -
Fixed
blacklistedSequencesandmaxRetriesPerToolon trajectories built from traces. Forbidden tool sequences and tool retries are now detected even whenprocessor_runormodel_generationsteps sit between the tool calls, so scoring a run from its trace gives the same result as scoring it from its messages. (#27387) -
Fixed trajectory scorers giving wrong results on trajectories built from traces. Traces wrap tool calls in
model_generationsteps and putprocessor_runsteps around them, so a correct run scored 0 for accuracy,maxStepscounted wrapper steps, and forbidden sequences and tool retries were missed. All trajectory checks (compareTrajectories,checkTrajectoryEfficiency,checkTrajectoryBlacklist,analyzeToolFailures, the code trajectory scorers, and the LLM trajectory judge) now read tool calls the same way for trace and message trajectories, so a run gets the same score either way. (#27487)Token budgets still read
model_generationsteps, and expectations that namemodel_generationorprocessor_runsteps are still compared against the raw trajectory.maxStepsand theindexinredundantCallsnow count tool-level steps on trace trajectories.The new
toToolLevelStepshelper returns this tool-level view if you need it in a custom scorer.import { toToolLevelSteps } from '@mastra/evals/scorers/utils'; const toolNames = toToolLevelSteps(trajectory.steps).map(step => step.name);
Retries are now grouped across
model_generationsteps of the same run, as they are on message trajectories. Tool calls in separate sub-agent runs are still counted separately.
@mastra/factory@0.24.0
Minor Changes
-
Factory user sessions can now start sub-sessions and hear back from them. (#27103)
factory_start_sessionstarts a side session with a prompt, or a role on a work item (like pressing Start, without moving the card). New sessions run as the caller, with the caller's credentials, and the caller follows the work item.- Spawned sessions report with
factory_message_origin. A side session reports to the session that started it; a work-item session reports to everyone following the item.failedandblockedreports are urgent,doneis high priority, andprogressupdates replace each other while unread. - Every run of a work session or side session ends with exactly one final report, however the run started (the board, a typed message, a wake, or after a restart): if the agent doesn't report, Factory sends
done,failed, orblockedwhen the run ends, including when it stops waiting for someone to approve a tool call. - When a work session or side session holds a tool call for approval, whoever follows it gets an urgent
approvalnotification right away and can approve or decline it withfactory_answer_approval. - Followed items send a low-priority notice to running sessions when they change stage.
- A
<factory-sessions>state signal shows a session the side sessions it started and the work items it follows, once there is something to show.
-
Moved the thinking level default onto each Factory. Organization admins set it under Settings → Models → Factory defaults, and every session and work-item run in that Factory uses it until it picks its own level. Every Factory, including existing ones, starts at medium after upgrading. (#27216)
Why: the old default lived in the server's settings.json, so one value was shared by every Factory on the server, and with sign-in turned on it could only be changed after setting
MASTRACODE_DEPLOYMENT_ORGANIZATION_ID.Removed: the
GET /web/config/thinkingandPUT /web/config/thinkingroutes and theMASTRACODE_DEPLOYMENT_ORGANIZATION_IDvariable. Set the level on the Factory instead:# Before curl -X PUT "$FACTORY_URL/web/config/thinking" -H 'Content-Type: application/json' -d '{ "globalDefault": "high" }' # After curl -X PATCH "$FACTORY_URL/web/factory/projects/$FACTORY_ID" -H 'Content-Type: application/json' -d '{ "defaultThinkingLevel": "high" }'
A level saved in settings.json (global or per mode) is no longer read by Factory sessions, so if you set one there, set it again on each Factory. The per-chat "Chat defaults" row is also gone; a level picked there still overrides the Factory default for the chats that used it, so run
/think defaultin those chats to follow the Factory default. -
Treat linked Slack conversations as User Sessions, with interactive User Session tools authorized as the linked sender, including other participants in an organization-visible session. Private sessions remain owner-only. (#27685)
Before: a linked teammate replying in a Slack session could not use User Session management tools without acting as the session owner. After: they can ask, for example, “Switch my other session to openai/gpt-6-luna,” and
factory_switch_session_modelchecks their own session ownership rather than the Slack session owner's. -
Added a built-in MCP server to Factory at
/web/factory/projects/:projectId/mcp. MCP clients such as ChatGPT can sign in with OAuth and use the Factory tools (work items, sessions, decisions, approvals, plussearchandfetch) with the same permissions as the web UI. Factories on Mastra Platform auth sign users in through Platform automatically; other auth providers setmcp.authorizationServers(orMASTRA_FACTORY_MCP_AUTHORIZATION_SERVER). Passmcp: falseto turn it off. (#27228)new MastraFactory({ // ... mcp: { authorizationServers: ['https://your-tenant.authkit.app'] }, });
-
Factory sessions now share memory. A Factory's work-item runs, side sessions, and Slack channel threads store their threads under the Factory id. The sessions a person opens, and their Slack DMs, store theirs under that person's user id, so a DM is never shared with the Factory's runs. Factory sessions always use thread-scoped observational memory, and session state,
MASTRA_OM_SCOPE, and `.mastracod