Highlights
Span Query API (list completed spans across traces)
New core/server/client support lets you query individual completed spans (with filters, cursors, previews, and model cost) via storage.querySpans() / client.querySpans() / POST /api/observability/spans/query, enabling workflows like “show every failed tool_call in the last hour” without first finding traces.
Trace Aggregation with token + cost analytics (now across stores)
aggregateTraces() now supports token and cost measures (tokens.* sums/avgs and cost.sum/cost.avg with coverage + currency handling), and is implemented in the ClickHouse, DuckDB, and Postgres observability stores—making cost/tokens-first dashboards and “top spenders” queries a first-class capability.
Self-embedding vector stores for Semantic Recall (no client embedder required)
Semantic recall can now run against vector stores that generate embeddings themselves (MastraVector.isSelfEmbedding), including MongoDBVector via autoEmbed, so memory recall and message writes work without configuring a client-side embedder.
@mastra/connect 1.0: new channels/providers capabilities (Teams, Discord credential security) + better observability
@mastra/connect reaches stable with a reworked provider API (providers allowlists, glob filters, top-level defaults, dynamic tool merging), adds Microsoft Teams to channels(), moves Discord to a single API-key bot-token credential stored in encrypted credentials, and now records connect tool calls as trace spans for end-to-end observability.
Sessions now persist one active model per thread + optional “no initial thread”
AgentController sessions now keep a single current model across mode switches (updated switch APIs and thinking-level persistence), and you can start sessions without creating a thread (createInitialThread: false / createSession({ createInitialThread: false }) + session.thread.ensureId()), reducing empty-thread clutter.
Breaking Changes
@mastra/connect@1.0.0: discovered MCP tools no longer require approval by default; userequireApproval(replacesautoApproveTools) to opt in.@mastra/connect: API renameintegrations→providers; removed legacy surfaces like theconnect()alias oftools(),environment()sandbox credential surface, and several resolver helpers/exports.- Slack channel integration id changed in
channels()fromslack→slack-channels(Slack tools remain onslack). - AgentController model switching: removed
modeId/scope;session.model.switchsignature changed toswitch(modelId, options?)and sessions persist one model per thread (mode switches no longer auto-change models). @mastra/reacthooks: positional arguments replaced with a single object argument; all hooks acceptqueryOptions(TanStack Query), and hooks no longer guard on empty ids by default.
Changelog
@mastra/core@1.75.0
Minor Changes
-
Added the
StorageMemoryReftype so a stored agent'smemoryfield can reference a registered memory instance with{ type: 'id', memoryId }, or hold an inline config. Existing inline configs are still accepted. (#25850)Also in: @mastra/client-js@1.52.0, @mastra/server@1.75.0
-
Added support for several processes sharing one notification store, where only some of them can run a given thread. (#25741)
dispatchDueNotificationsaccepts aresourceIdto dispatch only that resource's due notifications. A delivery policy can returnhold: trueto leave a due notification pending for another dispatcher, without counting a delivery attempt.holdonly applies when a due notification is dispatched. When a notification is first sent, return an action that does not deliver it, such asdefer:import { defaultNotificationDeliveryDecision } from '@mastra/core/notifications'; const agent = new Agent({ // ... notifications: { deliveryPolicy: { decide: input => canRunHere(input.record.resourceId) ? defaultNotificationDeliveryDecision(input) : { action: 'defer', deliverAt: input.now, hold: true }, }, }, });
-
Added an explicit reconcile step for channel installations whose connect flow finishes outside the app (for example Discord's bot invite, which never redirects back). Channel providers can implement the new optional
reconcileInstallation(agentId)method, exposed overPOST /api/channels/:platform/:agentId/reconcileandclient.channels.reconcileInstallation(platform, agentId). The route requires the same write access as connecting, and returns the agent's fresh installation — ornullwhen the platform doesn't support reconciliation. Listing installations is now a pure read and never changes state. (#25993)const installation = await client.channels.reconcileInstallation('discord', 'my-agent'); // { id, platform, agentId, status: 'active', ... } once the invite completed
Also in: @mastra/client-js@1.52.0, @mastra/server@1.75.0
-
Added optional
title,websiteUrl, andiconsoptions toMCPServerConfig, exposed onMCPServerBase, so MCP server implementations can announce a display title, website, and icons to clients. (#25985) -
Added token and cost measures to
aggregateTraces(). Requests can now ask fortokens.input,tokens.output,tokens.total,tokens.reasoning, andtokens.cached(each as.sumor.avg), pluscost.sumandcost.avg, and use them inhavingandorderBy. (#25735)Usage is summed per trace first, then per group. Averages are per trace with token usage, so traces with no recorded usage don't pull them down. Any request for a
cost.*measure also returnscoston each row:cost.coverageis the share of traces with token usage whose cost was fully priced, andcost.unitis the currency. When a group mixes currencies,cost.sumandcost.avgarenullandcost.unitis"mixed". Groups whose measure isnullsort last and never satisfy ahavingcondition.import { parseTraceAggregateRequest, planTraceAggregate } from '@mastra/core/storage'; const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-06-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, groupBy: ['entityName'], interval: '1d', measures: ['count', 'tokens.input.sum', 'tokens.output.sum', 'cost.sum'], orderBy: { field: 'cost.sum', direction: 'desc' }, limit: 20, }), ); const { rows } = await storage.aggregateTraces(plan); // rows[0].measures → { count, 'tokens.input.sum', 'tokens.output.sum', 'cost.sum' } // rows[0].cost → { coverage: 0.96, unit: 'usd' }
TokenMetrics, the names of the token usage metrics, is now exported from@mastra/core/observability. -
Semantic recall now works with a vector store that generates embeddings itself, so a self-embedding store needs no client-side
embedder. (#25009)Added
isSelfEmbeddingtoMastraVector. It defaults to false, so every existing store is unaffected. A store that embeds text itself overrides it to return true, and semantic recall against that store needs noembedder:const memory = new Memory({ storage, vector: new MongoDBVector({ id: 'vec', uri, dbName, autoEmbed: { model: 'voyage-4' } }), options: { semanticRecall: true }, });
A configured
embedderstill takes precedence, so adding one to the example above returns to client-side embedding.Messages embedded by the store are kept in an index named
memory_messages_selfembed, separate from the indexes holding client-supplied vectors.Configuring semantic recall with neither an embedder nor a self-embedding store now says so, naming both options.
-
Added a storage API for querying individual completed spans with filters, cursors, bounded previews, and model cost. (#25791)
Shared cursor, timeout, and resource-limit errors now say "query" so they also describe span queries accurately. Error codes are unchanged.
import { planSpanQuery } from '@mastra/core/storage'; const result = await storage.querySpans( planSpanQuery({ timeRange: { from: '2026-10-01T00:00:00Z', to: '2026-10-02T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'tool_call' } }, }), );
-
Changed AgentController sessions to keep one active model instead of a separate model for each mode. Switching modes no longer changes the model automatically, and
session.model.switchno longer acceptsmodeIdorscope. Themodel_changedevent no longer includesmodeIdorscope; consumers should read itsmodelIdand currentthinkingLevelfields. When an existing thread is first reopened, its active legacy per-mode selection is copied tocurrentModelId, a migration marker is stored, and obsoletemodeModelId_*keys are removed. Later model selections persist as the thread's authoritativecurrentModelId. Usesession.model.setfor an in-memory selection that should not be persisted or emit a model-change event. (#25997)Before
await session.model.switch({ modelId: 'openai/gpt-5.6', modeId: 'build' }); await session.mode.switch({ modeId: 'plan' }); // Restored the plan mode model.
After
await session.model.switch('openai/gpt-5.6'); await session.mode.switch({ modeId: 'plan' }); // Keeps openai/gpt-5.6 active.
-
Added
traceIdto agent stream chunks, next torunId. Clients that call/streamcan now link a run to its trace without switching to/generate. The field is undefined when tracing is disabled. Customdata-*chunks (for example, fromwriter.custom()) are left exactly as written and don't includetraceId. Fixes #25811. (#25927)const stream = await agent.stream('hi'); for await (const chunk of stream.fullStream) { console.log(chunk.runId, chunk.traceId); }
-
Added a
createSession()option to start a session without creating a thread, andsession.thread.ensureId()to create one on first use. Sessions that are never used no longer leave empty threads behind. Sending a message or signal creates the thread automatically. The default is unchanged: sessions still get a thread when none matches. (#22561)const session = await controller.createSession({ createInitialThread: false }); // Returns the current thread, or creates one. Concurrent calls share one thread. const threadId = await session.thread.ensureId();
-
Added the per-provider object form to the
ObservationalMemoryActivationTTLtype, soactivateAfterIdleaccepts values like{ default: 'auto', anthropic: '1h' }. NewObservationalMemoryActivationTTLValueandObservationalMemoryActivationTTLByProvidertypes are exported. (#25727) -
Added an
outputValidationoption tocreateTool(#23799). When a tool's result failsoutputSchemavalidation, Mastra replaces the result with a validation error, so the model is told the call failed. That happens even when the tool's side effect (an order placed, a message sent) has already happened, which invites a retry. SetoutputValidation: 'warn'to return the tool's actual result unchanged instead.'strict'is the default and keeps the existing behavior. (#26001)export const placeOrderTool = createTool({ id: 'place-order', description: 'Places an order', inputSchema: z.object({ sku: z.string(), quantity: z.number() }), outputSchema: z.object({ orderId: z.string(), total: z.number() }), outputValidation: 'warn', execute: async ({ sku, quantity }) => orders.create({ sku, quantity }), });
Also improved how output validation failures are reported:
- Every output validation failure now writes a warning to the Mastra logger. Previously, a failure from a
createTooltool left no trace in the logs. - The tool call trace span is now marked as failed when a
createTooltool returns a validation error. Previously it was recorded as successful. - Sensitive fields such as
apiKey,token, andpasswordare now redacted from the tool output shown in the validation error message.
- Every output validation failure now writes a warning to the Mastra logger. Previously, a failure from a
-
Changed
session.model.switchto accept a model ID followed by an optional options object. Pass{ thinkingLevel }to apply and persist model and thinking level together. Everymodel_changedevent includes the current thinking level, even when it is unchanged. Sessions synchronize both preferences from persisted thread settings before the next request, including thinking-only changes and removed overrides. Switches canceled by a thread change before any selection is committed reject without counting model use; writes already committed to the captured thread remain successful. (#26069)// Before await session.model.switch({ modelId: 'openai/gpt-5.5' }); await session.state.set({ thinkingLevel: 'high' }); // After await session.model.switch('openai/gpt-5.5', { thinkingLevel: 'high' });
Patch Changes
-
Update provider registry and model documentation with latest models and providers (
b54fda3) -
Fixed MCP resource reads dropping
mimeTypeand_meta. Reading a resource from a server registered throughMCPClient(MCPClientServerProxy.readResource()) and reading an app resource from a localMCPServer(MCPServer.readResource(), used by Studio) now return the same metadata aslistResources()and the MCPresources/readrequest, so MCP Appui://resources keep their content type and UI settings such as CSP. Fixes #23068. (#25992)Also in: @mastra/client-js@1.52.0, @mastra/mcp@2.2.0, @mastra/server@1.75.0
-
Fixed a process crash with
TypeError: Invalid state: ReadableStream is lockedafter a provider errored mid-stream. The crash happened when an agent had output processors, such asBatchPartsProcessororRegexFilterProcessor, and a retrying error processor, such asStreamErrorRetryProcessor. Output processors no longer carry buffered parts across the retry. A stream that is already being read now reports throughonErrorinstead of raising an unhandled rejection, which on Node 22 exited the process and dropped every in-flight run. (#25803) -
Fixed
subscribeToThread({ withInitialHistory: true })returningthread-historymessages newest first. The initial history now lists the newest page of messages oldest first, matchinglistThreadMessagesand the documented behavior. (#25821) -
Added an optional
scopefield toAuthorizeOptsso tool providers know whether a new connection is shared, per-author, or caller-supplied. (#26002) -
Fixed workspace search reporting the wrong
lineRangewhen a custom tokenizer preserves case. Highlighting now uses the same tokens as retrieval, so searchingPythonpoints at the line containingPythonrather than a line containingpython. (#26014) -
Fixed three cases where a durable agent's stream, or a regular agent's saved approval metadata, did not match what the stream reported. (#26006)
- Tool result chunks now include
providerExecuted, which tells the caller whether the tool ran on the provider's side. It was missing on durable runs, so clients receivedundefinedwhere regular agents report the value. - Approval requests on durable agents now publish the same
resumeSchemaas regular agents, including$schema,additionalProperties: falseand the field descriptions. The accepted resume data is unchanged. - Approval metadata saved by regular agents now carries the same
resumeSchemaas the live approval request, including the optionalreasonfield the saved copy was missing. The accepted resume data is unchanged.
- Tool result chunks now include
-
Fixed dataset schema updates validating items against a previously proposed schema, and fixed items being validated against the old schema after changing the schema of an empty dataset. (#25659)
-
Fixed model call spans that listed tools the model did not receive. Each
MODEL_INFERENCEspan now has atoolsattribute with the tool definitions (name, description, parameters) sent to the provider on that call, after input processors,prepareStep,activeTools, andtoolChoiceare applied.availableToolson the same span now matches it: it is empty whentoolChoiceis'none', and it no longer lists names that are not registered tools. (#25917) -
Fixed directly resumed nested workflows so successful children continue suspended parent runs. Fixes #24588. (#25817)
-
Fixed call-time
clientToolsbeing dropped by durable agents (such as Inngest agents) when the workflow worker runs in a different process from the caller. Client tool schemas are now carried on the workflow input and restored when the worker rebuilds the run's tools, so the model can call them as expected. (#25664) -
Fixed approval ordering for tools added by input processors, such as
ToolSearchProcessor. Approving one call now releases the next call's approval request instead of leaving the run stuck. (#25639)Fixed
foreachbehavior for all evented workflows. Evented workflows now stay suspended until unfinished iterations resume, and queued iterations start only when capacity is available. -
Fixed DurableAgent traces after a resume or crash recovery. The resumed or recovered agent run is now nested under the original agent run, so the trace stays a single tree instead of splitting into two root spans. (#25862)
Crash recovery traces are also complete now: the agent run left open by the stopped process is ended with an
interruptedstatus, and the recovered agent run is ended when the run finishes. Previously a recovered trace could be missing from the trace list or show only the part before the crash. -
Fix durable and evented tool approvals so each approval resumes its own tool call and the agent finishes after the last approval (#25831)
-
Persisted channel thread history as individually attributed messages, including attachments and a marker for omitted messages, instead of one text block on first mention. (#25717)
-
Fixed
execute_commandandget_process_outputresults for commands stopped by an aborted run. The result now says the command was aborted instead of showing only a kill exit code such asExit code: 128, which agents mistook for a real command failure. This covers foreground commands, background processes killed when the run that started them is aborted, and processes killed whileget_process_outputwas waiting on them. (#25752)get_process_outputon a background process that exited without printing anything now returns its exit status (for exampleExit code: 0) instead of(no output yet), so it no longer looks like the process is still running. -
Fixed
fetchWithRetryignoring cancellation. An already-abortedsignalnow rejects immediately, and aborting during a request or backoff delay stops further retries instead of waiting through the remaining delays. Retries for non-aborted requests are unchanged. (#26164) -
Removed the @experimental annotation from Agent goal APIs (goal config, setObjective, GoalSignalProvider) now that goals are stable. (#25941)
-
Preserved extra fields in tool results passed to model messages, client callbacks, and loop callbacks. Client
onOutputandtoModelOutputcallbacks now receive results containing only avaluefield as that field's value. AI SDK error outputs are stored as failed invocations, skipped by client callbacks, and exposed to response consumers as their underlying error value. (#25680) -
Fixed pausing, clearing, or replacing a goal while its judge is running being silently undone. The judge's verdict is now discarded when the objective changed during evaluation, so the agent stops instead of continuing a goal you already stopped. (#26093)
-
Fixed
goal.scorerso a string resolves a registered scorer by its id, as documented. Previously it was looked up by registration key, so a scorer registered asscorers: { testsPass }with idtests-passfailed with "Scorer with tests-pass not found" and paused the goal. Registration keys still work as a fallback. (#26154) -
Fixed agent runs failing when the goal judge's feedback signal could not be streamed (for example, because a durable agent's transport had closed). The verdict is still recorded and the run finishes normally. (#25665)
-
Resource read results from MCP servers now include the optional
mimeTypeand_metafields, both in theMCPServerBase.readResource()type and in thePOST /mcp/:serverId/resources/readresponse returned toreadMcpServerResource(). (#25992)Also in: @mastra/client-js@1.52.0, @mastra/mcp@2.2.0, @mastra/server@1.75.0
-
Fixed executor interruptions so default-engine workflow runs remain recoverable instead of being recorded as canceled. Fixes #24586. (#24684)
-
Removed the @experimental annotation from Agent signal APIs (sendSignal, subscribeToThread, sendMessage, queueMessage, cancelQueuedMessages, abortThread, state and notification signals, and signal providers) now that signals are stable. (#25946)
Also in: @mastra/client-js@1.52.0
-
Fixed a leak where an output stream processor that throws let the original, unprocessed chunk through. For example, a redaction processor that crashed would still stream the text it was meant to redact. (#25826)
Now streamed content chunks processed inside regular, durable, and evented runs stop before the failing chunk is emitted, emit an
errorchunk, and finish withfinishReason: 'error'. Durable tool-chunk processor failures also fail their workflow step before the tool chunk is published. For durable and evented agents, failures on lifecycle chunks such asstart,step-start,step-finish, andfinisherrorfullStreamand reject derived output promises. For regular agents,step-startandfinishfailures emit anerrorchunk and resolve withfinishReason: 'error', while astep-finishfailure errors the stream;startis not passed to output stream processors. Previously the processor error was logged and the unprocessed chunk was emitted while the stream finished withfinishReason: 'stop'.abort()in a processor still blocks the chunk as before.const result = await agent.stream('Reveal the card number'); try { for await (const chunk of result.fullStream) { if (chunk.type === 'error') { console.error(chunk.payload.error); // e.g. "redactor crashed" } } } catch (error) { // Some lifecycle-chunk processor failures error the stream directly. console.error(error); }
-
Added internal groundwork for querying individual spans across traces. (#25701)
-
Fixed durable threads that reported suspended tool calls as lost after a restart. Pending tool calls remain available for a response. (#25764)
-
Fixed
DurableAgentandEventedAgentruns to fail and report the error when mapping a server-executed tool result, including an awaited background result, throws intoModelOutput. Client-executed tools and completed deferred background tasks continue with the raw result when their mapper throws. (#25762) -
Fixed evented agents hanging forever when workflow workers run in a separate process (for example, an API server started with
MASTRA_WORKERS=falseplus a dedicated worker deployment). Runs now reach the worker process and stream back to the caller. Values used in more than one place in a workflow now keep their data when sent between processes instead of arriving asnull. A process without workers now logs a warning instead of silently dropping a workflow it can only run locally. (#26036) -
Fixed sendToolApproval to preserve separate approval decisions when custom resume data is provided. Approval-gated calls now reject custom resume data unless it is a non-null object that can carry the decision. (#25632)
-
Fixed
stream(..., { untilIdle: true })repeating earlier output when a background task triggers a follow-up turn. The suppliedrunIdcan abort the initial or follow-up turn throughabortRunStream(runId)orabortThreadStream(...). (#25661) -
Fixed agents running out of memory when
untilIdleis set indefaultOptions.agent.stream()andagent.resumeStream()now wait for background tasks once instead of looping endlessly before calling the model. Fixes #26043. (#26125) -
Fixed skill publishing so it rejects JavaScript frontmatter (
---js) inSKILL.mdinstead of running it. Publishing now parsesSKILL.mdthe same way as loading and validating a skill. Fixes #25551. (#25689) -
Fixed agent runs stopping with "Interrupted" when an Observational Memory reflection didn't compress enough on its first try. The reflector retries at a stronger compression level, but each retry was reported as a failure, and the agent controller cancelled the run before the retry could finish. Retry attempts are now marked as retrying, so only a final failure stops the run. (#26105)
Also in: @mastra/memory@1.36.0
-
Fixed structured output failing with 400 errors on OpenAI-compatible providers (for example Azure AI Foundry through
@ai-sdk/openai-compatiblewithsupportsStructuredOutputs: true). These providers send a strict JSON schema, but Mastra only prepared schemas for strict mode when the provider name started withopenai, so requests failed with errors like'uniqueItems' is not permittedorMissing 'subject'. Schemas are now prepared whenever the model sends a strict JSON schema, andnullvalues for optional fields are accepted in the response. SettingstrictJsonSchema: falsein the provider options keeps the schema unchanged. (#25707) -
Fixed requests failing with empty assistant content after switching models mid-thread, such as from an OpenAI reasoning model to Claude. When reasoning the new provider cannot accept is removed from history, assistant turns that contained only that reasoning are now left out instead of being sent empty. (#23050)
-
Fixed batched channel messages from other senders skipping custom
onMention,onDirectMessage, andonSubscribedMessagehandlers whenburst,debounce, orqueueconcurrency is set. (#25854)Your handler now runs once for each sender's turn, with its own
ctx.requestContext,ctx.signalMetadata, andctx.skipped. A handler can now be called several times for one dispatch. -
Cancelling a run while structured output is being generated no longer logs a false "Structured output processing failed" error. The structuring model call is now cancelled along with the run, and no output chunks are written after the run's stream has closed. (#25662)
-
Fixed
restart()for evented workflows that crashed while resuming a nested workflow. The resumed step now keeps its resume data and completes on restart instead of suspending again. Fixes #25365. (#25630) -
Fixed a thread failing on every turn when one of its attachments can't be used: a file that now returns 404, a relative path such as
/api/images/foo.png, or inline content that isn't valid file data. If error processors and fallback models don't recover a failed download, the agent now answers with an[Attachment unavailable: <name>]placeholder and logs a warning. The attachment is recorded on its stored message, so later turns reuse the placeholder without downloading it again. See #23705. (#25051) -
Fixed aborting a thread run that had several messages sent to it: the follow-up run now answers all of them in one turn, instead of one message per run (which made you abort once per pending message). Messages added with
queueMessagestill run one at a time after it. (#25749) -
Fixed durable and evented agent terminal behavior. Aborted runs now emit an
abortchunk, reportfinishReasonasaborted, and callonAbortinstead ofonFinish. Failed runs callonErrorinstead ofonFinish.resume(),observe(), andrecover()now accept anonAbortcallback. (#25765) -
Fixed
ResponseCachesharing cached responses between users who are identified only throughmemory: { resource }. When noscopeis set, the cache now falls back to the memory resource ID after the auth resource ID, so each user gets their own cache entries. Setscope: nullto keep sharing responses across users. (#26192) -
Fixed error-processor retries being dropped when a run reached its
maxStepslimit. An agent run withmaxSteps: 1that hit a transient 429/5xx would previously resolve as a successful empty (or partial) response withfinishReason: 'retry'. Retries now re-run the failed step without counting againstmaxSteps, and if every retry fails the run ends withfinishReason: 'error'and the last provider error (including fields likestatusCode) onoutput.errorandonError, on the default, durable, and evented engines. (#25804) -
Fixed streamed
PIIDetectoroutput with theblockandfilterstrategies. When a Social Security number, email address or similar value arrived split across several stream chunks, the first part of it reached the user before the rest was recognized. Nowblockstops the response before any part of the value is shown, andfilterremoves the text up to and including the value while keeping the text after it. Very long email addresses are now also held until they are complete, including withredact. As withredact, the end of a response may arrive slightly later. (#26082) -
Fixed fresh-process durable agent resumes replaying suspended output when cached stream history is unavailable. (#25760)
-
Fixed usage aggregation so omitted provider token counts remain unknown across agent, workflow, and durable streams. Reported cache and reasoning details remain additive. (#25402)
AccumulatedUsagefrom@mastra/core/agent/durableandWorkflowDataPart['data']['output']['usage']from@mastra/ai-sdknow represent incomplete primary counters asundefinedinstead of measured zeroes. When every input and output count is known,totalTokenscan still be derived from those complete aggregates even if the provider omitted its total. Derived totals now sum input and output tokens without addingreasoningTokens.For example,
{ inputTokens: 10, outputTokens: 20, totalTokens: 30 }followed by{ outputTokens: 5 }now produces{ inputTokens: undefined, outputTokens: 25, totalTokens: undefined }.Observability still records known per-step token contributions and marks their aggregate as incomplete.
TokenCostControltreats the partial estimated cost as a known lower bound: hard and soft thresholds still apply when that lower bound crosses them, while lower values do not imply the complete cost is under budget. The Responses API returnsusage: nullfor an incomplete aggregate instead of fabricating zero-valued counters.Durable iteration state written by this version may omit unknown primary counters and cannot be resumed by an older worker after a rollback. (#23469)
Also in: @mastra/ai-sdk@1.10.7
-
Fixed thread streams treating a run as finished while it was still waiting for a tool approval or a suspended tool. When thread updates were delivered with a delay (for example with
@mastra/redis-streams), the thread could unblock and subscribers on other servers could lose the pending approval. The run now stays waiting until the approval or tool resumes it. (#25962) -
Added optional
rulestoprompt_block_refinstruction blocks so stored agents can save and preview per-usage display conditions on prompt block references. (#25991)await client.getStoredAgent('support-agent').update({ instructions: [ { type: 'prompt_block_ref', id: 'default-user-prompt', rules: { operator: 'AND', conditions: [{ field: 'userPrompt', operator: 'not_exists' }] }, }, ], });
Also in: @mastra/client-js@1.52.0, @mastra/react@1.8.0, @mastra/server@1.75.0
-
Fixed
processToolResultaborts in output processors so they end the run at the first aborted tool result. Previously, aborting with parallel tool calls crashed the run withController is already closed, and aborting a single tool call made an extra model call after the stream had ended. (#26157) -
Fixed
ToolNotFoundErrorwhen approving a tool loaded byToolSearchProcessorwithstorage: 'context'after the run resumes without in-process state — for example aDurableAgentresuming after a restart or after its run registry entry expired, orresumeStreamlanding on a different instance. The resumed run now rebuilds the loaded tools from the thread's persisted messages. (#25666) -
Tool input validation errors now include the path of each invalid field. Previously, MCP 1.x servers returned errors like
Invalid input: expected array, received stringwith no indication of which argument was wrong. Errors now read, for example: (#25794)- items.0.tags: Invalid input: expected array, received string - options.destination: Invalid input: expected string, received undefinedFixes #25766.
-
Fixed two restart problems on durable runs. (#26035)
Recovering a suspended durable agent run now fails immediately with a clear message pointing at
resume(). Previously it resolved, then emitted a lone "This workflow run was not active" error. Continue a run that is suspended on a tool call or an approval withresume(runId, ...).Restarting an evented workflow in a process that has not started its workers now starts them before the restart is published. Previously the restart was published to no one, so the run stalled forever with no error, and starting the workers later could not revive it. This does not change instances configured with
workers: false(orMASTRA_WORKERS=false). Those still publish the restart to the broker, so the run completes only if another worker consumes it. -
Fixed agents sending an invented
.user message before a conversation that starts with an assistant message, such as a voice agent that greets first. Mastra now adds it only for Amazon Bedrock and Google Gemini, which reject assistant-first conversations. OpenAI, Anthropic, Groq and other providers get the conversation unchanged. The message is added by the defaultProviderHistoryCompatprocessor, after your input processors run, and is still not saved to memory. Fixes #22874. (#25074)MessageListprompt getters (such asmessageList.get.all.aiV5.prompt()) no longer add the.message, and theensureGeminiCompatibleMessageshelper is removed. If you build prompts from aMessageListyourself and send them to Bedrock or Gemini, add the user turn before a leading assistant message:const prompt = messageList.get.all.aiV5.prompt(); const first = prompt.findIndex(message => message.role !== 'system'); if (prompt[first]?.role === 'assistant') { prompt.splice(first, 0, { role: 'user', content: '.' }); }
-
Added captureThreadRunTerminalPublish() to detect when an open thread continuation has delivered terminal output. (#25759)
-
Fixed onIterationComplete feedback delivery when the default agent loop has finished. (#25761)
-
Fixed Workspace search indexing creating an extra chunk at the end of large files that only repeated content from the previous chunk. (#26166)
-
Fixed cron schedules whose cadence runs out, such as year-pinned crons. (#25654)
- The final occurrence now fires once, and the schedule then moves to
completed. - Editing or resuming a schedule with no future occurrence now completes it instead of failing, whatever status the row held.
- An invalid cron expression or timezone now returns an input error (400) instead of a server error.
ScheduleStatusnow includescompleted, so code that switches over every schedule status needs a branch for it.
- The final occurrence now fires once, and the schedule then moves to
@mastra/ai-sdk@1.10.7
Patch Changes
-
Fixed a client's own auth header being sent to the model provider. When a request body included
modelSettings.headerswith credentials such asAuthorization(for example, an app forwarding its user's bearer token), that header was passed to the provider and overrode theapiKeyconfigured on the server. Agent routes in@mastra/serverandhandleChatStream/chatRoutein@mastra/ai-sdknow drop credential headers (authorization,proxy-authorization,x-api-key,api-key,x-goog-api-key,cookie) from client-suppliedmodelSettings.headers. Other headers and body options are passed through unchanged. (#25548)Also in: @mastra/server@1.75.0
@mastra/auth-firebase@1.1.3
Patch Changes
- Updated
firebase-adminto v14 and switched to its modular API (firebase-admin/app,firebase-admin/auth). v14 dropsnode-forge, which has an unfixed signature-forgery advisory (CVE-2026-85393). No change to how you configureMastraAuthFirebase. (#25829)
@mastra/auth-okta@0.2.4
Patch Changes
MastraRBACOktanow calls the Okta groups API directly withfetchinstead of going through@okta/okta-sdk-nodejs. It pages through theLinkheader and only follows links on your Okta org. Behavior is unchanged, but the package no longer pulls innode-joseandnode-forge, which has an unfixed signature-forgery advisory (CVE-2026-85393). (#25829)
@mastra/braintrust@1.3.19
Patch Changes
- Bumped braintrust to ^3.36.0. Older braintrust 3.x releases pulled in simple-git 3.36.0, which has critical command-injection advisories (GHSA-x6jw-m9v5-85vh, GHSA-v5rq-49vh-5v5c) that are only fixed in simple-git 4. braintrust 3.36 no longer depends on simple-git. No API changes. (#25966)
@mastra/clickhouse@1.23.0
Minor Changes
-
Added
aggregateTraces()support to the ClickHouse observability store. The store now advertises thetrace-aggregatecapability. It returns grouped counts, error rates, duration statistics, and time-bucketed series over the same traces thatqueryTraces()selects. Retried and replaced trace roots are collapsed when the query runs, so results stay correct before background merges finish. (#25842)const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z' }, groupBy: ['entityName'], interval: '1d', measures: ['count', 'errorRate'], }), ); const { rows, truncated } = await observability.aggregateTraces(plan);
queryTraces()andaggregateTraces()on ClickHouse are also faster for filters on trace fields such asentityName,status, ormetadata.*. These filters now narrow the traces read before duplicate and replaced roots are collapsed, so selective queries over long time ranges do less work. Results are unchanged. -
Added filtered span queries to ClickHouse v-next observability storage. Queries select matching span identities before loading display fields, previews, and model cost. (#25791)
const result = await observabilityStorage.querySpans(plan);
Patch Changes
-
Fixed the order of the features the ClickHouse observability store reports, so it matches the Postgres and DuckDB stores. (#25934)
-
Fixed
updateMessagesdeleting and re-inserting every message whose content it updated. Content updates are now applied in place, which is faster and no longer logs the full message content. (#25984) -
Added token and cost measures to
aggregateTraces()in the ClickHouse observability store. (#25970)- Token measures:
tokens.input,tokens.output,tokens.total,tokens.reasoning, andtokens.cached, each as.sumor.avg. - Cost measures:
cost.sumandcost.avg. Rows for cost requests also includecost: { coverage, unit }.
const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, groupBy: ['entityName'], measures: ['tokens.total.sum', 'cost.sum'], }), ); const { rows } = await observability.aggregateTraces(plan); // rows[0] → { dimensions: { entityName: 'support' }, measures: { 'tokens.total.sum': 7800, 'cost.sum': 3.75 }, cost: { coverage: 0.75, unit: 'usd' } }
Usage comes from the model token metrics of each trace. Retried metric writes count once without waiting for background merges, and spend recorded after the window ends still counts for traces that started inside it.
- Token measures:
-
Improved ClickHouse query performance by letting filters use table indexes instead of scanning whole tables. (#25715)
Observability
- Delta polling and list cursors for traces, branches, logs, metrics, scores and feedback now read only the rows past the cursor instead of the whole signal table.
- Score dashboards and
listScoresfiltered by trace no longer merge the entire current-scores table. - Trace and branch list pages and paginated trace queries use far less memory and load faster. Listing branches no longer runs out of memory on large deployments.
- Metric, score and feedback percentile charts compute all requested percentiles in one query.
- Added skip indexes for trace-root and branch lookups by trace, feedback lookups by id, and log filters by trace, thread, resource, user, organization, experiment, run, session and request.
Memory, workflows and scores
updateMessagesandupdateResourceare much faster on large tables because they no longer rewrite the entire table on every call.saveMessages,updateMessagesandlistThreadsrespond faster, especially for batches of messages and for users with many threads.- Added skip indexes for message, thread, resource, workflow run and score lookups by id.
Skip index coverage
The new skip indexes are added automatically on the next
init(). They cover data written from then on immediately; existing data is covered only as ClickHouse merges it. Older memory and workflow data rarely re-merges, so lookups over existing history stay as slow as before until the index is built for it. To cover existing data now, run this once per table and index (a background job that reads the indexed column):ALTER TABLE <table> MATERIALIZE INDEX idx_<column>;
For example,
ALTER TABLE mastra_messages MATERIALIZE INDEX idx_thread_id;. List the indexes on a table withSELECT name FROM system.data_skipping_indices WHERE table = '<table>'. -
Fixed
listTracesfailing with a SQL syntax error when filtering byhasChildErrorin the ClickHouse observability store. (#25984)
@mastra/client-js@1.52.0
Minor Changes
-
Added the optional
liveKitRecordingRouteEnabledcapability togetSystemPackages()response types so applications can discover recording review support. (#24674)const packages = await client.getSystemPackages(); if (packages.liveKitRecordingRouteEnabled) { // Show recording controls for voice call traces. }
-
Added an optional thinking level to controller session
switchModelcalls. (#26069)// Before await session.switchModel('openai/gpt-5.5'); // After await session.switchModel('openai/gpt-5.5', { thinkingLevel: 'high' });
-
Added a typed
querySpans()method that lists completed spans matching a span filter, one row per span, with cursor pagination. (#25920)const result = await mastraClient.querySpans({ timeRange: { from: '2026-10-01T00:00:00Z', to: '2026-10-02T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'tool_call' } }, page: { limit: 50 }, }); // { spans: [...], page: { next } }
queryTraces()with aspans.somefilter returns the traces that contain a matching span.querySpans()returns the matching spans themselves. Checkcapabilities.spanQueryfromgetObservabilityCapabilities()before you call it. -
Removed the options argument from AgentController
switchModel. Model selection now persists to the active thread and remains active when its mode changes. (#25997)Before
await controller.switchModel('openai/gpt-5.6', { modeId: 'build', scope: 'thread' });
After
await controller.switchModel('openai/gpt-5.6');
-
Added skills.sh registry methods to the workspace client:
searchSkillsSh,listPopularSkillsSh,previewSkillsSh,installSkillsSh,removeSkillsShandupdateSkillsSh. Parameters and responses are typed from the server routes. (#25700)const workspace = client.getWorkspace('my-workspace'); const { skills } = await workspace.listPopularSkillsSh({ limit: 10 }); await workspace.installSkillsSh({ owner: 'vercel-labs', repo: 'skills', skillName: 'find-skills' });
Patch Changes
- Added
completedto the schedule status types. A schedule whose cron has no future occurrence reportscompletedwhen fetched by id, and the schedules list includes completed schedules only when you filter bystatus: 'completed'— the default listing omits them.statuson a schedule update still only acceptsactiveorpaused. (#25654)
@mastra/cloudflare@1.7.1
Patch Changes
-
Fixed
listWorkflowRuns({ status })failing the whole query withmalformed JSONwhen any stored snapshot was nested deeper than SQLite's JSON depth limit. Such snapshots are now skipped by the status filter, and other runs are returned normally. (#26131)Also in: @mastra/cloudflare-d1@1.4.1
@mastra/code-sdk@1.11.0
Minor Changes
-
Made the controller runtime independent of Mastra Code model packs. Hosts can now provide a generic modelRoute for fallback models, account routing, and observational-memory models. Removed disableModelPacks and activeModelPackId from the controller runtime state. (#25997)
Before
const code = await createMastraCode({ disableModelPacks: true }); await session.state.set({ activeModelPackId: 'anthropic' });
After
const code = await createMastraCode(); await session.state.set({ modelRoute: { entries: [ { id: 'primary', label: 'Primary', modelId: 'openai/gpt-5.6' }, { id: 'fallback', label: 'Fallback', modelId: 'anthropic/claude-fable-5' }, ], }, });
-
Added a
createInitialThreadoption tocreateMastraCode(). Set it tofalseto start without a thread; the first message creates one, so a session that's never used leaves no empty thread behind. The default is unchanged. (#22561)createMastraCode({ createInitialThread: false });
-
Agents in different projects on the same machine can now find each other. With cross-agent communication on (
crossAgentSignals: trueor thesignals.experimentalCrossAgentSignalssetting),agent_connections_listlists Mastra Code instances in every project, andagent_connectandagent_signal_sendreach them. Before, the list only showed instances in the same project. (#25758)import { createMastraCode } from '@mastra/code-sdk'; const mastraCode = await createMastraCode({ unixSocketPubSub: true, crossAgentSignals: true, });
An instance whose resource ID (from
MASTRA_RESOURCE_IDor.mastracode/database.json) can't be used as a directory name, for example one containing/, stays out of cross-project discovery and logs one warning at startup.Any local process running as the same user can discover and message advertised agents. Set
MASTRACODE_SIGNALS_SOCKET_ROOTto an absolute path to keep an instance or a test away from the shared/tmp/mcdirectory.Also fixed signal sockets piling up under
/tmp/mc. Every agent discovery request used to leave an open socket, and often a socket file, behind, so long-running instances could accumulate thousands. One-off discovery replies now close as soon as they're answered. Headlessmastracode --promptruns also close their signal sockets on exit instead of leaving the files behind. -
Editors that connect to Mastra Code over ACP (Agent Client Protocol), such as Zed and JetBrains IDEs, can now sign you in without leaving the editor. (#25871)
- Sign in with a ChatGPT subscription, Kimi For Coding, or xAI in the browser, started from the editor.
- Editors that support terminal sign-in also get Claude and GitHub Copilot sign-in, plus
mastracode-loginfor any other provider or an API key. Each runsmastracode loginin a terminal. - Starting a session with no configured provider returns an
auth_requirederror, so editors show these sign-in options instead of a session with no usable model. - New sessions start on a model you can use. If the default model's provider isn't configured, the session switches to the default model of a provider you signed in to.
const { authMethods } = await connection.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: { auth: { terminal: true } }, }); // openai-codex, kimi-for-coding, xai, anthropic, github-copilot, mastracode-login await connection.authenticate({ methodId: 'openai-codex' });
Patch Changes
-
Fixed Unix socket path handling so a thread ID can no longer place a socket outside its resource directory, whether or not cross-project agent discovery is enabled. Thread IDs that would reuse the lease or discovery socket names are rejected as well. (#25876)
-
Observational memory and thread-title calls on Anthropic no longer write their whole prompt to the 1-hour prompt cache. Each call sends different conversation content, so that cache entry was never read. They now cache only their shared instructions for 5 minutes, which the next observer call reads. The main agent still uses the 1-hour cache. (#25739)
-
Fixed Mastra Code starting a second agent run on a thread that was already running in another project's Mastra Code process. Every local Mastra Code process shares one database, so any of them could pick up a notification meant for another project's thread, see that thread as idle, and run it with its own session and workspace. (#25741)
Mastra Code processes now coordinate a thread's runs no matter which project they belong to, so a busy thread stays busy for every process. Each process delivers the due notifications of its own sessions' resources itself, and only one process delivers a resource's notifications at a time, so a notification is never run by a process that cannot own its thread.
-
Improved ACP session shutdown by waiting up to two seconds for pending notification dispatch and stopping workers before closing session resources. (#25875)
-
Fixed ACP model selection to apply the model and its compatible thinking level together. (#26069)
-
Mastra Code now caches prompts for 1 hour instead of 5 minutes on direct Anthropic connections (API key or Claude subscription), and waits for 1 hour of idle time before activating buffered observations on those models. Claude through other providers, such as Amazon Bedrock, keeps the 5-minute behavior. Coming back to a conversation after a break of up to an hour reuses the cached prompt instead of resending it in full. (#25727)
Cost tradeoff: writing a 1-hour cache entry costs 2× the base input price, compared with 1.25× for a 5-minute entry. Cache reads cost the same. Sessions that pause for between 5 minutes and an hour can come out cheaper because they read the cache instead of rewriting it. Sessions that never pause that long pay more for every cache write.
The 1-hour cache also applies to Anthropic models used for memory observation, reflection, and thread titles, since they go through the same model setup.
-
Bumped the ajv pin in the ACP validator script to 8.20.0. No runtime changes. (#25816)
-
Bumped probe-image-size to ^7.4.0 in @mastra/memory to pick up the fix for a quadratic-time denial of service in its SVG parser (GHSA-gjj5-9665-rwrc). Bumped the smol-toml pin in @mastra/code-sdk from 1.8.0 to 1.9.0 for a parser security fix. No API changes. (#25966)
Also in: @mastra/memory@1.36.0
@mastra/connect@1.0.0
Major Changes
-
Breaking: Discovered MCP tools no longer require approval by default, matching
@mastra/mcp's own default. Opt into approval per integration with the newrequireApprovaloption. (#25740)Previously, every MCP tool discovered through
connect()was forced to require approval, with anautoApproveToolsescape hatch to list specific keys that should skip it. The forced-approval policy made the typical case needlessly interactive and diverged from@mastra/mcp, whose default is "no approval unless the server definition opts in."The new
requireApprovaloption replacesautoApproveToolsand applies to generated HTTP toolsets as well as discovered MCP tools:- Omit (or pass
false) → no approval required for any tool on this provider true→ every tool on this provider requires approvalstring[]→ approval required only for the listed tool keys
Unknown names in the array fail resolution with an
invalid_optionserror instead of silently dropping the provider, so a typo can neither widen access nor remove the toolset. A config that still contains the removedautoApproveToolskey throws attools()call time with a migration hint, so loosely typed configs cannot carry the dead option forward and run previously-gated tools without a prompt.If you relied on the old always-on default (for example, agents scaffolded from the connect templates), opt back in explicitly with
requireApproval: trueon the providers whose tools should prompt before running.Migration
// Before: approval required by default, allowlist of tools that skip it connect({ integrations: { neon: { autoApproveTools: ['neon_list_projects', 'neon_describe_project'] }, }, }); // After (conservative): gate every tool on the provider. This preserves the // former approval policy, except the tools previously listed in // autoApproveTools now prompt too. connect({ integrations: { neon: { requireApproval: true } } }); // After (targeted): gate only the listed tools. Not equivalent to the old // default; every tool missing from the list runs without a prompt, so list // every tool that must stay gated. connect({ integrations: { neon: { requireApproval: ['neon_delete_project'] }, }, });
- Omit (or pass
Minor Changes
-
Reworked the
@mastra/connectAPI ahead of the first stable release. Breaking: the package has not shipped stable, so no deprecation period applies. (#26084)Renamed
integrationstoprovidersThe option on
tools()andchannels()is now calledproviders, with types renamed to match (ToolsProviderOptions,ChannelsProviders,ChannelsProviderOptions).// Before const connectTools = tools({ integrations: { linear: { allowTools: ['linear_get_issue'] } } }); // After const connectTools = tools({ providers: { linear: { allowTools: ['linear_get_issue'] } } });
Merge your own tools in a dynamic
toolscallbackSpread the resolver's result alongside local tools; your tools win on key collision.
const agent = new Agent({ // ... tools: async ctx => ({ ...(await connectTools(ctx)), weatherTool }), });
A
.with()convenience method also exists for the same merge (tools: connectTools.with({ weatherTool })); it accepts a static record or a (sync/async, optionally context-reading) function, and calls chain.Simpler provider selection
The array form is now a real allowlist (only the listed providers resolve), and the record form accepts boolean shorthand:
tools({ providers: ['linear', 'resend'] }); // only these resolve tools({ providers: { linear: true, github: false } }); // enable / exclude
Unknown provider ids now fail with
invalid_optionsinstead of silently resolving nothing.channels()throws at call time;tools()checks ids against the platform catalog when it resolves tools, and if the catalog can't be reached it logs a warning and skips the id.In
channels(),slackis accepted as an alias for the platform'sslack-channelskey, so the common spelling needs no quotes:channels({ providers: { slack: true, discord: true } });
Both spellings configure the same channel; naming it twice throws.
Glob filters and top-level defaults
allowTools,disallowTools, andrequireApprovalaccept*globs, and all three can be set at the top level oftools()as defaults for every provider. Per-provider options win, includingrequireApproval: falseto opt out of a global policy.tools({ requireApproval: ['*_delete_*', '*_send_*'], providers: { linear: { requireApproval: false } }, });
A per-provider glob that matches nothing fails like an unknown literal name, so typos never silently widen access.
Removed
- The deprecated
connect()alias oftools(). - The
environment()sandbox credential surface. - The
MASTRA_<PROVIDER>_CONNECTION_IDenv-var pin is no longer documented (it keeps working); passconnectionIdper provider instead. - The
TOOLSregistry alias (usePROVIDERS) and thefindRegistration/findChannelRegistrationlookup helpers. - Internal toolset plumbing (
defineProxyTool,applyAllowTools,resolveConnectionIdand their types) is no longer exported. - The
disabled: trueper-provider field — use thefalseshorthand instead:providers: { github: false }. - The resolver
invalidate()method — callrefresh()to force a fetch now, or lowerttlMsto control freshness.
- The deprecated
-
Discord (channel and generated tools) now targets the platform's
discordintegration as a single API-key connection whose credential is the bot token, sourced from the encrypted/credentialsendpoint on every call. The same connection powers both the channel and every generated tool from one credential. (#25358)Why: connection metadata is treated as non-secret by the platform, so a bot token stored there was readable by any caller with project access. Every Discord surface now reads the bot token from the platform's encrypted, audited credentials path.
Breaking:
- The 33 generated Discord tools no longer read the bot token from connection metadata; they call
getConnectionWithCredentials()and usecredentials.apiKey. - The
discordintegration on the platform is now API-key auth (Nango'sdiscord-botprovider), not OAuth. OAuth-typed credentials on the channel are skipped with a warning — Discord rejects OAuth user tokens for bot authentication.
Migration:
- Reconnect the
discordintegration on the platform against the new API-key auth flow (Nango'sdiscord-bot) and paste the bot token when prompted. The token is now stored in the encrypted credential, not connection metadata. - Remove any
botTokenyou were still writing to connection metadata — it is no longer read and, since metadata is non-secret, it should not be left behind.
Before:
// Bot token was pulled from non-secret connection metadata. // Tools called `getMetadata<{ botToken: string }>()` internally.
After:
import { channels, tools } from '@mastra/connect'; const projectId = 'proj_...'; // Generated Discord tools — bot token now comes from the encrypted // `credentials.apiKey` on the `discord` connection. const toolResolver = tools({ projectId, integrations: { discord: { connectionId: 'conn_...' } }, }); const discordTools = await toolResolver(); // Discord channel — same `discord` connection, same credential. const channelResolver = await channels({ projectId, integrations: { discord: { providerOptions: { commandScope: 'global' } }, }, }); const providers = await channelResolver(); // providers.discord
- The 33 generated Discord tools no longer read the bot token from connection metadata; they call
-
Improved error handling across all providers and updated the Clerk tools, verified by a new live end-to-end test suite that exercises every tool of all 26 checked-in providers. (#25815)
Clearer error messages — errors returned as
{ errors: [{ message, long_message }] }(Clerk, Linear, and other providers) now surface the human-readable message instead of a generic failure.Fixed error recovery in generated tools —
MastraConnectErrornow also exposes its HTTP status aserror.response.status. Generated tools check this field in their error handlers (for example the create-vs-update fallback ingithub_create_or_update_file), which previously never matched and failed instead of recovering.Updated Clerk tools (regenerated from the upstream template fix, NangoHQ/integration-templates#670):
clerk_list_sessionsnow requiresclient_idoruser_idand no longer returnstotal.clerk_create_usernow requires at least one identifier (email_address,phone_number, orusername).clerk_list_usersnow returns an accuratetotal.
// Before: accepted by the schema, then rejected by the Clerk API await toolset.clerk_list_sessions.execute({ status: 'active' }); // After: the input schema requires a client or user filter await toolset.clerk_list_sessions.execute({ user_id: 'user_123', status: 'active' });
-
Added Microsoft Teams to
channels()and re-keyed the Slack channel fromslacktoslack-channels. (#25332)Microsoft Teams: projects with an active
microsoft-teamsplatform connection now resolve aTeamsProviderautomatically — no bot credentials in your code. Connecting an agent provisions a dedicated Teams bot for it through the platform connection. RequiresMASTRA_ENCRYPTION_KEY(a 32-byte value, base64-encoded) in the server environment: the Teams install store persists each provisioned bot's client secret at rest, sochannels()refuses to construct the Teams provider without it and skips the integration with a warning until a key is configured.Breaking: Slack channel integration id renamed. Use the
slack-channelsconnection to hook the Slack channel up to agents; the existingslackconnection continues to back the generated Slack tools. Two separate connections, one purpose each.If you pass per-integration overrides to
channels(), migrate the key:channels: await channels({ projectId: process.env.MASTRA_PROJECT_ID, integrations: { - slack: { providerOptions: { defaultChannel: 'C123' } }, + 'slack-channels': { providerOptions: { defaultChannel: 'C123' } }, }, }),import { Mastra } from '@mastra/core/mastra'; import { channels } from '@mastra/connect'; const mastra = new Mastra({ agents: { myAgent }, channels: await channels({ projectId: process.env.MASTRA_PROJECT_ID }), }); // With `microsoft-teams` and `slack-channels` connections active, both // channels resolve automatically and agents can be connected from Studio // or via the API. Teams additionally requires MASTRA_ENCRYPTION_KEY set // in the server environment.
-
Connect tool calls now show up in traces. When observability is enabled, every platform API call and proxied vendor call appears as a child span under the tool call span, recording the method, a safe route template, connection id, and response status. Spans never include query strings, headers, bodies, or vendor path segments; apps without observability configured are unaffected. (#26080)
-
Add Google Analytics (GA4) provider to
@mastra/connectwith 11 generated tools from the Nango integration-templates upstream:archive-conversion-event,batch-run-reports,create-conversion-event,create-data-stream,create-property,get-metadata,run-pivot-report,run-realtime-report,run-report,update-data-stream, andupdate-property. (#25743)Opt in by setting
MASTRA_GOOGLE_ANALYTICS_CONNECTION_IDand selectinggoogle-analyticsin theintegrationsmap.
Patch Changes
-
Fixed Jira issue creation so agents can discover project-specific issue types and their required fields. (#26119)
-
@mastra/connectnow requires@mastra/core1.75.0 or later, matching the@mastra/mcpversion it depends on. (#25985)
@mastra/convex@1.7.1
Patch Changes
-
Added support for recording a schedule's terminal status in the same atomic update that claims its final firing. A schedule whose cron has no future occurrence is now stored as
completedinstead of stayingactive, so its last occurrence is delivered once instead of on every tick. (#25654)Upgrade this store alongside
@mastra/core. A store that predates this argument leaves the rowactive, and the scheduler re-fires that final occurrence on each tick.Also in: @mastra/libsql@1.25.1, @mastra/mongodb@1.22.0, @mastra/mysql@0.12.1, @mastra/pg@1.30.0, @mastra/spanner@1.9.2
@mastra/deployer@1.75.0
Patch Changes
- Fixed
mastra buildon Yarn 1 (classic) workspaces leavingworkspace-module/empty. Workspace dependencies are now packed into the build output, so installing the output no longer fails. (#25788)
@mastra/discord@1.3.0
Minor Changes
-
Fixed Discord installs staying "pending" forever after completing the bot invite. Discord's invite flow doesn't notify the server when it finishes, so the provider now exposes
reconcileInstallation(agentId)— it activates a pending install when the server can attribute the newly joined guild to it, and Studio calls it when you return, so the agent shows "Connected" right away: (#25993)const info = await discord.reconcileInstallation('my-agent'); // info?.status === 'active' once the invite finished
When the new guild can't be attributed safely (several invites in flight, or the bot joined more than one guild), the install stays pending and activates on its first interaction, as before. Invite attribution expires after 30 minutes and never crosses a bot credential change.
listInstallations()is now a pure read.
Patch Changes
- Fixed a Discord Gateway reconnect storm that could trip Discord's connection abuse limit (>1000 connects in a short window) and get the bot token force-reset. The provider now owns the Gateway reconnection loop: failed connects (invalid token, Message Content privileged intent not enabled) back off exponentially instead of retrying instantly, a revoked token parks reconnection until new credentials arrive, and exactly one loop runs per installation — credential rotations and
disconnect()now stop the previous loop instead of leaking it. (#26095)
@mastra/docker@0.9.3
Patch Changes
-
Fixed Docker sandbox process output corrupting non-ASCII characters. Emoji, CJK, and accented text in
stdout/stderrno longer turn into�when a character spans Docker stream frames — streamed output,handle.stdout/handle.stderr, andwait()results now contain the original text. (#26015) -
Fixed a spurious
kill(...) failed unexpectedlywarning when cancelling a command that had just finished on its own. If the command has already exited,kill()now returnsfalsequietly and the normal exit result is kept. Real failures to stop a running command are still logged. (#26179)
@mastra/duckdb@1.13.0
Minor Changes
-
Added filtered span queries to DuckDB observability storage with stable pagination, previews, and model cost. (#25791)
const result = await observabilityStorage.querySpans(plan);
-
Added
aggregateTraces()support to the DuckDB observability store. The store now advertises thetrace-aggregatecapability and returns grouped counts, error rates, duration statistics, and time-bucketed series over the same traces thatqueryTraces()selects. (#25835)const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z' }, groupBy: ['entityName'], interval: '1d', measures: ['count', 'errorRate'], }), ); const { rows, truncated } = await observability.aggregateTraces(plan);
queryTraces(),queryThreads(), and trace-query discovery are also faster over large stores: the time range, tenant scope, and simple root filters such asenvironment,entityName, orthreadIdnow narrow the scan before each trace's current root span is selected. Results are unchanged.
Patch Changes
-
Added token and cost measures to
aggregateTraces()in the DuckDB observability store. (#25971)- Token measures:
tokens.input,tokens.output,tokens.total,tokens.reasoning, andtokens.cached, each as.sumor.avg. - Cost measures:
cost.sumandcost.avg. Rows for cost requests also includecost: { coverage, unit }.
const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, groupBy: ['entityName'], measures: ['tokens.total.sum', 'cost.sum'], }), ); const { rows } = await observability.aggregateTraces(plan); // rows[0] → { dimensions: { entityName: 'support' }, measures: { 'tokens.total.sum': 7800, 'cost.sum': 3.75 }, cost: { coverage: 0.75, unit: 'usd' } }
Usage comes from the model token metrics of each trace. Retried metric writes count once, and spend recorded after the window ends still counts for traces that started inside it.
- Token measures:
-
Fixed trace list pages and trace queries that loaded most of the observability table and could fail with an out-of-memory error (#25518). (#25802)
listTraces,listTracesLightandlistBranchesnow read only the page's own spans. Oldest-first pages, such as the ones a retention job reads, no longer load the whole table.queryTracesreads only traces whose root spans fall in the requested time range, including when it filters on related spans.
On a 100k-trace store, the oldest
listTracespage went from loading about 1.9 GB of table data to about 32 MB, andqueryTracesfrom about 860 MB to about 25 MB. Results are unchanged. -
Reduced memory use when reading spans that carry large inputs or outputs (#25518). (#25932)
DuckDB loads every large value stored near a requested span when those values sit next to empty ones. Fetching one span or one trace page could therefore load hundreds of megabytes. New spans store missing
input,output,attributesandrequestContextvalues to avoid this. On a store with large agent payloads, reading one span went from about 316 MB to about 1 MB. The oldest trace page went from about 469 MB to about 145 MB.Results are unchanged. Spans written by earlier versions read the same as before and keep the old memory use until they are pruned.
@mastra/editor@0.16.0
Minor Changes
-
Added per-usage display conditions to prompt block references. A
prompt_block_refinstruction can now carry its ownrules, so an agent can include a shared stored block only in specific situations without adding rules to the shared block itself. If the stored block has rules too, both must pass. (#25991)This makes it possible to insert a runtime value from request context and fall back to a shared default block when the value is missing (#17878):
await mastra.getEditor()!.agent.update({ id: 'support-agent', instructions: [ { type: 'text', content: 'Follow the platform safety policy.' }, { type: 'prompt_block', content: '{{userPrompt}}', rules: { operator: 'AND', conditions: [{ field: 'userPrompt', operator: 'exists' }] }, }, { type: 'prompt_block_ref', id: 'default-user-prompt', rules: { operator: 'AND', conditions: [{ field: 'userPrompt', operator: 'not_exists' }] }, }, ], });
-
Added support for Composio shared connected accounts in
ComposioToolProvider. Fixes #18959. (#26002)Create shared accounts. Set
sharedConnectionsto create Composio SHARED accounts for connections authorized withscope: 'shared'. The optional ACL controls which Composio users can use the account. Without it, Composio's deny-by-default access applies.new ComposioToolProvider({ apiKey: process.env.COMPOSIO_API_KEY!, sharedConnections: { acl: { allowAllUsers: true } }, });
Select shared accounts. The connection picker now lists SHARED accounts, including accounts another user created in the Composio dashboard when their ACL grants access. Pinned SHARED accounts now report the correct connection status.
Fixed: Adding a second connection for the same toolkit failed with a multiple connected accounts error. You can connect multiple accounts per toolkit again.
-
Stored agents can now use a memory instance registered on your Mastra instance instead of copying its config. Set
memoryto{ type: 'id', memoryId: '<registry key>' }and the agent uses that exact instance, including its storage, processors, and working memory settings. Many stored agents can share one memory setup. (#25850)const mastra = new Mastra({ memory: { supportMemory }, editor: new MastraEditor(), }); await mastra.getEditor()!.agent.create({ id: 'support-agent', name: 'Support agent', instructions: 'Help customers.', model: { provider: 'openai', name: 'gpt-5' }, memory: { type: 'id', memoryId: 'supportMemory' }, });
If the key isn't registered, the agent loads without memory and a warning names the missing key. Existing inline memory configs keep working. Cloning an agent that uses registered memory now stores a reference instead of a copy. Fixes #21890.
@mastra/elysia@0.1.15
Patch Changes
- Fixed
createAuthMiddlewaredropping the refreshed sessionSet-Cookieafter a transparent session refresh. Raw Elysia routes protected bycreateAuthMiddlewarenow send the refreshed session headers on both allowed and denied (401/403) responses, alongside any cookies the route sets throughctx.cookie,set.headers, or a returnedResponse. Previously the refreshed cookie was lost, which could log users out or revoke sessions that use single-use refresh tokens. (#25796)
@mastra/factory@0.20.0
Minor Changes
-
Replaced the Factory web model-pack preferences API with one personal default model. New interactive chats start with that model, while existing chats keep their selected model. (#25997)
API consumers must replace calls to
/web/config/model-packswith the new default-model routes:await fetch('/web/config/default-model', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ modelId: 'openai/gpt-5.6' }), });
Use
GET /web/config/default-modelto read the preference andDELETE /web/config/default-modelto clear it.
Patch Changes
-
Fixed Review cards for pull requests that Factory discovers by polling GitHub, instead of through a webhook. These cards now record the pull request author. Previously the author was missing, so the reviewer refused to publish its verdict because it could not match the card to the pull request. (#25792)
-
Fixed issues and pull requests with the same number in two linked repositories collapsing into one Factory card. New GitHub cards are now keyed by repository and number, and reviews only link to pull request cards from their own repository. (#26094)
-
Updated controller model selection to use the model ID and optional options-object API. (#26069)
@mastra/inngest@1.10.3
Patch Changes
-
Fixed resumed Inngest agent runs so approved tool results and final responses continue reaching thread subscribers without replaying already completed output. (#25759)
-
Fixed Inngest agent traces so tracing exporters such as PostHog show every tool called during a run. The
MODEL_GENERATIONspan output now includes the run'stoolCalls. (#25819) -
Fixed sendToolApproval to preserve approval decisions when Inngest agents receive custom resume data. Approval-gated calls now reject custom resume data unless it is a non-null object that can carry the decision, and unresolved approval targets fail instead of resuming without the decision. (#25632)
@mastra/lance@1.5.0
Minor Changes
-
Fixed
indexConfig.type: 'ivfflat'creating a product-quantized (IVF PQ) index instead of an IVF Flat index.ivfflatnow creates a native LanceDB IVF Flat index, and the newivfpqtype creates the IVF PQ index thativfflatpreviously produced. (#26121)Indexes created before this release keep their IVF PQ type. Rebuild an index for a type change to take effect.
To keep IVF PQ, change
ivfflattoivfpq:// Before indexConfig: { type: 'ivfflat', numPartitions: 128, numSubVectors: 16 } // After indexConfig: { type: 'ivfpq', numPartitions: 128, numSubVectors: 16 }
To switch to IVF Flat, keep
ivfflat, removenumSubVectors, and rebuild the index:indexConfig: { type: 'ivfflat', numPartitions: 128 }
-
Added
optimize()andgetIndexCoverage()toLanceVectorStoreso you can keep vector indexes up to date. Rows written after an index is built stay outside the index and slow down queries until the table is optimized. Mastra never optimizes automatically, so you choose when to run it. (#26155)const [coverage] = await store.getIndexCoverage({ indexName: 'docs' }); if (coverage.numUnindexedRows >= 1_000) { await store.optimize({ indexName: 'docs' }); }
Concurrent
optimize()calls for the same table share one run.deleteUnverifieddefaults tofalse. See #26100.
Patch Changes
- Fixed
LanceVectorStore.createIndex()rebuilding an existing vector index on every call. Calling it again with the same column, index type, distance metric and build settings now reuses the existing index; changed settings or indexes replaced outside Mastra still trigger a rebuild. (#26122)
@mastra/livekit@0.5.0
Minor Changes
-
Raised the supported LiveKit Agents and plugin minimum to 1.7.1 so speech flushing and playback outcome APIs are available. Before: The peer range
^1.4.0accepted Agents 1.4–1.6. After: The minimum is1.7.1. Upgrade Agents and any installed LiveKit or Silero plugins to matching versions: (#24674)npm install @livekit/agents@1.7.1 @livekit/agents-plugin-livekit@1.7.1 @livekit/agents-plugin-silero@1.7.1
Existing session setup can stay in place. Attach the playback observer before starting the session:
import { voice } from '@livekit/agents'; import { observeVoiceSession } from '@mastra/livekit/plugin'; const session = new voice.AgentSession(); observeVoiceSession(session, { onSpeechComplete: result => console.log(result.outcome), });
-
Added a browser-safe
@mastra/livekit/cliententry point for recording playback. It exportsgetLiveKitRecording,LiveKitRecording, andLiveKitRecordingResponse, reusing a Mastra client's authentication and fetch configuration without importing LiveKit server or worker code. (#24674)Applications using the client entry point need
@mastra/client-js1.51.2 or newer within 1.x. The SDK is an optional peer; server and worker imports do not require it.If you used
client.getLiveKitRecording(traceId)in an earlier snapshot, replace it with:import { getLiveKitRecording } from '@mastra/livekit/client'; const recording = await getLiveKitRecording(client, traceId);
Studio now uses this entry point for Review Audio.
-
Fixed recording URL validation so malformed URLs produce validation failures without throwing unexpected exceptions. Playback remains limited to HTTP and HTTPS URLs. (#24674)
Aligned the Zod peer range with LiveKit Agents:
^3.25.76 || ^4.1.8. Older Zod versions are no longer accepted. Upgrade before installing this release:npm install zod@^3.25.76 # Or use Zod 4: npm install zod@^4.1.8 -
Added per-turn generation metrics, playback completion hooks, and speech-segment flushing.
onTurnCompletekeeps its generation-only behavior;onSpeechCompletereports server playback outcomes without changing memory persistence. (#24674)// Before: generation completion only createLiveKitWorker({ mastra, onTurnComplete: handleGeneration }); // After: observe generation and playback separately createLiveKitWorker({ mastra, onTurnComplete: handleGeneration, onTurnMetrics: handleMetrics, onSpeechComplete: handlePlayback, });
Reply generators can now emit a
VOICE_TEXT_FLUSHboundary as well as strings. Custom string-only generators still work. Code consuming generator output directly must handle the boundary explicitly:// Before text += chunk; // After if (typeof chunk === 'string') text += chunk; else flushSpeechSegment();
For
MastraLLM, use themastraLLMNodeadapter in the LiveKit agent to translate these boundaries. Playback transcripts may be partial or estimated; server playback does not prove that the remote listener heard the audio.
Patch Changes
-
Required an explicit recording authorization callback before trace lookup or playback URL resolution. Missing policies fail at route registration, and denied requests cannot access recording storage. (#24674)
// Before: authentication alone did not check recording ownership. liveKitRecordingRoute({ resolveRecording }); // After: the application verifies access to the requested trace. liveKitRecordingRoute({ authorize: authorizeRecording, resolveRecording });
The authorization callback must return
trueto permit access, including whenrequiresAuthisfalsefor a local demo. -
Fixed
vad: falseto disable voice activity detection when creating a LiveKit session. LiveKit treats an omitted VAD as a request for its bundled default, so the worker now explicitly opts out. Explicit VAD instances, prewarmed Silero, andsessionOptionsoverrides keep their existing behavior. (#24674) -
Fixed
liveKitConnectionRoute()to return HTTP 409 when recording is requested for an existing room, without dispatching an agent or issuing a participant token. Use a uniqueroomNamefor each recorded call. (#24674)Programmatic callers of
dispatchVoiceSession()can distinguish this conflict with the exportedLiveKitRecordingRoomConflictError:import { dispatchVoiceSession, LiveKitRecordingRoomConflictError } from '@mastra/livekit'; try { await dispatchVoiceSession({ roomName, recording }); } catch (error) { if (error instanceof LiveKitRecordingRoomConflictError) { console.error(`Room ${error.roomName} already exists; start the call with a fresh room name.`); } else { throw error; } }
@mastra/loggers@1.3.5
Patch Changes
-
HttpTransportno longer buffers logs without limit while its endpoint is unavailable. The buffer is now capped bymaxBufferSize(default 10,000 entries); when it is full, the oldest logs are dropped first. UsegetDroppedLogCount()to see how many logs were dropped. Only one flush request is sent at a time, so an outage no longer triggers a new failing request for every log written. Destroying the transport now sends every buffered batch before it finishes, not just the first one.batchSizeandmaxBufferSizemust be positive integers; the constructor now throws for values such as0,Infinity, or2.5(leavebatchSizeunset to use the default of 100). (#25958)import { HttpTransport } from '@mastra/loggers/http'; const transport = new HttpTransport({ url: 'https://logs.example.com/ingest', maxBufferSize: 5_000, }); transport.getDroppedLogCount(); // number of logs dropped because the buffer exceeded maxBufferSize
-
Fixed
HttpTransportretrying logs that the endpoint will never accept. When the endpoint rejects a batch as invalid, such as with a 400 or 413 response, the transport stops retrying right away. It drops that batch and counts it ingetDroppedLogCount(), so newer logs are no longer stuck behind it. (#26079)Temporary failures are still retried, including network errors, timeouts, rate limits and server errors. If the server sends a
Retry-Afterheader, the transport waits that long before retrying, up to the requesttimeout. -
FileTransportlog queries now read the log file as a stream instead of loading it synchronously, solistLogs()no longer blocks the event loop and paginated queries only keep the requested page in memory.listLogsByRunId()now finds matching logs beyond the first 100 records. (#26191)
@mastra/mcp@2.2.0
Minor Changes
-
Added
MCPClient.getServerInfo(), which returns the identity each connected MCP server announced when it connected: its name, version, and, when provided, title, description, website URL, and icons. Use it to show users which server they connected instead of only the URL they entered. A server's entry isundefinedif it has not connected yet, or if it connected without announcing an identity, which newer servers are allowed to do. Fixes #24559. (#25849)await mcp.listTools(); const info = mcp.getServerInfo(); console.log(info.myServer?.title, info.myServer?.version, info.myServer?.icons);
-
MCPServernow announces its full identity to MCP clients:title,description,websiteUrl, andicons, alongsidenameandversion. Clients, includingMCPClient.getServerInfo(), can show a display title, description, website, and logo for a Mastra server instead of only its name. The registry server info returned bygetServerInfo()on the server is unchanged. Fixes #25856. (#25985)const server = new MCPServer({ name: 'weather-server', version: '1.0.0', title: 'Weather Server', description: 'Forecasts and current conditions', websiteUrl: 'https://weather.example.com', icons: [{ src: 'https://weather.example.com/icon.png', mimeType: 'image/png', sizes: ['48x48'] }], tools: { weatherTool }, });
@mastra/mcpnow requires@mastra/core1.75.0 or later.
Patch Changes
-
Fixed MCP Apps not opening in hosts that read the app link from a tool call result.
MCPServernow returns a tool's_meta.ui.resourceUri(and the flatui/resourceUrikey for older hosts) on successfultools/callresults, as it already does ontools/list.getMcpCallToolMeta(result)from Mastra's own client now returns the link. Fixes #21277. (#25990)Two related cases change with it:
- Tools that declare two different links:
tools/listandtools/callnow both report the nestedui.resourceUriunder both keys, so hosts reading either key open the same app. - Tools that declare only the flat
ui/resourceUrikey: the server now advertises the MCP Apps extension for them, as it does for the nested form.
- Tools that declare two different links:
@mastra/memory@1.36.0
Minor Changes
-
recall({ vectorSearchString })and the message writes behind it now work against a vector store that generates embeddings itself, matching the agent-turn path. With such a store and noembedderconfigured, saved messages are sent as text and the search string is embedded server-side. (#25009)const memory = new Memory({ storage, vector: selfEmbeddingVector, options: { semanticRecall: true }, }); await memory.saveMessages({ messages }); const { messages: recalled } = await memory.recall({ threadId, resourceId, vectorSearchString: 'project deadline', });
cloneThreadandupdateThreadResourceIdreach the same path. A cloned thread's messages are embedded for semantic recall, and transferring a thread to another resource moves its message vectors to the new owner so resource-scoped recall keeps finding them. -
Added per-provider idle activation TTLs to observational memory. Pass an object to
activateAfterIdleto set a TTL for specific providers and adefaultfor the rest. Use this when your requests set a prompt cache TTL that'auto'can't detect, such as Anthropic's per-messagecacheControl: { ttl: '1h' }. (#25727)Before
// 'auto' uses 5 minutes for Anthropic: after 5 idle minutes OM activates and rewrites the prompt, invalidating a 1-hour cache that is still warm observationalMemory: { activateAfterIdle: 'auto', }
After
observationalMemory: { activateAfterIdle: { default: 'auto', anthropic: '1h' }, }
Keys match the model's provider before the first
.(soanthropicmatchesanthropic.messages), case-insensitively. Each value takes the same forms as before: milliseconds, a duration string,'auto', orfalse. Existing single-value settings behave exactly as before.
Patch Changes
-
Observational memory no longer sends the Observer attachments that the agent recorded as unavailable. The Observer still sees their
[Image #1: ...]or[File #1: ...]line in the transcript. See #23705. (#25051) -
Fixed CPU usage that kept growing when observational memory was created per request. Apps that build a new
Memoryinstance for each request no longer slow down over time on Node.js versions before 24. (#25872) -
Fixed Observational Memory sending an oversized prompt when a large tool result pushed a step over the
messageTokensthreshold. Activating buffered observations could leave the newest messages unobserved and still above the threshold, and the next model call could exceed the model's context window (for example, failing with "prompt is too long"). Observational Memory now activates every buffered observation it needs, then observes any remaining messages synchronously when the context is still above the threshold. Fixes #19767. (#25060) -
Fixed Observational Memory degenerate-output detection so very long lines are truncated and retained, repeated short lines share the existing bounded budget, and genuinely repetitive output remains rejected. (#25212)
Fixes #24354.
-
Fixed Observational Memory recording completed background-task tool results as
null. Anullstored model output now falls back to the actual tool result, matching how@mastra/corereplays tool results, so the Observer, token counting, and recall see the real result. (#25961) -
Fixed Observational Memory saving a cut-off or empty observation when the observer or reflector model call failed partway through. This covered streams that ended early (for example Gemini reporting a finish reason of
other) and transient provider errors such as a 429 or 500. These calls now retry the whole request from the original prompt instead of continuing from the partial reply, so only complete replies are saved. Fixes #24810. (#25058)
@mastra/mesa@0.3.0
Minor Changes
-
Upgraded to
@mesadev/sdk0.49.1.MesaFilesystemnow authenticates with a Mesa private key and mounts a Mesa layout, matching the current Mesa SDK.privateKeyfalls back toMESA_PRIVATE_KEYwhen omitted. (#25738)Breaking:
apiKey,org, andreposare replaced byprivateKey,authors, andlayout. Paths now start at the layout path instead of/<org>/<repo>.Before
import { MesaFilesystem } from '@mastra/mesa'; const filesystem = new MesaFilesystem({ apiKey: process.env.MESA_API_KEY, org: 'acme', repos: [{ name: 'docs', bookmark: 'main' }], }); await filesystem.readFile('/acme/docs/README.md');
After
import { MesaFilesystem, repo } from '@mastra/mesa'; const filesystem = new MesaFilesystem({ privateKey: process.env.MESA_PRIVATE_KEY, authors: [{ name: 'My Agent', email: 'agent@example.com' }], layout: { '/docs': repo('docs', { mode: 'rw', at: { bookmark: 'main' } }) }, }); await filesystem.readFile('/docs/README.md');
@mastra/mongodb@1.22.0
Minor Changes
-
MongoDBVectortakes anautoEmbedconfig in its constructor and reports itself as a self-embedding store, soMemory's semantic recall can use Automated Embedding with no client-side embedder. (#25009)new MongoDBVector({ id: 'vec', uri, dbName, autoEmbed: { model: 'voyage-4' } });
A
createIndexcall naming neither its ownautoEmbedconfig nor adimensionpicks up those defaults; naming either one overrides them, so one store can hold both kinds of index.
Patch Changes
-
Fixed vector queries failing for a few seconds after a new index is created. While Atlas first builds a vector search index, a query can fail with "cannot query vector index ... while in state INITIAL_SYNC".
querynow retries across that window instead of throwing, which matters for a caller that queries an index straight after creating it, as semantic recall does on a fresh database. (#25009) -
Fixed semantic recall returning a message the search had not selected, along with the wrong surrounding context, when several messages share a timestamp. (#25009)
Messages are ordered by
(createdAt, id), butlistMessagesresolved the message named byincludeoncreatedAtalone. Saving messages in one batch gives them the same timestamp routinely, so this was reachable on any query and failed with no error.
@mastra/observability@1.18.4
Patch Changes
-
Fixed stuck spans in Braintrust, LangSmith and PostHog traces. A span that ended right after it started stayed open until shutdown. Shutdown then marked it as failed with 'Observability is shutting down.' This happened, for example, when a tool call failed input validation. These spans now end with their real output. Fixes #26088. (#26136)
-
Added the
toolsattribute toMODEL_INFERENCEspans. It holds the tool definitions sent to the provider on that call, so exporters can read the tools for each model call. (#25917)
@mastra/pg@1.30.0
Minor Changes
-
Added filtered span queries to PostgreSQL v-next observability storage with stable pagination, previews, and model cost. (#25791)
const result = await observabilityStorage.querySpans(plan);
-
Added
aggregateTraces()support to the PostgreSQL observability store. The store now advertises thetrace-aggregatecapability and returns grouped counts, error rates, duration statistics, and time-bucketed series over the same traces thatqueryTraces()selects. (#25722)const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z' }, groupBy: ['entityName'], interval: '1d', measures: ['count', 'errorRate'], }), ); const { rows, truncated } = await observability.aggregateTraces(plan);
Patch Changes
-
Fixed standalone storage domains (
MemoryPG,WorkflowsPG,ObservabilityPG, and others) ignoring an explicitssloption when the connection string containssslmode=. Passingssl: { rejectUnauthorized: false }now works against servers with self-signed or private-CA certificates, matchingPostgresStore. (#26130) -
Fixed
PostgresStore.init()failing withschema "<name>" does not existwhen a process connects to more than one database using the sameschemaName. Each database now creates its own schema during initialization. (#26135) -
Fixed
PgVector.query()on HNSW indexes for pgvector versions before 0.8.0. On Postgres 15 or later, these queries failed withinvalid configuration parameter name "hnsw.iterative_scan". The query now setshnsw.iterative_scanonly on pgvector 0.8.0 or later. (#26013) -
Fixed
$existsfilters on nested metadata keys inPgVector. A filter like{ 'doc.lang': { $exists: false } }used to match every vector, and$exists: truematched none. RunningdeleteVectorswith such a filter could delete every vector in the selected namespace. Now$exists: truematches vectors where the nested key is present, and$exists: falsematches vectors where it is missing. (#25976) -
Added token and cost measures to
aggregateTraces()in the PostgreSQL observability store. (#25967)- Token measures:
tokens.input,tokens.output,tokens.total,tokens.reasoning, andtokens.cached, each as.sumor.avg. - Cost measures:
cost.sumandcost.avg. Rows for cost requests also includecost: { coverage, unit }.
const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, groupBy: ['entityName'], measures: ['tokens.total.sum', 'cost.sum'], }), ); const { rows } = await observability.aggregateTraces(plan); // rows[0] → { dimensions: { entityName: 'support' }, measures: { 'tokens.total.sum': 7800, 'cost.sum': 3.75 }, cost: { coverage: 0.75, unit: 'usd' } }
Usage comes from the model token metrics of each trace. Retried metric writes count once, and spend recorded after the window ends still counts for traces that started inside it.
- Token measures:
@mastra/playground-ui@61.0.0
Minor Changes
-
Improved tool approvals in Factory and Studio. Both now show the decision as a status badge beside the tool name and preview the complete file content before you approve. Pending requests stay expanded so the actions stay visible; once decided, Studio details can collapse without hiding the decision. (#25951)
Changed
ToolApprovalActionsnow renders only the Approve and Decline buttons and no longer acceptsstatus. Render it only while the request is pending, and show the decision with the newToolApprovalStatusbeside the tool name.Before:
<ToolApprovalActions status={decision} toolName="write_file" onApprove={approve} onDecline={decline} />
After:
<> <ToolApprovalStatus status={decision} /> {!decision && <ToolApprovalActions toolName="write_file" onApprove={approve} onDecline={decline} />} </>
ToolApprovalrenders both parts for you and now acceptsargsto preview the tool arguments.Removed
ToolApprovalButtonsfrom@mastra/playground-ui/domains/chat/tools/badges/tool-approval-buttons. Wrap the tool's details inToolApprovalBadgefrom@mastra/playground-ui/domains/chat/tools/badges/tool-approval-badgeinstead: it takes the tool'sBadgeWrapperprops plus the approval request, shows the status beside the tool name, and keeps the request expanded until it is decided.ToolApprovalButtonsPropsis nowToolApprovalRequest.Before:
<BadgeWrapper title="Write file"> {details} <ToolApprovalButtons {...request} /> </BadgeWrapper>
After:
<ToolApprovalBadge title="Write file" approval={request}> {details} </ToolApprovalBadge>
-
Added
useDropZoneto turn any element into a native drop target for one drag type. It reports while an accepted drag is over the element and hands the dropped data to your handler. Drags of other types, such as files, pass through untouched. (#25786)import { useDropZone } from '@mastra/playground-ui/hooks/use-drop-zone'; const { isDragOver, dropZoneProps } = useDropZone({ accept: 'application/x-task', dropEffect: 'move', onDrop: dataTransfer => moveTask(dataTransfer.getData('application/x-task')), }); <section className={isDragOver ? 'bg-fill-hover' : undefined} {...dropZoneProps} />;
-
Added thinking-level controls for picking a model's reasoning level.
ThinkingLevelPickeris a signal-bars button that sits next to a model picker in aButtonsGroupand opens a ramp of the model's levels in a popover. When the model has no levels, it shows as disabled with a tooltip that explains why.ThinkingLevelRampand the compactThinkingLevelSlidersave when you let go, and hold the dropped level until the save finishes. (#25799)import { ThinkingLevelPicker } from '@mastra/playground-ui/components/ThinkingLevel'; <ThinkingLevelPicker label="Thinking" options={[ { value: 'off', label: 'Off', emphasis: 'muted' }, { value: 'medium', label: 'Medium' }, { value: 'high', label: 'High', emphasis: 'warning' }, ]} value={level} onChange={setLevel} />;
-
Text roles (#25827)
Added shared headline, introduction, and uppercase section-label roles to Txt:
<Txt as="h1" variant="hero">Welcome</Txt> <Txt variant="lead">Introduction</Txt> <Txt as="span" variant="eyebrow">Recent activity</Txt>
Text elements and component ownership
Txt supports text elements only; native containers and controls retain their markup, default typography, and child composition.
Migrate text-only div usages to paragraphs:
// Before <Txt as="div" variant="body">Content</Txt> // After <Txt as="p" variant="body">Content</Txt>
Keep a native layout wrapper for controls or multiple blocks. Use Code for preformatted code and Txt at the text leaf. Shared components continue to own their typography through existing size and semantic variants, without generic text-role forwarding props. Command groups retain compact uppercase headings.
Editor fonts
CodeEditor accepts
font="body"for prose editors and defaults tofont="mono"for code. -
Added
opensViewto sidebar links. A link withopensView: trueshows a trailing caret so users can tell it opens its own navigation view. (#25868)<Sidebar.Sections sections={[{ key: 'infra', links: [{ name: 'Gateway', url: '/gateway', icon: <GatewayIcon />, opensView: true }] }]} />
-
Added
InputNumber, built on Base UI NumberField, with the same styling asInputGroup. Compose it with optional increment and decrement buttons, decimal steps, and minimum or maximum values. (#25416)import { InputNumber, InputNumberDecrement, InputNumberGroup, InputNumberIncrement, InputNumberInput, } from '@mastra/playground-ui/components/InputNumber'; <InputNumber defaultValue={0.7} min={0} max={2} step={0.1}> <InputNumberGroup> <InputNumberDecrement /> <InputNumberInput aria-label="Temperature" /> <InputNumberIncrement /> </InputNumberGroup> </InputNumber>;
Inside a
Field, useFieldLabelandFieldErrorto name the input and associate validation errors. Existing numeric inputs are unchanged. -
Added an inset variant to
CommandDialog. It wraps the search and results in one panel inside a muted frame, pins the dialog near the top so the input stays put while results change height, and takes afooterfor actions like feedback links or keyboard hints.DialogContentalso acceptsshowCloseButton={false}to hide its close button. (#25894)<CommandDialog variant="inset" open={open} onOpenChange={setOpen} footer={ <Button variant="ghost" size="sm"> Send feedback </Button> } > <CommandInput placeholder="Search" /> <CommandList scrollArea scrollAreaViewportClassName="max-h-dropdown"> {/* groups */} </CommandList> </CommandDialog>
-
Changed the sidebar More row to work like Linear's. (#25931)
More menu. More opens a floating menu sized to its content, listing the optional links you moved out of the sidebar, then "Customize sidebar". Opening it no longer pushes the rest of the sidebar down. A link from the More menu appears in place while you are on its page and leaves when you navigate away.
Customize sidebar. "Customize sidebar" opens a dialog listing every optional link per section. Each link has one of three placements:
- Always show: stays in the sidebar.
- Hide in More menu: reachable from More.
- Never show: hidden from both the sidebar and More.
Right-click an optional link in the sidebar to change its placement without opening the dialog. Choices save in local storage and survive reloads.
Pass
visibilityStorageKeytoSidebar.Sectionsto scope saved choices per product, anddefaultVisibleon an optional link to show it by default. A section with a single optional link keeps showing it without a More row.recentItemsStorageKeyis replaced byvisibilityStorageKey, and links are no longer promoted automatically after a visit.// Before <SidebarNew.Sections sections={sections} recentItemsStorageKey="my-app:sidebar" /> // After <Sidebar.Sections visibilityStorageKey="my-app:sidebar" sections={[ { key: 'primitives', links: [{ name: 'Agents', url: '/agents' }], moreLinks: [ { name: 'Tools', url: '/tools' }, { name: 'Workspaces', url: '/workspaces', defaultVisible: true }, ], }, ]} />
Saved visit history is not migrated. Users can pick placements again through More → Customize sidebar.
-
Added
SavedViewsto save named filters and page settings in the current browser. Users switch views from tabs, preview their filters, and save or reset edits without losing the selected tab. Failed saves keep edits available to retry. (#25786)import { SavedViewEditor, SavedViewTabs, useSavedViews } from '@mastra/playground-ui/components/SavedViews'; const views = useSavedViews({ storageKey: 'my-page.views', settingsSchema, activeViewId, onActiveViewChange }); const setFilters = filters => (views.applied ? views.change({ filters }) : setPageFilters(filters)); <SavedViewTabs views={views} fields={fields} operators={operators} defaultLabel="All" newViewSettings={settings} /> <SavedViewEditor views={views}> <FilterBar fields={fields} value={views.applied?.filters ?? pageFilters} onValueChange={setFilters} /> </SavedViewEditor>
-
Added
headerActionsSlottoTraceSpanPanelandTraceDataPanelViewso applications can place actions such as Review Audio alongside the trace controls. Existing panels keep their current behavior when the slot is omitted. (#24674)<TraceSpanPanel {...tracePanelProps} headerActionsSlot={<button onClick={openRecording}>Review Audio</button>} />
-
Metrics KPI cards now show the change badge next to the card title, and hovering or focusing it names the window it compares against, such as "vs previous 7d (693)". The window follows the selected time range, including custom ranges, instead of the generic "vs prior period". (#25968)
MetricsKpiCard.Changenow takes acomparisonprop for the tooltip text, and the newMetricsKpiCard.Headerplaces it beside the label.<MetricsKpiCard.Header> <MetricsKpiCard.Label>Agent runs</MetricsKpiCard.Label> <MetricsKpiCard.Change changePct={2.6} comparison="vs previous 7d" prevValue="692" /> </MetricsKpiCard.Header>
-
Added
DataList.Groupfor blocks of rows that share the list's columns but keep their own hover. A group's row lights only while the pointer is on it or in the gap between two of the group's rows. Headings, buttons, empty states and padding around the rows stay dark, and the highlight never jumps to another group. (#25947)<DataList columns="auto 1fr auto"> <DataList.Group aria-label="Open">{openRows}</DataList.Group> <DataList.Group aria-label="Closed">{closedRows}</DataList.Group> </DataList>
-
Added
card-title,card-title-tight, andcard-title-strongroles toTxt. Factory card titles keep their existing appearance when developers use these shared styles. (#25784)<Txt variant="card-title-tight" tone="ink"> Review the deployment configuration </Txt>
-
Removed the unused outlined tab frame. Contained tabs now use the inset frame by default; explicit frame="inset" remains supported. (#25720)
Before:
<Tabs defaultTab="overview" appearance="contained" frame="stroke"> {children} </Tabs>
After:
<Tabs defaultTab="overview" appearance="contained" frame="inset"> {children} </Tabs>
The same frame change applies to TabbedContainer.
-
Added
EmptyStateIllustration, line art for empty and error states that animates on hover. Pass it toEmptyState'siconSlot. There are eight for empty screens (traces,logs,api-keys,environments,requests,databases,threads,deploys) and five for failures (disconnected,server-error,locked,not-a-member,rate-limited).tone="error"tints it for failed loads. (#25895)<EmptyState variant="fill" iconSlot={<EmptyStateIllustration name="logs" />} titleSlot="No logs yet" descriptionSlot="Logs appear here when your project runs." />
-
Merged
MainSidebarandSidebarNewinto oneSidebarcomponent at@mastra/playground-ui/components/Sidebar. Products used to build one sidebar from two overlapping components with different spacing; they now share one API and one look. (#25931)Breaking: the
@mastra/playground-ui/components/MainSidebarand@mastra/playground-ui/new/sidebarimport paths are removed. Import everything from@mastra/playground-ui/components/Sidebar.// Before import { MainSidebar, MainSidebarProvider } from '@mastra/playground-ui/components/MainSidebar'; import { SidebarNew, useSidebarNew } from '@mastra/playground-ui/new/sidebar'; import type { SidebarNewLink, SidebarNewSection } from '@mastra/playground-ui/new/sidebar'; <MainSidebarProvider storageKey="my-app-sidebar" LinkComponent={Link}> <SidebarNew> <SidebarNew.Sections sections={sections} /> </SidebarNew> <MainSidebar.MobileTrigger /> </MainSidebarProvider>; // After import { Sidebar, SidebarProvider, useSidebar } from '@mastra/playground-ui/components/Sidebar'; import type { SidebarLink, SidebarSection } from '@mastra/playground-ui/components/Sidebar'; <SidebarProvider storageKey="my-app-sidebar" LinkComponent={Link}> <Sidebar> <Sidebar.Sections sections={sections} /> </Sidebar> <Sidebar.MobileTrigger /> </SidebarProvider>;
Renames
SidebarNew→Sidebar, and everySidebarNew.*part keeps its name:Sidebar.Header,Sidebar.Nav,Sidebar.NavStack,Sidebar.Footer,Sidebar.Meter…MainSidebarProviderandSidebarNew.Provider→SidebarProviderorSidebar.ProvideruseMainSidebaranduseSidebarNew→useSidebarMainSidebar.MobileTrigger→Sidebar.MobileTriggerMainSidebar.Bottom→Sidebar.Footer- Types:
NavLinkandSidebarNewLink→SidebarLink;SidebarNewSection→SidebarSection; every otherSidebarNew*Props→Sidebar*Props data-slot="sidebar-new-*"→data-slot="sidebar-*", for examplesidebar-header,sidebar-brandandsidebar-nav-stack-view. Update CSS or test selectors that target them.- The default
visibilityStorageKeyis nowmastra:sidebar:link-visibility.
Collapsed state and width keep their storage keys, so users keep their sidebar layout after upgrading.
-
Added
WorkspaceTreeView, a file browser for a workspace with a lazily loaded folder tree, combined file and skill search, and a file viewer for Markdown, syntax-highlighted code, images, and videos. (#25700)import { WorkspaceTreeView } from '@mastra/playground-ui/domains/workspace'; const workspace = client.getWorkspace('my-workspace'); <WorkspaceTreeView workspaceId="my-workspace" activeFilePath={activeFilePath} onActiveFileChange={setActiveFilePath} onDelete={({ path, type }) => workspace.delete(path, { recursive: type === 'directory' })} onCreateDirectory={path => workspace.mkdir(path, true)} />;
- Paths are workspace-relative and
.is the root, as on the server. - Folder listings and the open file refresh every 10 seconds. The skeleton only shows on the first load.
- The search icon swaps the tree for a search panel. Typing searches files and skills in parallel.
- Hovering or focusing a tree row shows the file size. When you pass
onDelete, the row also has a delete action. When you passonCreateDirectory, the header has a "New folder" button. Without these callbacks, the actions don't appear. - Errors show a notice that explains the cause, such as an expired session, missing permission, a missing path, or an unsupported operation.
- Use
renderPreviewto replace how a file is shown. Returnundefinedto keep the built-in preview. - Markdown files that start with YAML frontmatter (such as
SKILL.md) show it as a YAML block above the rendered body.WorkspaceMarkdownPreviewandsplitFrontmatterare exported so customrenderPreviewfactories can reuse them. - Use
asideActionsto add your own icon buttons to the aside header.
The layout is also available as composable
Workspace.*parts (Root,Aside,AsideHeader,Search,SearchToggle,CreateDirectory,Tree,ActiveFile,ActiveFileHeader,FilePath,ActiveFileContent).Also added
FileIcon,TrashIcon, andSearchIconto the design-system icon set.The open file is controlled: the parent owns
activeFilePath(state, URL, …) and updates it fromonActiveFileChange, which also receivesundefinedwhen a delete closes the open file.More optional props for embedding the view in a page:
readOnlyPaths: folders where delete and new folder are hidden. Pass['.']to make the whole workspace read-only.searchFiles/searchSkills: turn each search source on or off. The search button is hidden when both are off.
The tree also opens the parent folders of the active file and scrolls it into view, shows optional
fileCount/skillCounttotals in the aside title (for example3 Skills), and marks mounted folders with their provider icon, a lock when read-only, and an alert when the mount failed.Pass
addSkillto show an "Add skill" action (in the aside header and the empty state) that opens the skills.sh browser and calls youronInstallhandler. - Paths are workspace-relative and
Patch Changes
-
Fixed the default hover highlight, MCP loading text, workspace folder and error icons, and fractional stroke widths on checkbox and switch icons and the workflow time dial. These utilities now generate styles with the current theme. (#24680)
-
Trace and thread loading skeletons now match the loaded layout, so the panels no longer shift when data arrives. The span tree placeholder no longer shows a second search field, its rows have the same height as the real span rows, and the thread placeholder lines up its tabs, messages column and span tree with the loaded thread. (#25812)
The Messages, Feedback and Scores tabs in the trace panel now appear only once the spans have loaded, so the first tab no longer switches from Feedback to Messages. Rows in the full thread view are no longer dimmed when they are out of view or hovered.
-
FilterBarchips with several values now show the first value and a count, such asprod +2. Hovering the chip or using a screen reader still gives every value. (#25786)Fixed chip text copying
[object Object]from hidden form inputs. -
Fixed responsive layout classes being overridden in apps that load
@mastra/playground-ui/style.cssbefore their own styles. The workflow graph no longer loads a second copy of the stylesheet, so classes likelg:blockandlg:gap-x-32apply again. (#25902) -
Fixed search fields and other form fields collapsing to a few pixels wide when placed inside the new sidebar navigation. (#25986)
-
Fixed search inputs overflowing narrow toolbars. (#25786)
-
Fixed the
ChatShellcomposer bouncing with the transcript when you scroll past either end in Chrome, vertically or sideways. Nothing shows through behind or beside the composer anymore: the transcript fades out above it. The scroller also keeps the same room on both sides (--chat-edge,0.5remby default), so its scrollbar never overlaps the composer and the column stays centred. (#25592) -
Added a shared sent-attachment component and Storybook examples for images, PDFs, text, and spreadsheets in Studio and Factory chat. (#25921)
-
Fixed three issues in
Commandlists: (#25751)- Clicking a disabled item no longer selects the item the hover highlight moved to.
- A
Commandinside a dialog no longer paints over the dialog's outline. CommandItemonly sizes and colors its own icons, so icons inside aBadgeor other nested element keep their color when the row is selected.
-
Fixed uneven spacing in RelativeTimestamp when it sits inside a sentence such as "Deployed 1h ago". Only short labels like "1h" stay monospace; words like "ago" and full dates now match the surrounding text. (#25867)
-
Opening the full thread from a trace now shows the thread in its own drawer stacked above the trace, instead of replacing it. The trace stays visible underneath, and "Back to trace" or Escape closes only the thread drawer.
TraceThreadPanelnow takesopenand an optionaldepthprop, andonBackwas removed in favor ofonClose. (#25812)The "Open full thread" button now sits in the trace side column's tab row, next to Messages, Feedback and Scores, instead of above the conversation.
When the trace side column is 500px wide or less, the Messages, Feedback and Scores tabs and the "Open full thread" button collapse to icons, with tooltips on hover and keyboard focus.
Tabnow accepts an optionaltooltipprop. -
Improved sent attachment cards in chat with readable filenames, file-type icons, image previews, and PDF and text dialogs. Attachments appear above the message bubble in Studio and Factory and share its borderless surface and subtle shadow. Image previews have a light inner highlight and progressive blur, with a mask that keeps captions readable in both themes. (#25922)
-
Fixed default buttons blending into cards. A default
Buttonnow uses the same fill and edge as the inputs beside it in light and dark themes, so it stands out on a card, settings group, or dialog instead of matching it. Selects and inputs inside aButtonsGroupnow show a single line at each seam instead of two. (#25755) -
Bumped the jsdom dev dependency to ^30.1.1 for tests. No runtime changes. (#25830)
Also in: @mastra/react@1.8.0
-
"Highlight spans" in the trace panel now only highlights the spans behind a message: it no longer opens the span detail panel, and it scrolls the last highlighted span into view. (#26064)
-
Fixed
MessageScrollerwithautoScrollnot following the first reply in a new conversation. (#25592) -
Trace panel columns (messages, trace, span) are now resizable down to a minimum width; the messages column is wider by default. In the thread view, the span detail column can also be resized, up to half the width. The divider in data panel headers is now decorative and no longer announced as a separator by screen readers. (#25790)
-
Improved KPI labels to use the shared card-heading typography without changing their appearance. (#25773)
-
Form text and destructive red now match across controls: (#25756)
- Select and Combobox show the picked value in medium weight, matching text typed into an Input.
- Validation messages under a field are 13px, the same size as the label and value, instead of 12px.
red-400moves onto the same lightness step as the other hues, so destructive text in dark mode reads as red instead of salmon.- Destructive badges use the same light text as the other status badges, so their label stays readable on the red tint.
- Metrics KPI values drop from semibold to the medium weight of the title role, so no text in the design system goes above 500.
-
Composer attachments now show filenames in consistently sized, compact cards with tighter bottom spacing that stays consistent when scrolling. Remove controls appear on hover or keyboard focus and stay visible on touch devices. Studio shows attachments inside the composer with the same spacing as Factory. (#26061)
-
Fixed trace views for recovered DurableAgent runs. A span that a crashed process left open now shows an "Interrupted" notice instead of being displayed as a regular result. (#25862)
-
Added React Query hooks for the Mastra client, grouped by domain under
@mastra/react/hooks/<domain>(for exampleagents,workflows,traces,metrics,datasets,memory,mcps,tools). React apps can