Highlights
Studio Workflow Builder Backend (persisted workflows)
A new Studio Workflow Builder backend can be enabled via the workflowBuilder editor option, adding an editor-owned agent that authors persisted workflow definitions plus new server endpoints (GET /editor/workflow-builder/settings, POST /editor/workflow-builder/stream) gated by stored-workflow permissions.
New @mastra/connect Package (platform integrations → agent tools)
@mastra/connect turns Mastra Platform integration connections into agent tools that execute through the platform connection proxy (credentials injected + token refresh) so your app never handles provider secrets; it supports live discovery/refresh of connected tools and optional allowlisting via integrations.
Thread Ownership Transfer (resourceId reassignment)
Threads can now be transferred to a new resourceId with Memory.updateThreadResourceId({ threadId, resourceId }), a new server route POST /memory/threads/:threadId/transfer, and client-js support via MemoryThread.transfer(), with transactional/serialized implementations across major SQL adapters (and vector migration when semantic recall is enabled).
More Powerful Background Tool Execution Controls
Tools now support per-call background dispositions (foreground/deferred/awaited), awaited background calls, caller-scoped completion signals via createBackgroundWorkSignalProcessor(), and bounded delegated background tool execution—enabling more predictable UX and orchestration around deferred work.
Workspace & Sandbox Capabilities: Bulk File Upload + Permissions + Performance
WorkspaceSandbox.writeFiles now supports per-file POSIX mode and optional cancellation via abortSignal, and DockerSandbox implements bulk uploads plus richer Docker mounts (including volume subpaths). Workspace grep/list_files can be accelerated by provider-native walk()/grep() to avoid per-file round trips on remote filesystems.
Breaking Changes
- Replace
subscribeQueuedMessages({ resourceId, threadId }, listener)withsubscribeThreadEvents({ resourceId, threadId }, listener)(listener now receives typed events likequeue-count-changed). @mastra/archil:ArchilFilesystem.grep()renamed todiskGrep().
Changelog
@mastra/core@1.67.0
Minor Changes
-
Added the Studio Workflow Builder backend. Configure the editor with the new
workflowBuilderoption to enable a hidden, editor-owned agent that authors persisted workflow definitions: (#23493)import { Mastra } from '@mastra/core'; import { MastraEditor } from '@mastra/editor'; const mastra = new Mastra({ editor: new MastraEditor({ workflowBuilder: { enabled: true, model: 'openai/gpt-5.5', // optional, this is the default lastMessages: 100, // optional, raise or lower how much authoring history the agent recalls }, }), });
The server exposes two new endpoints for it:
GET /editor/workflow-builder/settingsreports availability and the admin model policy, andPOST /editor/workflow-builder/streamstreams responses from the builder agent. Access is gated by thestored-workflows:readandstored-workflows:writepermissions, and thestored:<action>permission umbrella now also matchesstored-workflows:<action>, so roles grantedstoredaccess can use the stored-workflow endpoints.Also in: @mastra/editor@0.15.0, @mastra/server@1.67.0
-
Added per-call background execution dispositions, awaited background calls, caller-scoped completion signals, and bounded delegated background tool execution. (#23026)
Background-eligible tools continue to run deferred by default. Use
defaultDisposition: 'foreground'when eligibility should only give the model the option to background individual calls:const tool = createTool({ id: 'research', background: { enabled: true, defaultDisposition: 'foreground', }, // ... });
Eligible calls can now override their execution mode with
_background.disposition:{ "topic": "distributed systems", "_background": { "disposition": "awaited" } }Add the background-work signal processor when tools need caller-scoped completion signals:
import { Agent } from '@mastra/core/agent'; import { createBackgroundWorkSignalProcessor } from '@mastra/core/processors'; const agent = new Agent({ // ... inputProcessors: [createBackgroundWorkSignalProcessor()], }); const stream = await agent.stream('Research distributed systems'); for await (const part of stream.fullStream) { if ( part.type === 'data-signal' && (part.data.tagName === 'work-completed' || part.data.tagName === 'work-failed') ) { handleBackgroundSignal(part.data); } } function handleBackgroundSignal(signal: { tagName?: string }) { if (signal.tagName === 'work-completed') { // Handle successful background work. } else if (signal.tagName === 'work-failed') { // Handle failed background work. } }
deferredreturns a task placeholder while the run continues,awaiteduses durable background execution while holding the current branch for the authoritative result, andforegroundexecutes inline. The legacy_background.enabledfield remains supported. -
Added
NotificationsStorage.updateNotificationsStatus()to set one status on many notifications of a thread in a single write. The notification inbox tool now uses it to mark a viewed page seen with one round-trip instead of one update per record. (#23718)// Before: one write per notification await Promise.all(ids.map(id => storage.updateNotification({ threadId, id, status: 'seen' }))); // After: one write for the whole page; returns the updated records const seen = await storage.updateNotificationsStatus({ threadId, ids, status: 'seen' });
Adapters that don't override the method fall back to per-record
updateNotificationcalls, so existing custom storages keep working. -
Added experimental tools for connecting Mastra Code agents and sending prioritized signals between freshly advertised peer threads. Cross-agent communication is off by default. Enable it with the "Experimental cross-agent communication" toggle in
/settings, then restart Mastra Code. Embedded clients can enable it directly: (#21986)import { createMastraCode } from 'mastracode'; const mastraCode = await createMastraCode({ crossAgentSignals: true, });
Use
agent_connections_listto discover peers,agent_connectto save an exact peer endpoint,agent_signal_sendto send a correlated message, andagent_disconnectto remove the saved connection. Sends require a currently advertised thread owner, return an error when delivery isn't acknowledged, and retain a bounded sender-side history so sequential retries can reuse the samemessageId. A busy claimed owner acknowledges a safely queued wake immediately and delivers it after the active run finishes. -
Restored reactive and system-reminder signals in live streams by default. Added caller-local
hideSignalsto modern agent streams, resume/until-idle streams, and thread subscriptions. Exclusions leave model context, persistence, transforms, and other subscribers unchanged. (#23554)// Before: reminders were hidden from every live consumer. const output = await agent.stream('Continue'); // Now: reminders are visible by default; opt out for this caller only. const filtered = await agent.stream('Continue', { hideSignals: ['reactive', 'system-reminder'], }); const subscription = await agent.subscribeToThread({ threadId: 'thread-1', resourceId: 'user-1', hideSignals: ['reactive'], });
Set
hideSignals: trueto hide all recognized signals,falseor[]to show all, or an array to hide selected types. Stream exclusions normalize legacy aliases. They filter streamed signal chunks after transforms, not aggregate output, and are not a security boundary. Shared execution options also accepthideSignalsongenerate()andresumeGenerate()without filtering their returned results. HTTP/client-js options are unchanged.Fixed directory-scoped instruction discovery so completed file operations load the nearest
AGENTS.mdinto the next model request. Repeated operations in the same directory no longer load duplicate instructions, aliased paths resolve to the same instructions, and operations with multiple path fields can discover instructions for each destination. -
Added configurable error handling to model-backed guardrail processors. Existing configurations continue to warn and fail open when an internal model call fails: (#23934)
new PromptInjectionDetector({ model: 'openrouter/openai/gpt-oss-safeguard-20b', });
Set
errorStrategy: 'strict'to stop processing with a tripwire instead of allowing unchecked content:new PromptInjectionDetector({ model: 'openrouter/openai/gpt-oss-safeguard-20b', errorStrategy: 'strict', });
-
Added
hookDurationMsto output stream processor spans. It is the time spent insideprocessOutputStream, summed across all chunks. Fixes #22343 (#23832)Read it from the span attributes in any exporter:
import type { ObservabilityExporter, ExportedSpan } from '@mastra/core/observability'; const exporter: ObservabilityExporter = { name: 'processor-cost', async exportSpan(span: ExportedSpan) { if (span.entityType === 'output_processor') { console.log(span.name, span.attributes?.hookDurationMs); } }, };
-
Added a storage contract for observability adapters to query thread identities using cross-trace predicates. (#23558)
-
Added
hideSignalsto the core memory recall contract so memory implementations and callers can explicitly control which stored signal messages are returned. Omitted exclusions preserve the existing reminder-hidden history default, and explicit visibility settings take precedence over the deprecatedincludeSystemRemindersoption. (#23554)await memory.recall({ threadId: 'thread-1', hideSignals: false }); await memory.recall({ threadId: 'thread-1', hideSignals: true }); await memory.recall({ threadId: 'thread-1', hideSignals: ['reactive', 'system-reminder'], });
Use
trueto hide all recognized signal types,falseor[]to include all, or an array to hide selected exact stored types. Legacy reminder rows without a recognized encoded signal type matchsystem-reminder. Returned-message filtering preserves pagination totals, raw storage, ordinary messages, and model context. -
Exported
validateToolInputfrom@mastra/core/tools(alongside the existingvalidateToolOutput) for validating a value against a tool's input schema: (#23493)import { validateToolInput } from '@mastra/core/tools'; const { data, error } = validateToolInput(myTool.inputSchema, input, myTool.id); if (error) { // error is a ValidationError describing the schema mismatch }
-
Added
delegation.enableResultReferencesso a later subagent delegation can reuse an earlier subagent's result verbatim, instead of the supervisor restating it. When enabled, each successful foreground subagent result ends with a[ref: <id>]line (for exampleexplorer-1), and every delegation tool gains acontextFromRefsinput that inserts the referenced results ahead of the prompt. Rejected, failed, empty, and background-task delegations don't get a reference. Off by default; when it's off the tool schemas and model output are unchanged. (#22940)const supervisor = new Agent({ id: 'supervisor', instructions: 'Delegate research to explorer, then hand the findings to implementer.', model: 'openai/gpt-5', agents: { explorer, implementer }, }); await supervisor.generate('Find and fix the token refresh bug', { delegation: { enableResultReferences: true }, }); // 1. agent-explorer → "...expiry check uses `<` instead of `<=`.\n\n[ref: explorer-1]" // 2. agent-implementer({ prompt: 'Fix the bug', contextFromRefs: ['explorer-1'] }) // receives the explorer's text verbatim before the prompt.
Referenced text is subagent output. If subagents handle untrusted input, validate or rewrite
resultTextinonDelegationComplete(or through processors) before it can be referenced. Closes #22910 -
Added an optional per-file
modetoWorkspaceSandbox.writeFilesinputs so callers can set POSIX permissions (0o001–0o777) when provisioning files. The Docker sandbox applies the requested mode to each uploaded file, falling back to0644when omitted. Sandboxes that cannot honor an explicit mode (Vercel, E2B, Daytona, Cloudflare) reject the request withSandboxUnsupportedFeatureErrorinstead of silently ignoring it. Closes #23580. (#23652)await sandbox.writeFiles([ { path: 'scripts/setup.sh', content: '#!/bin/sh\necho ready\n', mode: 0o755 }, { path: 'config/private.json', content: JSON.stringify({ token }), mode: 0o600 }, { path: 'README.md', content: 'Sandbox instructions' }, // defaults to 0644 ]);
@mastra/corenow exportsvalidateSandboxFileMode,assertModesUnsupported, andSandboxUnsupportedFeatureErrorfor sandbox providers; the built-in providers'@mastra/corepeer dependency floor is raised to1.67.0accordingly.Also in: @mastra/cloudflare-sandbox@0.4.0, @mastra/daytona@0.11.0, @mastra/docker@0.8.0, @mastra/e2b@0.12.0, @mastra/vercel@1.6.0
-
Add thread ownership transfer (resourceId reassignment). (#23533)
You can now transfer an existing thread to a different resource, reassigning both the thread and its messages to the new
resourceIdwhile preserving the thread's originalcreatedAttimestamp. This supports scenarios like moving a private thread into a shared workspace without the previous upsert workaround.@mastra/core/@mastra/memory: newMemory.updateThreadResourceId({ threadId, resourceId })method, backed by a defaultMemoryStorage.updateThreadResourceIdimplementation. When semantic recall is enabled, the message vectors are migrated to the newresourceIdso resource-scoped retrieval keeps surfacing the transferred thread.@mastra/server: newPOST /memory/threads/:threadId/transferroute. The endpoint is restricted to privileged, non-resource-scoped callers and rejects requests made with a resolved resource scope.@mastra/client-js: newMemoryThread.transfer({ resourceId })method.@mastra/pg,@mastra/libsql,@mastra/mssql,@mastra/dsql,@mastra/oracledb,@mastra/mysql,@mastra/spanner: atomic, serializedupdateThreadResourceIdoverrides. The thread and all of its messages are moved inside a single transaction, so overlapping transfers of the same thread cannot interleave and leave split ownership. Postgres, MySQL, SQL Server and Oracle take a row lock (SELECT ... FOR UPDATE/UPDLOCK, HOLDLOCK); libSQL and Spanner serialize their write transactions; Aurora DSQL relies on its optimistic concurrency control with automatic retry. Adapters without a transaction primitive fall back to the base best-effort implementation, which fails closed by reverting on error.
// Server-side, from a privileged (non-resource-scoped) context: const thread = await memory.updateThreadResourceId({ threadId: 'thread-123', resourceId: 'new-resource-456', }); // Client-side: const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); const thread = client.getMemoryThread('thread-123', 'agent-id'); await thread.transfer({ resourceId: 'new-resource-456' });
Also in: @mastra/client-js@1.46.0, @mastra/dsql@1.4.0, @mastra/libsql@1.23.0, @mastra/memory@1.30.0, @mastra/mssql@1.8.0, @mastra/mysql@0.9.0, @mastra/oracledb@0.3.0, @mastra/pg@1.25.0, @mastra/server@1.67.0, @mastra/spanner@1.7.0
-
Added an optional
abortSignalto theWorkspaceSandbox.writeFilescontract so file uploads can be cancelled. Also added aSandboxAbortError(codeABORTED) that providers throw when a write is cancelled. (#23644)const controller = new AbortController(); await sandbox.writeFiles?.(files, { abortSignal: controller.signal });
Observing the signal is provider-dependent. Providers that do not support cancellation ignore the option and run to completion.
Patch Changes
-
A workflow
.agent()step that declaresstructuredOutput.schemanow fails when the agent finishes without producing an object, instead of silently reportingsuccessand returning{ text }. The step throws aMastraError(STRUCTURED_OUTPUT_OBJECT_UNDEFINED) carrying thefinishReason, matching how the rest of the agent stack guards missing structured output. A validly-parsed falsy object (e.g.0) is still treated as produced, and steps without a declared schema are unaffected. Fixes #23403. (#23559) -
Sampling settings that a model does not accept are now left out of the request for models passed in directly and for models hosted on Amazon Bedrock. Previously these settings were only removed for models referenced by name, so directly-passed and Bedrock-hosted models could fail their requests. Fixes #23319. (#23549)
-
Update provider registry and model documentation with latest models and providers (
e86be03) -
Fixed
DurableAgentlosing tools discovered byToolSearchProcessorafter the first search. WithincludeResolvedTools: true, the first model step correctly saw onlysearch_tools, but the next step saw onlysearch_toolsagain instead of the tool the search auto-loaded, so real models looped onsearch_toolsforever. The durable loop now keeps the complete resolved toolset separate from the narrowed per-step snapshot, so tools loaded bysearch_toolsorload_toolbecome available on the following step. Fixes #22933. (#23889) -
Fixed signals that wake an idle thread so they continue through durable execution when the agent is wrapped by a durable integration such as
@mastra/inngest, instead of falling back to the wrapped agent's in-process run. Fixes #23800. (#23868) -
Fixed browser providers signalling a remote browser's PID on the host machine. When a provider connected to an existing browser over
cdpUrl(or a Firecrawl/Browserbase cloud session), it captured the remote browser's PID and, on disconnect, ranprocess.kill(-pid, 'SIGKILL')locally. That PID belongs to another host or container, so the signal hit an unrelated local process group — and when the remote Chromium was its container's PID 1,kill(-1)broadcast SIGKILL to every process the Mastra user owned. (#23599)Providers now skip PID capture whenever the browser was reached over CDP, so there is nothing to signal for browsers we do not own. As defense in depth,
killProcessGroupin@mastra/corenow refuses any PID that cannot name a killable local process group (non-integer, negative,0, or1). Locally launched browsers are unaffected and still have their process group cleaned up.Also in: @mastra/agent-browser@0.5.3, @mastra/browser-firecrawl@0.2.3, @mastra/stagehand@0.3.5
-
Channel adapters can now discard the agent's buffered reply when a run is aborted. A new
onAbort: 'flush' | 'discard'option on the adapter config controls what the static (non-streaming) driver does with not-yet-posted text on anabortchunk:'flush'(the default) posts the partial reply as before, and'discard'drops it so nothing is posted. This supports human-takeover flows on non-streaming platforms, where an in-flight agent reply should not appear as a truncated message beside the operator's own reply. (#23655)const adapter = new MyChannelAdapter({ // ...existing config onAbort: 'discard', // drop buffered text on abort instead of posting it });
Fixes #23640.
-
Fixed an issue where calling sendSignal() from a processToolResult processor hook could silently drop the just-completed tool call and result from later model inputs and saved history. The in-flight response message is now only sealed when a message id rotation follows, so the next streamed step merges into it instead of replacing it. (#21940) (#23634)
-
Added
copyThread()so a thread and its messages can be duplicated without loading every message payload into the Node heap. Fixes #23434. (#23567)memory.cloneThread()keeps its existing signature and still returnsclonedMessages, but the copy now happens inside the database first (viaINSERT … SELECTon LibSQL and Postgres) and the messages are read back afterwards only because the caller asked for them. Forked subagents and other callers that only need the new thread id use the newmemory.copyThread(), which never returns message content. When semantic recall is enabled, the copied messages are still read back to generate embeddings, but in batches of 100 instead of all at once.// Same as before: returns the copied messages. const { thread: clonedThread, clonedMessages } = await memory.cloneThread({ sourceThreadId }); // New: copy without returning message payloads. const { thread: copiedThread, messageIdMap } = await memory.copyThread({ sourceThreadId });
Storage adapters now implement
copyThread(); the basecloneThread()is derived from it. The unreleasedhydrateMessagesoption has been removed.Also in: @mastra/libsql@1.23.0, @mastra/memory@1.30.0, @mastra/pg@1.25.0
-
ProviderHistoryCompatnow handles signed thinking blocks that cross providers. The newanthropic-strip-foreign-signed-reasoningrule drops signed reasoning from the outbound prompt when the turn that produced it was stamped with a different provider (for example Kimi For Coding ↔anthropic/claude-sonnet-4-6), since the receiving provider rejects a foreign signature withInvalid `signature` in `thinking` block. To support provenance-aware rules,processLLMRequestargs now expose themessageListthe prompt was built from. Goal scorers created withcreateGoalScorernow includeProviderHistoryCompatin their input and error processor lanes by default, since goal judges talk to the same providers as the agent they judge. (#23695)import { ProviderHistoryCompat } from '@mastra/core/processors'; export const agent = new Agent({ inputProcessors: [new ProviderHistoryCompat()], errorProcessors: [new ProviderHistoryCompat()], });
-
Fixed
Mastra.shutdown()tearing down pub/sub before in-flight workflow runs could finish. Runs that were mid-step whenmastra.shutdown()was called used to hang forever because the events they needed no longer had a consumer; durable agent runs were drained too late for the drain to help. (#23168)shutdown()now waits for in-flight workflow runs (plain and durable agent) to reach a finished or suspended state before stopping workers, bounded by a newdrainTimeoutoption (default 5 seconds). Workers also wait for events they are already processing before tearing down. The timeout is one shared deadline for the whole shutdown: the workflow drain, background task cancellation, and worker teardown all draw from it, so a stuck step can never holdshutdown()open for longer thandrainTimeoutbefore workspace and storage cleanup.A durable agent whose terminal error event cannot be published (for example because the pub/sub client is already closing) now logs a warning instead of surfacing an unhandled rejection.
Note: the durable agent wait was previously unbounded. If you rely on
shutdown()waiting longer than 5 seconds for durable agent runs, pass a largerdrainTimeout.await mastra.shutdown({ drainTimeout: 30_000 });
Fixes #22863
-
Fixed sub-agent delegation so processed supervisor context remains available without exposing observational memory control messages or rerunning observational memory for inherited memory. (#21929)
-
Improved core primitive declarations with concise usage examples and directions to bundled documentation. (#23489)
-
Added persisted error parts for failed agent turns so thread history retains terminal failures. (#23867)
const memory = await agent.getMemory(); const { messages } = await memory!.recall({ threadId: 'thread-123', perPage: false }); for (const message of messages) { for (const part of message.content.parts ?? []) { if (part.type === 'error') { console.error(part.error.name, part.error.message); } } }
-
Updated Session follow-ups to share the Agent-owned thread queue and pending count across collaborators. Steering retains its abort-then-send behavior without clearing queued follow-ups. Session cleanup preserves submitted messages while cancelling unfinished local preparation. (#23235)
Breaking change: replace
subscribeQueuedMessages({ resourceId, threadId }, listener)withsubscribeThreadEvents({ resourceId, threadId }, listener). The listener now receives a typed event instead of a{ count }snapshot:const unsubscribe = agent.subscribeThreadEvents({ resourceId, threadId }, event => { if (event.type === 'queue-count-changed') { console.log(event.count); } });
This API currently reports only local pending queue counts, not composite thread state or individual message lifecycle events. Explicit cancellation remains available by signal ID or optional queue owner. Observation and cancellation apply only to local pending messages, not running or remote work.
-
Fix AgentController Session turning missing/unknown token counts into false zeros. Step-finish now skips fabricating, persisting, and emitting a usage update when a step reports no usable primary counts (prompt/completion/total all absent), instead of folding a
{0,0,0}tally into the running total.loadMetadata()no longer callsresetTokenUsage()when a persisted metadata read fails transiently, so a measured tally is preserved instead of being destroyed by a read error. Fixes #23471. (#23657) -
Fix assistant text parts being reordered when merging multiple text parts after tool results. Account for synthetic step-start markers without moving later tool results across text, preserving the order used for stored messages and model history. (#23840)
-
Fixed file attachment names being lost when stored messages are converted to AI SDK v5 UI messages. (#23523)
-
Fixed background tasks whose initial dispatch is rejected by marking still-pending tasks as failed and freeing local capacity for queued work. Tasks already claimed by a worker are preserved when a transport reports an ambiguous publication failure. (#23024)
-
Fixed workflow retry counts leaking across concurrent runs and foreach items. (#23593)
-
Fixed the notification inbox tool leaving notifications pending forever after the agent viewed them. Listing, reading, or searching now marks the returned unread notifications as seen.
listdefaults to unread notifications in pages of 20 and reportshasMoreandmarkedSeen; passstatus: 'seen'to list already-viewed notifications orlimitto change the page size. Internalmetadataandpayloadfields are no longer included in list and search results. (#23710) -
Fixed execute_command to include stderr when commands exit successfully. (#23679) (#23683)
-
Fixed durable agents continuing past a tool call that the client is meant to execute. (#23612)
A tool declared without
executeruns on the client, which answers it on a follow-up request, so the run must end at the call. Durable agents instead recorded an empty result for it and called the model again — the agent answered as though the tool had returned nothing, and the client never received the call.const agent = new Agent({ model, durable: true, // No `execute`: the client runs this tool and sends the result back. tools: { approveInvoice: createTool({ id: 'approveInvoice', inputSchema }) }, }); // The turn now ends at the call so the client can answer it. const { output } = await agent.stream('Approve invoice 42');
See #23295.
-
Fixed Unix socket pubsub clients hanging forever on startup when connecting to a broker from an older build that never acknowledges membership requests. Both
subscribeandunsubscribenow proceed best-effort after a configurable timeout when no acknowledgement arrives, so newer clients no longer deadlock against legacy brokers. (#23699)Configure the timeout via the new
membershipAckTimeoutMsoption (default 5000ms):const pubsub = new UnixSocketPubSub(socketPath, { membershipAckTimeoutMs: 5000 });
-
Reduced TypeScript memory usage for applications that define many tools with Zod schemas. (#23677)
Also in: @mastra/schema-compat@1.3.10
-
Durable agents attached to a chat channel now render their final answer after a tool call. Previously the first tool step ended channel rendering, so the tool cards posted but the answer that followed was silently dropped — the run still reported success and saved the message to the thread. Any output processor reading
stepResult.isContinuedonstep-finishchunks now receives it on durable runs too, matching regular agent runs, including when a tool error makes the loop continue past a modelstop. Fixes #23341. (#23610) -
summarizeNotifications()now counts notification sources whose names collide withObject.prototypemembers (for example__proto__,constructor,toString) as ordinary own numeric properties. The per-source and per-priority accumulators are seeded with null-prototype objects, so a__proto__source is no longer silently dropped from the summary andconstructor/toStringsources no longer produce non-numeric string counts. Fixes #23693. (#23700) -
Fixed a stuck agent run when a message is sent immediately after aborting. Calling
abort()(orsteer()) and sending another message right away now correctly starts a fresh, observable run instead of losing its start/end events and leaving the session stuck in a running state. (#23565) -
Simplified core maintenance without changing schema validation, event serialization, or callback behavior. (#23729)
-
Improved background task execution across multiple processes. Invocation-bound tasks stay on the process that owns their executor, cancellation reliably reaches local work, and queued tasks continue after dispatch failures. (#23024)
Stale task recovery
Startup recovery remains enabled by default. Disable it when multiple live managers share storage and task ownership cannot be verified across processes:
const mastra = new Mastra({ backgroundTasks: { enabled: true, recoverStaleTasksOnStart: false, }, });
-
Improved streaming performance for output processors by avoiding unnecessary workflow runs for each chunk. (#23435)
-
Only emit the "logger already wired to another Mastra instance" re-attach warning when
loggerOptions.exportis enabled. Withexport: falsethere is no observability export target to clobber, so attaching a shared logger to multipleMastrainstances no longer prints a spurious warning. (#23544) -
Scorer judge configuration now accepts an optional
modelSettingsfield (temperature, topP, topK, maxOutputTokens, maxRetries, frequencyPenalty, presencePenalty, timeout, etc.), forwarded to the internal judge agent run. It can be set at the scorer level and overridden per step, removing the need for an input-processor workaround. Closes #23458. (#23637)const scorer = createScorer({ id: 'answer-relevancy', description: 'Scores answer relevancy', judge: { model: openai('gpt-4o'), instructions: 'Return a relevancy score.', // New: configure the judge model call directly modelSettings: { temperature: 0, maxRetries: 3 }, }, }) .analyze({ description: 'analyze', outputSchema: z.object({ value: z.number() }), createPrompt: () => 'analyze this', // Optional per-step override (replaces the scorer-level value) judge: { modelSettings: { temperature: 0.7 } }, }) .generateScore(({ results }) => results.analyzeStepResult.value);
-
Improved default goal-judge retries by adding JSON instructions to the latest user message. The first attempt still selects the supported output format automatically. Validation remains strict. Other scorers keep their existing retry behavior. (#23901)
-
Fix background sub-agent tool approvals restarting the delegation instead of resuming it. When a sub-agent run as a background task suspends on a tool approval, the nested run id (carried in
suspendOptions.runId) is now bridged into the background task's persisted suspend data, soresumerestores it and the sub-agent continues its existing run and executes the approved tool — rather than starting a fresh run and re-suspending on the same approval. Fixes #23626. (#23659) -
Honor the configured logger and error strategy when structured output uses a separate model. Handled validation failures now warn through the agent logger or return the configured fallback without misleading error-level console logs. Preserve fallback metadata on structured output results. (#23849)
-
Fixed a fractional
tailvalue producing a truncation notice that disagreed with the returned output. Theexecute_commandandget_process_outputtools now reject a non-integertail, and the[showing last N of M lines]notice always reports a whole number equal to the lines actually returned. (#23570) -
Fix network completion scoring to honor its timeout as a hard deadline. Previously
runCompletionScorersawaited every scorer to settle after the deadline, so a slow scorer could return a late passing verdict withcomplete: true, a never-settling scorer could hang the call indefinitely, and the default timeout timer was never cleared. Parallel and sequential scoring now race each scorer against a single shared deadline, return promptly when it elapses, preserve the evidence of scorers that already finished, mark unfinished checks as errored failures, prevent late results from changing the returned verdict, and clear the timer on completion. Non-Error scorer rejections (null,undefined, strings, plain objects, and objects with throwingmessagegetters) are now converted into explicit scorer failures instead of crashing the error handler or losing their reason. Fixes #23449. (#23564) -
Reject non-finite, fractional, and negative numeric pagination inputs while preserving defaults, zero-sized pages, and fetch-all pagination. Throw a clear error when directly creating a subagent tool without any subagent definitions. (#23846)
-
Fixed workspace tool output truncation producing invalid Unicode when cutting through emoji. (#23431)
-
Add an opt-in
injectCatalogoption toToolSearchProcessor. When enabled, the available-tool catalog (each tool's name and a short description) is injected into the system prompt so the agent can skip thesearch_toolsturn and callload_tooldirectly, collapsing the defaultsearch -> load -> use(3 turns) intoload -> use(2 turns).search_toolsstays available as a keyword fallback, injected entries respect thefilterhook, and the option defaults tofalseso existing behavior is unchanged. Best suited to small/medium tool sets where listing the catalog inline is cheaper than a discovery round-trip. Closes #16463. (#23578) -
Fixed processor step
modelSettingstypes so they accept thetimeoutfield (stepMs,totalMs,firstChunkMs) that Mastra already applies at runtime. Input processors can now returnmodelSettings.timeoutinline without a TypeScript error, and readingtimeoutfrom step arguments type-checks correctly. (#23560) -
Added consumer groups to
UnixSocketPubSub. (#23020)Usage
// Every ungrouped subscriber receives each message. await pubsub.subscribe('events', handleAllEvents); // One subscriber in the group receives each message. await pubsub.subscribe('events', handleWorkerEvent, { group: 'workers' });
Behavior
- Delivers each message once per named group and rotates delivery among available group members.
- Restores group membership when a client reconnects.
- Retries rejected local deliveries a bounded number of times.
-
Include token
usagediagnostics in theSTRUCTURED_OUTPUT_OBJECT_UNDEFINEDerror thrown when a workflow agent step declares a structured-output schema but produces no object, so loggers and workflow error consumers retain the finish result's usage data. (#23563) -
Fixed notification delivery policy so a
sourcenamed after a JavaScriptObject.prototypemember (such asconstructor,toString, orhasOwnProperty) no longer bypasses your configuredprioritiesanddefault. Previously such a source resolved an inherited function and was delivered even whendefault: 'discard'was set. Source and priority lookups now only match keys you explicitly configured. Fixes #23694. (#23701) -
Add
WidenModelId<T>andWidenedMastraModelConfigtype helpers that replace the model-id literal union withstringfor internal plumbing. Public config fields keepMastraModelConfigfor autocomplete; internal code that merges model values (??, ternaries) should widen first so TypeScript does not subtype-reduce the full model registry union. (#23947) -
Improved workspace
grepandlist_filestool performance on remote filesystems. Thelist_filestree walk now issues directory listings concurrently instead of one at a time, andWorkspaceFilesystemproviders can implement optionalwalk()andgrep()methods to run tree walks and content searches natively in a single call. The workspace tools use these capabilities automatically when available and fall back to the existing host-side walk otherwise, so a grep over a remote sandbox filesystem no longer needs one network round trip per directory and file. When a native capability fails and the tools fall back to the host-side walk, the downgrade is logged through the workspace logger (infofor an unsupported grep pattern,warnfor any other failure) so unexpected per-file round trips are visible.Workspacenow exposes a read-onlyloggergetter. Fixes #22285 (#22317)import { UnsupportedGrepPatternError } from '@mastra/core/workspace'; import type { WorkspaceFilesystem, WalkEntry, FilesystemGrepResult } from '@mastra/core/workspace'; class RemoteFilesystem implements WorkspaceFilesystem { // ...required methods... // Optional: return every entry under `path` in one call. async walk(path: string, options?: { maxDepth?: number; includeHidden?: boolean }): Promise<WalkEntry[]> { return this.api.listTree(path, options); } // Optional: run the search remotely. `column` must be a UTF-16 index. // Throw UnsupportedGrepPatternError to let the tool fall back to the host walk. async grep(options: { pattern: string; path: string; caseSensitive: boolean }): Promise<FilesystemGrepResult[]> { if (!this.api.supportsRegex(options.pattern)) throw new UnsupportedGrepPatternError(options.pattern); return this.api.search(options); } }
-
Workflow control-flow spans now carry the identity of the graph entry that created them, so observability tools can correlate a span with the authored operation instead of inferring it from structure. (#23542)
.parallel(),.branch(),.dowhile(),.dountil(), and.foreach()container spans use the authored entryidfor their display name (e.g.parallel: 'check-document') and expose the entryid,description, andmetadataas span attributes (entryId,entryDescription,entryMetadata). Spans without an authoredidkeep their previous structural name as a fallback..sleep()and.sleepUntil()spans keep their descriptive duration/date names and now expose the same identity attributes..map()step spans now forward the entrydescriptionandmetadata(they already exposed the entryid).
For example, given:
workflow.parallel([checkSpelling, checkGrammar], { id: 'check-document', metadata: { title: 'Check document' }, });
the container span is now named
parallel: 'check-document'and its attributes includeentryId: 'check-document'andentryMetadata: { title: 'Check document' }, letting an exporter display "Check document" and distinguish it from other parallel groups with the same branch count. -
Fix workspace
read_filethrowingTypeError: mimeType.startsWith is not a functionfor files whose extension matches an inheritedObject.prototypemember (e.g.file.constructor,file.__proto__).getMimeTypenow only resolves own string entries of its MIME table, so these filenames fall back toapplication/octet-streamand read as text like any other unknown extension. Fixes #23957. (#23959) -
Fixed live agent scores being dropped when tracing is enabled. Durable agents now save scorer results, and scores link to the exported agent span instead of a hidden workflow step span. (#23465) (#23600)
@mastra/ai-sdk@1.10.3
Patch Changes
-
Fixed a nested agent's tool failure being invisible to AI SDK hosts.
transformAgenthad no case fortool-error, so a sub-agent whose tool threw emitted the samedata-tool-agentparts as one whose tool succeeded and a host could not tell a failed delegated step apart from a successful one. Failures now appear indata.toolErrorsas{ toolCallId, toolName, args?, errorText, providerExecuted? }, whereerrorTextis a JSON-safe string (a rawErrorserializes to{}over the wire), and they are also carried on the finisheddata-tool-agent-steppayload. Runs where no tool throws are unchanged. Fixes #23022. (#23616) -
Remove OpenAI text item references from UI streams when reasoning is hidden, so persisted UI messages can be replayed without referencing a message whose required reasoning item was omitted. Preserve provider linkage when
sendReasoning: trueand leave unrelated provider metadata unchanged. (#23323)
@mastra/archil@0.3.0
Minor Changes
-
Renamed
ArchilFilesystem.grep()todiskGrep(). The method is an Archil-specific search API whose options and result shape differ from the new optionalWorkspaceFilesystem.grep()capability contract in@mastra/core; keeping the same name would have caused the core workspacegreptool to call it with mismatched arguments. (#22317)// Before const results = await filesystem.grep({ directory: '/src', pattern: 'TODO', recursive: true }); // After const results = await filesystem.diskGrep({ directory: '/src', pattern: 'TODO', recursive: true });
@mastra/auth-studio@1.3.6
Patch Changes
- Fixed deployments pinned to an organization (
MASTRA_ORGANIZATION_ID) serving members under whatever organization their shared Mastra session cookie happened to be on. A member is now served in the pinned organization with their role in that organization, and bearer tokens are verified against it. Signing in to one deployment no longer changes which organization another deployment treats you as. (#23427)
@mastra/blaxel@0.9.1
Patch Changes
- Bump @blaxel/core to ^0.3.20 for a High severity dependency fix (SEC-170/171). (#23665)
@mastra/clickhouse@1.19.0
Minor Changes
- Added ClickHouse support for querying thread identities with cross-trace predicates. The store now advertises the
thread-querycapability. (#23686)
@mastra/client-js@1.46.0
Minor Changes
-
Removed thread-group responses from
queryTraces(). The client now accepts trace-only queries and returns trace records. (#23923)Before
const result = await client.queryTraces({ timeRange, group: { by: ['threadId'] } });
After
const result = await client.queryTraces({ timeRange });
-
Added client SDK support for streaming agents from custom endpoints with client-side tools: (#23493)
clientToolsResolveron generate and stream params resolves client tools at call time instead of requiring them up front. It works on both streaming paths: the legacy stream route and thread signals, where tools are re-resolved before each execution and continuation round.- Streamed partial tool calls now merge their accumulated arguments before
onToolCallfires, so handlers always see complete arguments. client.getAgent(agentId, version, { stream: "/custom/stream" })overrides the agent stream route, anduseChatin@mastra/reactaccepts a matchingstreamPathoption.- New
client.getWorkflowBuilderSettings()reports whether the Studio workflow builder is available.
// Stream an agent from a custom endpoint, resolving client tools at call time const agent = client.getAgent('my-agent', undefined, { stream: '/custom/stream' }); await agent.stream('Hello', { clientToolsResolver: () => getMyCurrentTools(), });
// Same thing from React const chat = useChat({ agentId: 'my-agent', streamPath: '/custom/stream' }); await chat.sendMessage({ mode: 'stream', message: 'Hello', clientToolsResolver: () => getMyCurrentTools(), });
Also in: @mastra/react@1.5.0
Patch Changes
-
AgentControllerSession.subscribe()now honorsunsubscribe()at every await and dispatch boundary, so a detached subscriber no longer receives stale session state or leaks a response body. Four cancellation races are fixed: anonEventdispatched from areader.read()that resolved just before unsubscribe; further buffered frames dispatched after anonEventhandler unsubscribes mid-chunk; the freshly fetched reconnect response body left open whenonReconnectunsubscribes; and anonErrorfired when a reconnect request rejects after unsubscribe. Fixes #23454. (#23574) -
Scope dataset and experiment listings to a target entity server-side. (#23869)
@mastra/server:GET /datasetsacceptstargetTypeandtargetIdsquery params;GET /experimentsandGET /datasets/:datasetId/experimentsaccepttargetTypeandtargetId. The filters are forwarded to storage, which already supported them.@mastra/client-js:listDatasets(),listExperiments()andlistDatasetExperiments()accept the same target filters.- Studio: the Datasets, Experiments and Review queue pages read
?targetType=/?targetId=from the URL and expose a Target filter in their toolbars, so the global pages can show the same scoped view as an agent's Evaluate / Review tabs.
Also in: @mastra/server@1.67.0
-
Improved MastraClient declarations with a server connection example and directions to bundled client documentation. (#23489)
@mastra/cloudflare-sandbox@0.4.0
Patch Changes
-
Expose the remaining Cloudflare Sandbox Bridge routes through the adapter instead of requiring hand-rolled
fetchcalls.CloudflareSandboxBridgeClientgains typedreadFile,persistWorkspace,hydrateWorkspace,mountBucket,unmountBucket,createSession, anddeleteSessionmethods that reuse the existing auth, path-encoding, andCloudflareSandboxBridgeErrorhandling.CloudflareSandboxadditionally surfacesreadFile,persistWorkspace, andhydrateWorkspace, letting a multi-turn agent read generated files back and back up or restore/workspaceacross container sleep. Resolves the persistence need in #23706. Fixes #23861. (#23944) -
Run bare Workspace command strings through
/bin/bash -cso the built-inexecute_commandtool works on CloudflareSandbox. Previously a command string supplied without separate arguments was sent to the bridge as a single executable name and failed with exit 127 (command not found). Explicit argument arrays remain literal. (#23546)
@mastra/code-sdk@1.7.2
Patch Changes
-
Fix Bedrock prompt caching to enable by default for all cache-capable Claude models. (#23556)
The gate now enumerates the closed set of legacy Claude 3.x models that support caching and defaults every newer Anthropic family on, instead of matching an allow-list of current model IDs. The old allow-list was permanently behind — newly released cache-capable models were silently billed at full input rate (~10x the cached cost) until their IDs were manually appended.
- Claude 4+ and all named families (opus-5, sonnet-5, fable, mythos, and future families) now cache without a code change.
claude-3-5-sonnet-20240620and non-Anthropic models remain correctly excluded.
Fixes #23552.
-
Forwarded
updateNotificationsStatusthrough the lazy notifications storage so the inbox tool's bulk seen-marking reaches the configured store. (#23718) -
Clarified cross-agent peer states, required fresh discovery before sends, bounded expected-reply reminders when a peer becomes unavailable, and added an explicit disconnect tool. Agents can remove a saved connection from the current sender thread with the peer's stable ID: (#21986)
agent_disconnect({ targetId: "code-agent:resource-id:thread-id" }) -
The sandbox-backed workspace filesystem now runs
list_filestree walks andgrepsearches inside the sandbox in a single command (usingfindandrg/grep), instead of one round trip per directory and file.walk()throwsDirectoryNotFoundError/NotDirectoryErrorfor a missing or non-directory root and reports symlink targets. Ripgrep results are kept whenrgexits with a per-file error (for example an unreadable file) instead of being discarded, and patterns whose meaning differs between POSIX ERE and JavaScript regex ([[:digit:]],\<,\>) fall back to the host-side search so match columns stay correct. (#22317) -
Fixed trusted instruction loading when the project path uses a filesystem alias, including macOS
/varand/private/var. Review sessions keep reading AGENTS.md and CLAUDE.md from the trusted git ref instead of accidentally falling back to checkout content. Untrusted sessions without a trusted base ref still disable checkout instructions; normal trusted sessions continue reading project instructions. (#23554) -
Kimi For Coding models now report
kimi-for-codingas their provider (instead ofanthropic.messages) so message history compatibility can tell Kimi turns apart from Anthropic turns when a thread switches between them. No action is required — the value only feeds Mastra's internal provider-stamping and compatibility logic. Turns persisted before this change keep the oldanthropic.messagesstamp and stay indistinguishable from Anthropic turns; they are left as-is. (#23695) -
/prune vacuumno longer compacts local libSQL databases that carry alibsql_vector_idxvector index. Any VACUUM over such a database deterministically corrupts itslibsql_vector_meta_shadowtable — silently at first, since vector queries keep returning rows — and compounds over repeated runs untilREINDEXcan no longer repair it. Databases are now detected by schema (not filename), skipped rather than compacted, and reported in/pruneoutput with the reason, so the skip is never silent. Detection fails closed: a database whose schema cannot be inspected is left untouched instead of vacuumed. Ordinary databases are still compacted as before. (#23645) -
Bump smol-toml to 1.8.0 for a High severity dependency fix (SEC-170/171). (#23665)
@mastra/connect@0.1.0
Minor Changes
-
Added
@mastra/connect, a new package that turns Mastra platform integration connections into agent tools without exposing provider credentials to your app. Tools execute through the platform connection proxy, which injects credentials and refreshes tokens. (#23087)Use every shipped provider connected to a project
import { Agent } from '@mastra/core/agent'; import { connect } from '@mastra/connect'; const agent = new Agent({ name: 'ops', instructions: 'You manage our workspaces.', model: 'openai/gpt-5-mini', tools: connect(), });
connect()returns a live resolver compatible with an agent's dynamictoolsargument. This release establishes the connection runtime and provider-generation pipeline; generated provider modules are shipped separately, and the resolver returns an empty tool record until the installed package includes one. Once providers are present, it discovers matching project connections, merges their tools into one flat record, and refreshes its cached snapshot without requiring a server restart. Its resolver type remains compatible across linked or locally built packages that resolve a different@mastra/coreinstallation.Use the
integrationsoption to allowlist providers, restrict tool names, or select a connection when a project has more than one. Per-provider resolution failures, including ambiguous connections and connections that need reauthentication, are warned and skipped so one provider doesn't disable the other tools. Callinvalidate()orrefresh()to control the resolver cache manually.Fetch a raw credential for custom interactions
import { credential } from '@mastra/connect'; const credentialValue = await credential('c_yourconnectionid');
Use
credential()when a provider SDK requires direct credentials instead of the platform proxy.
@mastra/daytona@0.11.0
Patch Changes
-
Fixed Daytona S3 mounts to forward temporary session credentials, protect credential uploads, and remove temporary staging files after launch. Added a bounded mount-access check and recovery so failed mounts can be retried. Long-lived credential files are cleaned up on unmount once the mount process has exited, including after reconnecting to a sandbox. Credentials are not automatically refreshed. (#23519)
Previously,
sessionTokenwas used for host-side S3 requests but dropped by Daytona mounts. Now pass all three temporary credential values toS3Filesystemto authenticate the mounted filesystem too:import { Workspace } from '@mastra/core/workspace'; import { DaytonaSandbox } from '@mastra/daytona'; import { S3Filesystem } from '@mastra/s3'; const workspace = new Workspace({ mounts: { '/s3-data': new S3Filesystem({ bucket: process.env.S3_BUCKET!, region: process.env.S3_REGION ?? 'us-east-1', endpoint: process.env.S3_ENDPOINT, accessKeyId: process.env.SCOPED_S3_ACCESS_KEY_ID!, secretAccessKey: process.env.SCOPED_S3_SECRET_ACCESS_KEY!, sessionToken: process.env.SCOPED_S3_SESSION_TOKEN!, }), }, sandbox: new DaytonaSandbox({ language: 'python', ephemeral: true }), });
Obtain credentials from your storage provider with only the permissions the sandbox needs.
@mastra/deployer@1.67.0
Patch Changes
-
Fixed repeated builds producing different tool bundle filenames when the source files have not changed. (#23647)
-
The generated server now passes
server.drainTimeouttomastra.shutdown()so in-flight workflow runs get the same window to finish as HTTP requests, and the core shutdown deadline is extended by that window. Related to #22863 (#23168) -
Fixed Studio not reloading after dev-server restarts, including restarts before the first refresh connection succeeds. Production reconnects do not trigger instance-based page reloads. (#23936)
-
Add
MASTRA_BUILD_SKIP_INSTALLto skip dependency installation duringmastra build. When set totrueor1, the deployer no longer runs the dependency install or generates apackage-lock.jsonin the build output directory. This unblocks hermetic build systems (such as Bazel) that supplynode_modulesexternally and run in a network-less sandbox, where the output install was previously both redundant and fatal. Default behavior is unchanged. (#23530)MASTRA_BUILD_SKIP_INSTALL=1 mastra build
@mastra/docker@0.8.0
Minor Changes
-
Added a
mountsoption toDockerSandboxfor mount configurations that thevolumesoption cannot express. Each entry maps directly to Docker's native mount API, so you can now mount a subdirectory of a named volume, set per-mount read-only, labels, bind propagation, and tmpfs sizing. (#23953)The most common use case is mounting a read-only parent alongside a writable subdirectory of the same named volume — for example, giving each conversation its own writable folder inside a shared persistent volume.
volumesandmountscan be used together.import { Workspace } from '@mastra/core/workspace'; import { DockerSandbox } from '@mastra/docker'; const workspace = new Workspace({ sandbox: new DockerSandbox({ image: 'node:22-slim', mounts: [ { type: 'volume', source: 'project-data', target: '/shared', readOnly: true }, { type: 'volume', source: 'project-data', target: '/work', volumeOptions: { subpath: 'conversations/abc123' }, }, ], }), });
Subpath mounting requires Docker Engine 26.0 or newer. Docker does not create the subpath directory — it must already exist inside the named volume before the container starts, so provision it ahead of time.
-
Implement the optional
writeFiles()bulk upload API onDockerSandbox. (#23571)You can now upload multiple files to a running Docker sandbox in a single call instead of relying on host bind mounts or shell writes:
await sandbox.writeFiles([ { path: 'src/index.js', content: 'console.log("hello")' }, { path: 'data.json', content: Buffer.from('{"count":1}') }, ]);
Files are uploaded in one operation using Docker's native archive transfer (
container.putArchive). Relative paths resolve against the sandboxworkingDirectory, absolute paths are honored as-is, missing parent directories are created automatically, existing files are overwritten, and bothstringandBuffercontents are preserved. New files are created with mode0644. The upload is not atomic across files: if it fails, the promise rejects and partially written files may remain. CallingwriteFiles()before the sandbox has started throwsSandboxNotReadyError. -
Added cancellation support to
DockerSandbox.writeFiles. Pass anAbortSignalto stop uploading files to a container mid-transfer. (#23644)Cancelling a file upload
import { SandboxAbortError } from '@mastra/core/workspace'; const controller = new AbortController(); // Cancel from elsewhere (e.g. a timeout or user action) setTimeout(() => controller.abort(), 1000); try { await sandbox.writeFiles(files, { abortSignal: controller.signal }); } catch (error) { if (error instanceof SandboxAbortError) { // Upload was cancelled } }
If the signal is already aborted, the upload rejects before any work begins. If it aborts during transfer, the in-flight upload to the Docker daemon is terminated. Cancellation rejects with a
SandboxAbortError(codeABORTED). Cancellation does not roll back files that were already written, so dispose of or clean up the sandbox if you need a clean state.
Patch Changes
-
Fixed
DockerProcessHandle.kill()reporting exit code 137 while the process kept running inside the container, and stopped killed/timed-out processes from accumulating againstpidsLimit. (#23951)Namespace-correct kill
kill()previously used the host PID fromexec.inspect(), which does not match the PIDs an in-containerkillcan address, so the signal missed the target and the process stayed alive. Each spawned command now runs in its own session/process group (setsid -w) and records its PGID to a private file;kill()thenSIGSTOPs andSIGKILLs the whole kernel-owned process group in the container's own PID namespace. Because the group identity is enforced by the kernel, descendants are still terminated even if they drop their environment or re-parent to PID 1, and the identity cannot be forged by another container process. Images withoutsetsid -w(e.g. BusyBox) fall back to signalling the recorded leader PID directly.Zombie reaping via an init process
The default container command (
sleep infinity) as PID 1 never reaps children, so terminated processes lingered as zombies and consumed PIDs.DockerSandboxnow runs a Docker init process as PID 1 by default (HostConfig.Init), which reaps children. Disable it with the newinitoption:const sandbox = new DockerSandbox({ init: false });
Fixes #23773.
@mastra/duckdb@1.9.0
Minor Changes
- Added DuckDB support for querying thread identities with cross-trace predicates. The store now advertises the
thread-querycapability. (#23686)
Patch Changes
- Fixed DuckDB feedback storage to preserve string and numeric value types. (#23948)
@mastra/e2b-desktop@0.1.2
Patch Changes
- Raised the
@mastra/corepeer dependency floor to1.67.0to match@mastra/e2b, which it depends on. (#23652)
@mastra/editor@0.15.0
Patch Changes
-
Processor graph hydration now validates each stored step configuration against the selected provider's
configSchemabefore instantiating the processor, matching the documentedProcessorProvidercontract. (#23569)Previously
resolveStep()passed the raw stored config straight tocreateProcessor(). Malformed stored/API config could construct a broken processor that failed later during request execution, and schema.default()/transform outputs were skipped.Now invalid configuration throws at hydration time with an error identifying the provider and graph step, and the validated config (including defaults and transforms) is what reaches
createProcessor(). -
Fix editor namespaces returning a cached default entity for
getById(id, { versionNumber: 0 }). Version-request detection used a truthiness check, soversionNumber: 0was treated as a default request and served from the cache on a warm cache while returningnullon a cold cache. Detection now uses explicit!== undefinedchecks in the sharedCrudEditorNamespaceand the agent adapter, so aversionNumber(including0) consistently bypasses the cache and reaches version resolution. Fixes #23396. (#23562) -
Preserve code-defined scorers when stored scorer definitions are updated, deleted, or evicted from the Editor cache. Only remove stored-owned runtime registrations at the exact definition key. (#23837)
-
Prompt block templates no longer resolve inherited
Object.prototypemembers. PreviouslyrenderTemplateread placeholders like{{constructor}},{{toString}}and{{valueOf}}as context data, injecting native-code text (e.g.function Object() { [native code] }) into stored-agent instructions and skipping any declared fallback. Path resolution now only follows own properties, so an inherited member is treated as unresolved — left in place when there is no fallback, and replaced by the fallback when one is provided — while a context key that deliberately shadows a built-in name (e.g.{ toString: 'shadowed' }) still resolves. Fixes #23447. (#23568) -
Preserve static per-tool workspace settings when hydrating stored workspaces and snapshotting runtime workspaces. Per-tool enablement, approval requirements, and read-before-write controls now round-trip between storage and runtime configuration shapes without being silently dropped. Dynamic settings remain unpersisted. (#23838)
@mastra/elasticsearch@1.4.2
Patch Changes
-
cloneThread()now returnsmessageIdMap(source message id → copied message id), matching the other storage adapters.Memory.copyThread()uses it to embed copied messages by id instead of paging the destination thread. (#23674)Also in: @mastra/redis@1.4.5, @mastra/valkey@0.2.3
@mastra/elysia@0.1.7
Patch Changes
-
Fixed request-body validation so missing required bodies and invalid falsy JSON values return validation errors. Bodyless object requests still support optional fields and field defaults. Whole-body defaults apply when the framework passes the omitted body as
undefined. (#23893)Also in: @mastra/express@1.5.11, @mastra/fastify@1.5.11, @mastra/hono@1.7.9, @mastra/koa@1.7.11
@mastra/evals@1.10.2
Patch Changes
-
Fixed trajectory scorers ignoring explicit empty expectations and allowing deeply nested forbidden tool calls to receive passing scores. (#23505)
-
Fixed tool-use checks incorrectly passing when a tool threw an error. (#23596)
A natively thrown tool call is stored as
state: 'output-error', which the Quick Checks did not recognize, and a call present only incontent.partswas hidden whenever the message also carried a legacytoolInvocationsarray. As a resultchecks.noToolErrors()scored a perfect 1 for a failed tool,calledToolundercounted, anddidNotCall,usedNoTools,maxToolCalls, andtoolOrdercould pass on runs where the tool did run and throw.Tool calls are now merged from both message forms and thrown calls count as real, failed invocations — matching how
@mastra/corealready extracts trajectories. Fixes #23460.
@mastra/factory@0.15.0
Minor Changes
-
Added an idempotent HTTP endpoint that lets trusted external orchestrators queue a deferred skill dispatch for a work item without risking duplicate work on retries. (#23576)
A trusted external event controller can now enqueue exactly one deferred skill dispatch against a work item. The
requestIdmakes the call idempotent: replaying the same request returns the prior result instead of queuing duplicate work. Reusing arequestIdfor a different operation (work item, role, skill, or arguments) is rejected with a409 request_id_conflictinstead of falsely reporting success. Every queue and reject is recorded to the audit trail in both tenant and local no-auth deployments, with one event perrequestId.await fetch(`/web/factory/projects/${projectId}/work-items/${workItemId}/automation-runs`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ requestId: crypto.randomUUID(), expectedRevision: 1, role: 'work', skillName: 'factory-plan', }), });
Patch Changes
-
Reconcile Factory PR status labels with the automated review verdict. (#23854)
-
Fixed Factory web sessions using personal observational-memory defaults instead of the Factory project's configured models. (#23529)
-
Fixed
git pushover HTTPS failing in Factory issue, Linear, and manual sandbox sessions. The Git credential helper is now installed for every session type, not only pull request sessions. (#23540) -
Fixed a Factory reconnect bug where reconnecting a sandbox after a role or token change could reauthorize a stale GitHub token refresh context, causing the current context to fail with "GitHub token refresh no longer matches the active Factory workspace role". Reconnects now preserve the existing token authority and only re-target token injection to the current sandbox (#23543). (#23548)
-
Kept Factory chat status indicators, goal progress, and status commands consistent, and cleared the previous run's status when starting a new thread. (#23605)
-
Fixed reused managed Factory sessions keeping stale observational-memory models: when automation reuses an existing managed session, the project's current observer and reflector model settings are now reapplied before the run, instead of silently keeping the models the session was originally created with. Also, when a run is aborted by a permanent observational-memory failure (for example a provider rejecting an unsupported model), the real error is now surfaced and the decision is marked as a non-retryable configuration failure instead of being retried behind a generic "aborted" message. (#23573)
@mastra/fastify@1.5.11
Patch Changes
-
Bump Fastify to ^5.12.4 so transitive fast-uri picks up the High severity fix (SEC-170/171). (#23665)
Also in: @mastra/inngest@1.8.12
@mastra/github-signals@0.4.1
Patch Changes
-
Reduced the size of GitHub pull request notification records and added a
failingCheckUrlsattribute linking directly to failing CI checks. (#23710)// A pull-request-ci-failure notification now exposes check names and run links as attributes const { attributes } = notification; attributes.failingChecks; // 'Quality assurance, Lint' attributes.failingCheckUrls; // 'Quality assurance: https://github.com/…/runs/1; Lint: https://github.com/…/runs/2'
@mastra/inngest@1.8.12
Patch Changes
-
Fixed resuming a step that suspended inside a nested workflow on the Inngest engine. Resuming by the nested workflow's step id or by a resume label no longer fails with "No suspended steps found in nested workflow" (#23182). (#23624)
-
Forward chunks written inside nested Inngest workflows to the outermost parent run's stream, while preserving child-local streaming and existing lifecycle events. (#23621)
-
Fixed
createInngestAgent()waking idle threads through the wrapped agent's in-processstream()when a signal arrived viasendSignal(),sendStateSignal(), orsendNotificationSignal(). Signal-started runs now take the Inngest durable path, and durable runs are registered with the thread runtime sosubscribeToThread()andgetActiveThreadRunId()can see them. Fixes #23800. (#23868)
@mastra/langfuse@1.5.7
Patch Changes
- Fixed Observational Memory session grouping in Langfuse, including observer and reflector spans received before their parent spans. (#23459)
@mastra/libsql@1.23.0
Patch Changes
-
Implemented
updateNotificationsStatusas a singleUPDATE … RETURNINGso marking many notifications seen no longer issues one write per record. (#23718)Also in: @mastra/pg@1.25.0
-
Fixed dataset item writes so purged payloads cannot be restored during concurrent updates or deletes. (#23467)
Also in: @mastra/mongodb@1.18.8, @mastra/mysql@0.9.0, @mastra/pg@1.25.0, @mastra/spanner@1.7.0
-
Fixed background task result serialization so primitive values round-trip correctly through LibSQL storage. (#23024)
-
Create read indexes for observability trace queries so listing traces and loading a single trace no longer scan the full
mastra_ai_spanstable or build a temporary B-tree for ordering. Addsmastra_ai_spans_roots_started_at_idx(partial index onstartedAtfor root spans) andmastra_ai_spans_trace_started_at_idxon(traceId, startedAt), created idempotently on init for fresh, repeated, and existing databases. (#23638)
@mastra/livekit@0.4.0
Minor Changes
- Added a per-call
configuration.turnDetectionresolver tocreateLiveKitWorker(). LiveKit'sTurnDetectorclasses need the job's inference executor, so they could not be constructed at module scope where worker options live; the resolver runs inside each job with the call context and falls back to the top-levelturnDetectionoption. Fixes #22495. (#22842)
@mastra/loggers@1.3.2
Patch Changes
- Improved PinoLogger declarations with a Mastra attachment example and directions to bundled logging documentation. (#23489)
@mastra/mcp@1.18.0
Minor Changes
-
Added
MastraApiMCPServerto expose supported Mastra server operations from themastra apiCLI as MCP tools. The server reads the target API's input schemas and forwards authentication. All non-GET operations, including agent, workflow, experiment, and tool execution, are marked as potentially destructive so MCP clients can ask for confirmation. Factory commands aren't included. (#23077)import { MastraApiMCPServer } from '@mastra/mcp'; const operations = await MastraApiMCPServer.create({ url: 'https://my-mastra-server.example.com', headers: { Authorization: `Bearer ${process.env.MASTRA_API_TOKEN}` }, });
Patch Changes
- Improved MCP client and server declarations with concise setup examples and directions to bundled documentation. (#23489)
@mastra/memory@1.30.0
Minor Changes
-
Added
hideSignalstomemory.recall()so callers can choose which stored signal types to omit without altering saved messages or model context. DeprecatedincludeSystemReminders; omitted exclusions preserve existing history defaults. (#23554)// Before: include all reminders through the legacy flag. await memory.recall({ threadId: 'thread-1', includeSystemReminders: true }); // Now: any explicit visibility setting takes precedence over that flag. await memory.recall({ threadId: 'thread-1', hideSignals: false }); await memory.recall({ threadId: 'thread-1', hideSignals: true }); await memory.recall({ threadId: 'thread-1', hideSignals: ['reactive', 'system-reminder'], });
Use
trueto hide all recognized signals,falseor[]to include all, or an array to select types. Unlike modern streams, recall matches stored types exactly. Legacy reminder rows without recognized signal types matchsystem-reminder. Filtering preserves pagination totals and never changes ordinary messages. HTTP/client-js options are unchanged.
Patch Changes
-
Improved Memory class documentation with a usage example and directions to bundled package docs. (#23489)
-
Improved diagnostics for Observational Memory model and provider failures. (#23528)
-
Fixed observational memory overwriting incoming client tool results with older message history. Idle observation now buffers completed messages when the newest unobserved message holds an incomplete client or provider tool call, as long as the boundary is safe; an abandoned call buried under newer messages no longer stalls buffering. If the boundary is unsafe, the buffer attempt is deferred instead. Raw messages are still saved, and completed tool results can be buffered on a later turn. Mid-loop step-time buffering (
bufferTokens) now admits the same safe completed prefix instead of deferring the whole batch while a call is pending. (#23716) -
Fixed observational memory errors hiding provider diagnostics returned in a string detail field, including Codex rejection messages. (#23534)
-
- Preserved caller thread identity in Observational Memory traces without assigning a session ID for other observability integrations. (#23459)
- Preserved the supplied observability context when explicitly triggering asynchronous observation buffering.
-
Fix
TS2590: Expression produces a union type that is too complex to representin observational memory type checks after the model registry grew. Model config fields are now read into a widened type before being combined, so type checking no longer scales with the number of registered model ids. (#23947)
@mastra/mongodb@1.18.8
Patch Changes
- Implemented
updateNotificationsStatuswith a singleupdateManyso marking many notifications seen no longer issues one write per record. (#23718)
@mastra/observability@1.17.8
Patch Changes
-
Improved Observability declarations with a Mastra attachment example and directions to related documentation bundled in core. (#23489)
-
Fixed built-in storage exporters continuing to write in Mastra Platform deployments. Other exporters remain enabled. (#23555)
@mastra/otel-exporter@1.3.16
Patch Changes
-
Fix workflow traces losing detail when exported to Langfuse and other OpenTelemetry backends. (#23651)
Previously, every step and branch in a workflow trace showed up under the parent workflow's name, so sibling steps and conditions were indistinguishable. Branch, loop, sleep, and wait-event details were also dropped.
Now each step keeps its own name, control-flow spans keep their descriptive names, and their metadata is included in the export. Fixes #23579.
@mastra/pg@1.25.0
Minor Changes
- Added PostgreSQL support for querying thread identities with cross-trace predicates. The store now advertises the
thread-querycapability. (#23686)
Patch Changes
- Fix PostgresStoreVNext observability trace reader dropping scalar-string span inputs and outputs. The pg driver already decodes JSONB columns into native JS values, but the reader re-parsed decoded strings, so a stored string like
hellothrew duringJSON.parseand was returned asundefined, while JSON-looking strings such as123ortruewere coerced to a number or boolean. Decoded JSONB values are now returned unchanged, preserving both value and type. Fixes #23575. (#23577)
@mastra/playground-ui@55.0.0
Minor Changes
-
Added composable PageHeader slots, including metadata that can sit below or beside the title. (#23819)
<PageHeader> <PageHeader.Title>production</PageHeader.Title> <PageHeader.Meta beside>Live</PageHeader.Meta> <PageHeader.Action>Edit</PageHeader.Action> </PageHeader>
-
Added title and caption variants to the Txt component. Each variant carries its full text treatment, so call sites no longer override color or weight with class names. (#23880)
<Txt as="h2" variant="title">Section title</Txt> <Txt variant="caption">Supporting caption text</Txt>
Settings group titles and descriptions now use these variants, which also fixes their text color outside Factory where the previous icon color utilities did not resolve.
-
Added a composable
ThreadTracecomponent for rendering a memory thread as its traces (one row per agent turn, with the messages beside the span tree and a side panel for the selected span). Every part (ThreadTrace.List,.Rail,.Row,.Messages,.Details,.DetailsHeader,.TabList,.Tab,.TabContent,.SpansTab,.SpanPanel, …) acceptsclassNameand extra props, anduseThreadTrace/useThreadTraceRowexpose the selection, highlight and expansion state so custom tabs and slots can be plugged in from the call site. Also moved theuseExpandedSpanIdsanduseVisibleTraceRowshooks into the package, andTabsnow forwards extra props (such asdata-testid) to its root element. (#23836) -
Added an
onSelectRowprop toDataList.RowWrapperso a whole row (including trailing cells) can be focused, clicked, or activated with Enter.DataList.RowLinknow forwards its ref. (#23762) -
Added
KeyboardShortcutsProviderandKeyboardScopeso shortcuts declared inside a scope override the same shortcuts declared higher in the tree, and are removed as soon as the scoped component unmounts. The provider owns a singlewindowlistener and a single sequence state, so a sequence likegthentcan resolve to a global handler in one place and to a page-specific handler in another without both firing. (#23831)Before: two
useKeydowncalls binding the same keys both fired.After:
// App root <KeyboardShortcutsProvider> <GlobalShortcuts /> {/* useKeydown({ 'g$+t': () => navigate('/traces') }) */} <Routes /> </KeyboardShortcutsProvider> // Agent page: wins over the global binding while mounted <KeyboardScope> <AgentShortcuts /> {/* useKeydown({ 'g$+t': () => navigate(`/agents/${id}/traces`) }) */} </KeyboardScope>
useKeydownkeeps its signature. Without a provider, or when atargetref is passed, it behaves as before (own listener, no override). -
Added reusable slash-command suggestions and keyboard navigation for chat composers. (#23766)
Use
ComposerSuggestionsanduseComposerCommandsfrom@mastra/playground-ui/components/Composer. Supply the available commands, controlled draft, input ref, and submission callback:const commands = useComposerCommands({ commands: availableCommands, value: draft, onValueChange: setDraft, onSubmit: submitCommand, inputRef, }); <ComposerBox> <ComposerSuggestions {...commands.suggestionsProps} /> <ComposerInput {...commands.inputProps} ref={inputRef} aria-label="Message" /> </ComposerBox>;
Compose the input key handler with normal message submission: call
commands.inputProps.onKeyDown(event)first, then submit only ifevent.defaultPreventedis false. The application continues to own command execution and permissions. -
Added
useLocalStorageStatefor schema-validated browser-local state. The hook restores validated values, persists React-style state updates, and keeps in-memory state usable when storage is unavailable. (#23676)import { useLocalStorageState } from '@mastra/playground-ui/hooks/use-local-storage-state'; import { z } from 'zod/v4'; const countSchema = z.number(); function Counter() { const [count, setCount] = useLocalStorageState({ initialKey: 'counter', defaultValue: 0, schema: countSchema, }); return <button onClick={() => setCount(previous => previous + 1)}>{count}</button>; }
initialKeyanddefaultValueinitialize state once per mount. Remount the consumer with a Reactkeywhen switching storage entries. An optionalserializefunction supports custom storage representations; it defaults toJSON.stringify. -
Added reusable workflow cards, graph presentation, data inspectors, and debug controls with Storybook examples. (#23769)
import { WorkflowStepCardView } from '@mastra/playground-ui/components/Workflow'; <WorkflowStepCardView label="Enrich customers" displayStatus="running" />;
-
Added a reusable AppShell for composing mobile headers, route headers, framed pages, and independently scrolling page content. PageHeader icons now sit inside the header grid, with aligned text and readable description contrast. (#23909)
<AppShell mainLabel="Agents" mobileHeader={<MobileHeader />} routeHeader={<RouteHeader />}> <Page /> </AppShell>
-
Added the named settings components at
@mastra/playground-ui/new/settings:SettingsGroup,SettingsHeader,SettingsTitle,SettingsDescription,SettingsContainer, andSettingsRow, alongside the existingSettingsLayout. (#23747)The components use Factory's settings presentation. Import
SettingsRowfrom the new entry point withoutvariant="factory". The oldcomponents/SettingsRowand settings-specificSectionrow APIs remain compatible through shared implementations and are deprecated for new settings screens.import { SettingsContainer, SettingsRow } from '@mastra/playground-ui/new/settings'; <SettingsContainer> <SettingsRow label="API prefix" htmlFor="api-prefix"> <input id="api-prefix" defaultValue="/api" /> </SettingsRow> </SettingsContainer>;
-
Added opt-in table actions to copy a single table as markdown or download it as CSV once its text finishes streaming. Markdown copies preserve formatting and referenced link and footnote definitions. CSV exports preserve cell text and footnote markers and protect against spreadsheet formula injection. (#23537)
Enable the controls on
MarkdownRendererwithtableActions:<MarkdownRenderer tableActions streaming={streaming}> {text} </MarkdownRenderer>
Added a compact dropdown size for smaller controls. Set
size="sm"onDropdownMenu.ContentandDropdownMenu.Itemto reduce padding, text size, and corner radius without changing other menus.<DropdownMenu.Content size="sm"> <DropdownMenu.Item size="sm">Download CSV</DropdownMenu.Item> </DropdownMenu.Content>
-
Adds
TabbedContainer, a contained tab composition for mixed panel types.Panelaccepts arbitrary content.DataListrenders a table and can place search and filter controls in the tab rail. Both types support the existing tab states and close behavior. Visited panels stay mounted, preserving their scroll position and local state. (#23538)<TabbedContainer defaultTab="overview"> <TabbedContainer.Panel value="overview" label="Overview"> <Overview /> </TabbedContainer.Panel> <TabbedContainer.DataList value="runs" label="Runs" columns="auto minmax(0,1fr) auto" search={{ label: 'Search runs', placeholder: 'Search runs', value: query, onSearch: setQuery }} filter={{ 'aria-label': 'Filter by status', multiple: true, options: statusOptions, value: statuses, onValueChange: setStatuses, }} > {runRows} </TabbedContainer.DataList> </TabbedContainer>
Contained tabs move extra items into a
+Nmenu. Closable overflow items can now be closed from that menu without selecting them.<Tabs defaultTab="runs"> <TabList> <Tab value="runs" onClose={() => closeTab('runs')}> Runs </Tab> </TabList> <TabContent value="runs" flush keepMounted> <RunsTable /> </TabContent> </Tabs>
Adds
DataList.SortableTopCell, a controlled column header that switches between ascending and descending sort directions.<DataList.SortableTopCell sortDirection={sortDirection} onSortChange={setSortDirection}> Created at </DataList.SortableTopCell>
Multi-select comboboxes can show a
clearLabelfooter action, and combobox triggers accept an explicitaria-label.<Combobox aria-label="Filter by status" multiple options={statusOptions} value={statuses} onValueChange={setStatuses} clearLabel="Clear filters" />
Also adds
flushandkeepMountedtoTabContent.flushlets a panel component own the body surface.keepMountedkeeps a visited panel in the DOM after a tab switch. -
Adds
TabbedContainerat@mastra/playground-ui/layout/tabbed-container. The existing DataList entrypoint still exports the same component. (#23887)import { TabbedContainer } from '@mastra/playground-ui/layout/tabbed-container'; <TabbedContainer defaultTab="overview"> <TabbedContainer.Panel value="overview" label="Overview"> <Overview /> </TabbedContainer.Panel> <TabbedContainer.DataList value="runs" label="Runs" columns="1fr"> {rows} </TabbedContainer.DataList> </TabbedContainer>;
Patch Changes
-
DropdownMenu.Trigger,PopoverTriggerand theDateTimePickerdefault trigger now render a design-systemButtonby default and accept Button'svariant,sizeandtooltipprops, so every click-to-open trigger shares the same recipe asSelectandCombobox(including the open-state styling).renderandasChildkeep working and take precedence overvariant/size. Bare triggers that relied on being unstyled must now passvariant="ghost"or userender. (#23763) -
Button: add an
iconprop. The icon is always rendered on the left of the label, wrapped in<Icon>, with a fixed gap, size, opacity and hover transition defined once inButton. All icon+label buttons in@mastra/playground-uiand@mastra/playgroundnow useicon={...}instead of composing the icon insidechildren. (#23764) -
Give every text-only Button an icon via
icon={...}: entity icons from the sidebar (Agent, Workflow, Dataset, Scorer, Trace, Memory, Tools, …) when the action targets a Mastra entity, lucide icons by action verb otherwise (Cancel →X, Save →Check, Delete →Trash2, Connect →Plug, Publish →Rocket, …). Buttons whose label is data (ids, values, zoom level) and pass-through wrappers are left unchanged. (#23764) -
Added a
chatdomain (@mastra/playground-ui/domains/chat) exposing the shared chat context hooks (useChatRunning,useChatSend,useChatMessages,useChatTasks) and the presentational tool-call badge primitives (BadgeWrapper,SectionLabel,LoadingBadge,NetworkChoiceMetadataDialogTrigger) so they can be reused outside of Studio. (#23526) -
Moved the tool-call classifier and grouping helpers (toolCardKind, badgeStatus, toolInteraction, collectToolGroups) and the first self-contained tool badges (ToolApprovalButtons, AskUserBadge, AskUserTool, CodeModeBadge) into playground-ui so hosts can render approval, ask-user and code-mode tool calls without depending on Studio internals. (#23625)
-
Added a
globaloption touseDataListKeyboard/useTableKeydown. When enabled, ArrowUp/ArrowDown/PageUp/PageDown move the list selection from anywhere on the page, without first focusing a row. Keys typed into inputs, comboboxes, menus or open dialogs are left untouched. Enable it on the single main list of a page: (#23760)const { containerRef, getRowProps } = useDataListKeyboard({ count: items.length, global: true });
Studio list pages (agents, tools, workflows, MCP servers, processors, prompts, scorers, datasets, experiments, schedules, inbox, skills, logs, traces) now use it, so pressing ArrowUp/ArrowDown moves the selection right away without having to click or tab into the list first.
-
Added a
hideExpandButtonprop toCollapsiblePanelso the built-in floating "Expand panel" button can be omitted when another control (for example a header toggle) expands the panel. (#23870) -
Fixed legacy PageHeader loading states to hide icons with title content. (#23888)
-
Studio lists with cells outside the main link/button (agents, datasets, experiments, workflows, inbox, skills) now activate from anywhere on the row: clicking a trailing cell navigates or selects, and keyboard focus lands on the row itself instead of the inner link. Buttons, popovers and expanders inside those rows keep their own behavior without triggering the row. (#23762)
-
Tighten Studio's visual density: smaller controls, headings, table rows and badges, plus reduced page gutters, section gaps, card and dialog insets to match a denser layout scale. (#23741)
-
Fix PageHeader alignment when no separate icon is rendered, keeping titles and descriptions aligned with page content. (#23919)
-
Added reusable tool-call approval context to playground-ui with stable provider values. (#23604)
-
Added timed key sequences to
useKeydown. Bindings likeg$+afire whengis pressed and thenawithin a fixed 500ms window, enabling GitHub-style shortcuts.useKeydownnow also ignores unmodified keys (e.g.?,g) while the user is typing in an input, textarea, contenteditable field (including empty andplaintext-onlyattributes), combobox or other keyboard widget, so single-character shortcuts no longer block typing; modifier combos likemod+kkeep working from anywhere. (#23831) -
Added
toggle()toCollapsiblePanelHandleso callers can flip a resizable panel between collapsed and expanded without tracking its state.CollapsiblePanelaccepts an optionalexpandShortcutto show a key hint in the expand button tooltip, and the sidebar toggle button tooltip now shows its[keyboard shortcut. (#23855) -
Fixed settings group heading size to match setting labels. (#23873)
-
Split the trace timeline into two composable views.
TraceSpanTreerenders the span hierarchy with each span's duration at the end of the row, andTraceSpanTimelinerenders spans as bars on a shared time axis. Both share the same expansion, selection and reveal behavior through the headlessSpanRowswalker, so they stay aligned when shown together.TraceTimelineandTraceTimelineSpanare deprecated and now wrapTraceSpanTree; the trace panel, trace details and thread trace views now show the tree with the duration as text. (#23879) -
Moved workspace tool constants, the submit-plan tool id, and Code Mode call detection into playground-ui so hosts can classify tool calls without depending on Studio internals. (#23623)
-
Updated composer commands to use the existing element-ref support in
useKeydownfor keyboard navigation. Command selection uses the shared shortcut dispatcher and leaves events already handled withpreventDefault()or coming from IME composition untouched. (#23828)Before, your keyboard handler forwarded events to the command menu:
inputProps.onKeyDown(event); if (event.defaultPrevented) return;
After, attach the
inputRefpassed touseComposerCommandsto your input and remove that forwarding call. The hook listens on the input directly; your handler still checks whether the command menu consumed the event:function handleComposerKeyDown(event: React.KeyboardEvent<HTMLTextAreaElement>) { if (event.defaultPrevented) return; const composing = event.nativeEvent.isComposing || event.keyCode === 229; const shouldSubmit = event.key === 'Enter' && !event.shiftKey && !composing; if (shouldSubmit) { event.preventDefault(); submitMessage(); } } <ComposerInput {...inputProps} ref={inputRef} onKeyDown={handleComposerKeyDown} />;
Exact commands without options still reach the caller's submit handler. Mount the input with the hook. Shortcuts attached to the input can handle its keys; ancestor and page shortcuts still leave unmodified keys in editable fields alone.
-
Restored the previous control focus styles while gradient focus regressions in inputs and comboboxes are investigated. (#23931)
-
Improved default and outline button contrast. (#23933)
-
Moved the observation marker badge into playground-ui so hosts can render observational-memory markers without Studio internals. (#23642)
-
TraceDataPanelView: the trace panel is now always tabbed (Spans · Timeline · Feedback · Scores), with a new "Timeline" tab that keeps the span tree (names, expansion controls) and adds a trailing column of bars on a shared time axis (TraceSpanTimeline). Both tabs share the same search, selection and expansion state. The tab header is more compact and the span type legend is left-aligned. (#23879)The
messagesPanelSlotcolumn still renders to the left of the span tree, and now folds away while the Timeline tab is active so the bars get the width.TraceDataPanelTabgains the'timeline'value. -
Removed the built-in minimum width from the Combobox trigger and dropped the call-site
min-w-*overrides on Select triggers (agents/entities sort, rule-engine field/operator/value selects) so popover triggers size to their content like any other button. Popups keep their minimum widths. (#23763) -
Studio now renders chat messages with the shared primitives from
@mastra/playground-ui/domains/chatinstead of its own copies. No visible change. (#23594) -
Fixed keyboard shortcuts so rejected scoped sequences do not block other shortcuts, disabled or unmounted shortcuts cannot resume pending sequences, and consumed or composing events do not trigger global actions. Disabled list search shortcuts no longer intercept the active search field's shortcut. (#23831)
-
Fixed
pill-ghosttabs looking too airy after the dark-theme scaling pass. Tabs in a<TabList variant="pill-ghost">now render with the exact ghost Button recipe (28px height, 13px text, same padding, hover and focus styles), the list no longer adds its own padding and uses a tighter gap, and the active pill fills the full tab height. Consumers no longer need to pass padding overrides toTaborTabList. (#23829) -
Fixed the new Dialog variant to use the design system's paired typography scale. (#23935)
-
Improved the span and trace panel headers in Studio: the ID is shown without a
#prefix, the label and ID button are aligned, hovering shows the copy action, and clicking the ID copies it without an extra icon or layout shift, with a confirmation tooltip. The compact span panel on the Logs page now shows start, end and duration as icons with tooltips instead ofStarted/Ended/Durationrows, matching the main span panel. (#23618) -
Added the chat message rendering primitives (text, reasoning, data/signal and file renderers, signal/tripwire/system-reminder badges, message metadata types) under
@mastra/playground-ui/domains/chat/messages/*, and the attachment helpers (classifyAttachment,isTextMimeType, preview dialog entries) under@mastra/playground-ui/domains/chat/attachments/*.MessageMetadata, signal data helpers andreadToolPart/isToolPartare also exported from@mastra/playground-ui/domains/chat. These were previously internal to the Studio app and can now be reused by other hosts. (#23594) -
The advanced thread view (
?variant=advanced) is now built on the composableThreadTracecomponent from@mastra/playground-ui. Behaviour is unchanged. (#23836) -
Unified Studio typography on design-system tokens. Tailwind text-xs…4xl utilities now map to DS sizes with paired line-heights, headings follow a consistent hierarchy, and arbitrary pixel sizes in badges, code views, and charts were replaced with token values. (#23629)
-
Improved grouped tool-call summaries with successful, failed, and incomplete counts. Calls without a recorded result are not counted as successful when a run stops. Existing consumers that omit outcome information retain their previous summaries. (#23536)
-
Unified form control sizes on the Button scale. The duplicate
defaultsize (identical tomd) was removed fromInput,InputGroup,Textarea,ButtonsGroup,TextFieldBlockandSearchFieldBlock— usemdinstead.lgis now 28px (with 14px text) across all text controls so a large input and a large button line up in the same row; icon buttons (icon-lg) keep their 32px size.Textareagained anxssize, and theform-defaultsize token was removed (useform-md). (#23825) -
Unify popover menu item styling on the Button ghost recipe (DropdownMenu, ContextMenu, Select, Combobox, PropertyFilter, DataFilter). Items now share one
menuItemClassprimitive (28px height,text-ui-smd,rounded-lg,bg-neutral6/5highlight, right-aligned check indicator) and onemenuPopupClasscontainer. (#23826) -
Unified breadcrumb crumb styling. Every crumb now uses the same box as a ghost/sm button (height, radius, padding, colors), so a label sits pixel-aligned next to icon-only controls. Added
icon,isLoadingandCrumbSkeletontoCrumb, andComboboxnow acceptssize="icon-sm"with anaria-labelto render a chevron-only switcher, plus analignprop to open the popup from the trigger's end edge. Menu-like popups (Combobox, Select, DropdownMenu, ContextMenu) now size to their widest item instead of stretching to the full available width. (#23833) -
Added
CreateButtonto the design system. It renders aPlusicon, binds theCkey to its click, and shows a tooltip with the text you pass plus aCkey hint. (#23844)import { CreateButton } from '@mastra/playground-ui/ds/components/Button'; <CreateButton variant="primary" tooltip="Create a new agent" onClick={openDialog}> New agent </CreateButton>;
@mastra/rag@2.6.3
Patch Changes
- Improved MDocument declarations with a short creation example and directions to bundled retrieval documentation. (#23489)
@mastra/react@1.5.0
Patch Changes
-
Fixed chat run correlation by retaining run IDs on streamed messages and exposing the active run ID from useChat. This lets interfaces keep unfinished history separate from current execution. (#23536)
Example
Read
activeRunIdinside a component usinguseChat:import { useChat } from '@mastra/react'; function ChatRunStatus() { const { activeRunId } = useChat({ agentId: 'support-agent' }); return <p>{activeRunId ? `Active run: ${activeRunId}` : 'No active run'}</p>; }
-
Improved MessageFactory so terminal agent failures remain visible when rendering message history. (#23867)
-
Fixed delayed chat history responses overwriting streamed messages, including after completion. Restore saved tasks and pending tool approvals during active runs without overwriting newer live updates or restoring resolved approvals. (#23550)
@mastra/redis-streams@0.4.3
Patch Changes
-
close()now waits for in-flight publishes to reach the stream before quitting the writer, including publishes that were still connecting. A workflow's terminal event published right asmastra.shutdown()closed the pub/sub used to be dropped and reject the publisher with aClosingError. (#23168)Also in: @mastra/valkey-streams@0.5.2
-
inFlightTimeoutMsnow settles a timed-out entry atomically. The ownership check and the republish +XACKrun in a single Redis script, so a sibling consumer claiming the entry between the two can no longer cause a duplicate republish or anXACKof the sibling's pending entry. (#23698) -
Fixed
inFlightTimeoutMshaving no effect on fan-out (ungrouped) subscriptions. The reclaim loop skipped those subscriptions entirely, so a hung handler on one was never nacked and recovered. The sibling-claim scan is still skipped for fan-out subscriptions (they have no siblings), but the in-flight timeout pass now runs for them wheninFlightTimeoutMsis set. (#23697) -
RedisStreamsPubSub's reclaim loop no longer re-invokes a subscription's own handler for a message that is still being processed locally. Previously, a grouped subscription'sXAUTOCLAIMreclaim could claim a still-pending entry back onto the same consumer and deliver it again — invoking the callback a second time, concurrently, for the same event whenever a handler ran longer thanreclaimIdleMs. Because the reclaim path never incrementeddeliveryAttempt, this redelivery repeated every reclaim cycle indefinitely, bypassingmaxDeliveryAttemptsand producing duplicate concurrent executions (e.g. long-running workflow steps). Each subscription now tracks its in-flight stream entry IDs, and the reclaim loop lists idle pending entries withXPENDINGand onlyXCLAIMs the ones it is not already processing. Claiming resets an entry's idle clock even for its current owner, so filtering before the claim (rather than skipping after it) also keeps a genuinely hung handler's entry reclaimable by a live sibling consumer. Fixes #23648. (#23656)Behavior change: a subscription never redelivers to itself anymore, so a handler that hangs (never acks or nacks) is only recovered by a different consumer in the group. In a single-consumer group that message stays pending until the process restarts. Set the new
inFlightTimeoutMsoption to have the subscription nack such a message on the handler's behalf after that long; the nack republishes with an incrementeddeliveryAttempt, somaxDeliveryAttemptsstill bounds retries. It defaults to0(disabled).import { RedisStreamsPubSub } from '@mastra/redis-streams'; const pubsub = new RedisStreamsPubSub({ url: process.env.REDIS_URL, // Give up on a handler that has neither acked nor nacked after 10 minutes // and retry it (bounded by maxDeliveryAttempts). inFlightTimeoutMs: 10 * 60 * 1000, });
Before nacking on a handler's behalf, the timeout path checks that this consumer still owns the pending entry; if a sibling has already reclaimed it, the local marker is dropped without republishing. The reclaim loop also paginates its
XPENDINGscan so a large number of locally in-flight entries cannot hide a reclaimable one that sorts after them.The package now documents a Redis 7.0+ requirement: the reclaim loop relies on
XCLAIMdropping trimmed entries from the pending list, which Redis 6 does not do.
@mastra/s3@0.6.3
Patch Changes
- Fixed S3 filesystem initialization with prefix-restricted credentials by checking the configured prefix instead of the whole bucket. Prefixed filesystems require permission to list that prefix. (#23519)
@mastra/schema-compat@1.3.10
Patch Changes
-
Fixed structured output 400s on OpenAI strict endpoints by stripping JSON Schema validation keywords that strict mode rejects.
prepareJsonSchemaForOpenAIStrictModenow recursively removes these keywords — including inside$defs/definitionsreferenced schemas — and folds their intent into each node'sdescription, matching how the tool path already degrades constraints. (#23547)Keywords removed
- Array:
uniqueItems,minItems,maxItems - String:
minLength,maxLength,pattern,format - Number:
minimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOf - Structural (dropped, no useful mapping):
contains,minContains,maxContains,minProperties,maxProperties,patternProperties,unevaluatedItems,unevaluatedProperties - Composition/conditional:
allOfis flattened into the containing node,oneOfis converted to the supportedanyOf, andnot/if/then/else/dependentRequired/dependentSchemasare dropped
Referenced schemas hoisted into
$defs/definitionsreceive the same required-property,additionalProperties: false, and keyword handling as inline schemas. - Array:
@mastra/server@1.67.0
Patch Changes
-
Simplified A2A handler maintenance without changing discovery, validation, or execution behavior. (#23730)
-
Fixed score, dataset, and background-task list endpoints to return
400 Bad Requestfor invalidpageorperPagevalues, such as?perPage=2.5or?page=-1, instead of500 Internal Server Error. (#23892) -
Fixed parsing of request bodies supplied as
undefinedto preserve whole-schema defaults and optional values while supporting defaults for bodyless object fields. The empty-object fallback is limited to object schemas, so omitted required record bodies remain invalid. When no fallback succeeds, validation retains the original missing-input error. Explicit null and other falsy JSON values are validated without being replaced. (#23893)
@mastra/valkey-streams@0.5.2
Patch Changes
-
Fixed
ValkeyStreamsPubSubconsumer-group reclaim so an idle unacknowledged message is redelivered to another live consumer in the group instead of back to the consumer whose handler is still processing it. Previously a handler running longer thanreclaimIdleMswas invoked again, concurrently, for the same event on every reclaim tick with nodeliveryAttemptincrement, somaxDeliveryAttemptsnever applied; and because each self-reclaim reset the entry's idle clock, a genuinely hung handler's entry was never released to a sibling.unsubscribe()now also waits for an in-flight reclaim pass before tearing down. This brings@mastra/valkey-streamsin line with@mastra/redis-streams. (#23697)Behavior change: a subscription never redelivers to itself anymore, so a handler that hangs (never acks or nacks) is only recovered by a different consumer in the group. In a single-consumer group that message stays pending until the process restarts. Set the new
inFlightTimeoutMsoption to have the subscription nack such a message on the handler's behalf after that long; the nack republishes with an incrementeddeliveryAttempt, somaxDeliveryAttemptsstill bounds retries. It defaults to0(disabled). The timeout settlement verifies ownership and settles in one atomic server-side step, so a sibling that has already reclaimed the entry is never acked out from under it.import { ValkeyStreamsPubSub } from '@mastra/valkey-streams'; const pubsub = new ValkeyStreamsPubSub({ url: process.env.VALKEY_URL, // Give up on a handler that has neither acked nor nacked after 10 minutes // and retry it (bounded by maxDeliveryAttempts). inFlightTimeoutMs: 10 * 60 * 1000, });
@mastra/voice-google-gemini-live@0.14.10
Patch Changes
-
Fix missing usage events when Gemini Live sends usage metadata alongside response content, setup, or tool calls. Preserve content-derived modality and normal message routing while processing usage independently. (#23835)
-
Fixed realtime audio input to include the configured sample rate in its MIME type, so Gemini Live can interpret incoming PCM audio at the correct rate. (#23834)
@mastra/voice-openai-realtime@0.14.0
Minor Changes
-
Added session hooks to
OpenAIRealtimeVoicefor applications that manage parts of the OpenAI Realtime session themselves. Every server event is now re-emitted asopenAIRealtime:<event.type>, the socket emitsopenandclose, andsendEvent()is public so you can send any client event, such as adding conversation items. (#23916)voice.on('openAIRealtime:rate_limits.updated', event => console.log(event.rate_limits)); voice.on('close', ({ code, reason }) => console.log('socket closed', code, reason)); voice.sendEvent('conversation.item.create', { item: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'Hello' }] }, });
Fixed the provider sending an extra
response.createfor function calls whose tools were not registered withaddTools(). Tools declared directly throughsession.updateare now left to the application, so OpenAI no longer rejects the application's ownresponse.createwithconversation_already_has_active_response. Fixes #20219.
Patch Changes
-
Fixed public realtime speech boundary events and complete input transcription payload delivery. Subscribe to the native event names to receive speech timestamps and transcription usage when provided, without changing existing
writingevents. (#23432)voice.on('input_audio_buffer.speech_started', event => { console.log(event.item_id, event.audio_start_ms); }); voice.on('input_audio_buffer.speech_stopped', event => { console.log(event.item_id, event.audio_end_ms); }); voice.on('conversation.item.input_audio_transcription.completed', event => { console.log(event.transcript, event.usage); });
Other updated packages
The following packages were updated with dependency changes only:
- @mastra/agent-builder@1.1.19
- @mastra/arize@1.3.16
- @mastra/arthur@0.4.16
- @mastra/braintrust@1.3.13
- @mastra/datadog@1.4.8
- @mastra/deepeval@0.1.10
- @mastra/deployer-cloud@1.67.0
- @mastra/deployer-cloudflare@1.2.26
- @mastra/deployer-netlify@1.2.26
- @mastra/deployer-sandbox@0.3.11
- @mastra/deployer-vercel@1.2.26
- @mastra/laminar@1.3.18
- @mastra/langsmith@1.3.18
- @mastra/longmemeval@1.1.26
- @mastra/mcp-docs-server@1.2.26
- @mastra/nestjs@0.2.26
- @mastra/next@0.2.25
- @mastra/opencode@0.1.26
- @mastra/otel-bridge@1.5.8
- @mastra/posthog@1.3.10
- @mastra/sentry@1.2.18
- @mastra/tanstack-start@0.2.25
- @mastra/temporal@0.4.5
- @mastra/turso@0.1.6
- @mastra/upstash@1.4.6
- @mastra/voice-xai-realtime@0.2.10