Repository navigation
Releases
November 12, 2026
Highlights
Resumable Agent streams (now for every Agent)
Every Agent can now publish a resumable run stream with replay caching, cross-instance observe/abort (agent.observe(runId), observe().abort(), agent.abortRunStream(runId)), and ownership/TTL enforcement—so clients can reconnect without killing the run by default.
Built-in MCP server at /api/mcp + OAuth discovery
@mastra/server now ships a built-in MCP endpoint (enabled by default when server.auth is configured) so MCP clients can call your agents/workflows/tools without defining an MCPServer, plus OAuth discovery/audience checks via server.mcp.oauth and a new introspection route GET /api/mcp/builtin.
Workflow Storage Version 2 (delta writes) now supported on Postgres + libSQL
Experimental storageVersion: 2 reduces writes by persisting only per-step changes (instead of rewriting the whole run) and now works with @mastra/pg and @mastra/libsql, with results that can lazily load step history via an async steps() function.
Resumable AI SDK chat routes (useChat({ resume: true }) support)
chatRoute({ resumable: true }) now generates a POST + reconnect GET endpoint pair that replays/follows active runs via agent.observe(runId), with explicit route-array configuration support (server.apiRoutes: [routes]).
One-off and bounded schedules for agents & workflows
Schedules now support one-time execution (runAt) and bounded recurring execution (endAt), with TypeScript updates (cron is optional) across server, client, and multiple storage backends.
Breaking Changes
Agent’sdurableoption now controls resumable streams only (not checkpoint-backed durable execution); migrate durable execution tocreateDurableAgent({ agent }), and note plain-agent disconnect behavior differs when streams are enabled.
Changelog
@mastra/core@1.83.0
Minor Changes
-
Added resumable streams to every
Agent, not just durable agents. An agent registered onMastranow publishes each run on its stream topic and keeps a replay cache, so a client can reconnect after a disconnect, another reader can follow the run withagent.observe(runId), and any reader can stop it withobserve().abort()oragent.abortRunStream(runId). (#28424)Also in: @mastra/server@1.83.0
-
Avoid blocking the event loop while loading instruction files from a trusted Git ref and while discovering tool-result instruction reminders. Keep missing and untrusted-ref behavior unchanged. (#30104)
Also in: @mastra/code-sdk@1.19.0
-
Add model and provider as span query sort fields. (#30096)
-
Added
server.mcp.oauth(resource,requireAudience,authorizationServers,scopesSupported) to configure OAuth discovery and token audience checks for the built-in MCP server. Auth providers can implement the new optionalgetOAuthMetadata()andverifyAudience()methods to advertise their authorization server and check the token audience.CompositeAuthcombines the authorization servers of its providers and checks the audience with the provider that accepted the token. The newCompositeAuth.getAuthenticatedProvider(user)returns that provider. (#29930) -
Added the
server.mcpoption for configuring the built-in MCP server at/api/mcp: turn it on or off, change its path, choose toolsets, feature agents and workflows, serve only read-only tools, and include or exclude agents, workflows, and tools by id. (#29929) -
Added
memory,observability,observability_analytics, andstored_agentsto the toolsets accepted byserver.mcp.toolsets. (#29931) -
Chat SDK logs from channels now use your Mastra logger, so they follow its format, level, and redact settings. These logs no longer print with the
[chat-sdk]console prefix. To filter them, usecomponent: 'chat-sdk'. Use the newtoChatLoggerexport to send adapter logs to the same logger. (#29392) -
Added experimental
generativeUIoption to the Slack channel adapter. When enabled, the agent gets arender_cardtool that posts json-render specs as Block Kit cards: fields, tables, charts, images, and link buttons. Invalid specs are returned to the agent as a list of issues so it can fix and retry. Requires the optional@json-render/chatpackage. (#29820)Also in: @mastra/slack@1.9.0
-
Added resumable AI SDK chat routes and explicit route-array inputs for server configuration. (#29549)
Also in: @mastra/ai-sdk@1.12.0
-
Add optional feedback replacement events and storage support. Keep the original feedback ID, trace ID, and timestamp when replacing a record. Storage callers can preserve existing feedback with
onConflict: 'ignore': (#30059) -
Added duration ordering for completed span queries. Use
orderBy: [{ field: "durationMs", direction: "desc" }]to find the slowest spans. (#30085)Also in: @mastra/clickhouse@1.30.0, @mastra/client-js@1.60.0, @mastra/duckdb@1.19.0, @mastra/pg@1.36.0, @mastra/server@1.83.0
-
The experimental workflow
storageVersion: 2option now works with@mastra/pgand@mastra/libsqlas well as in-memory storage. (#28299) -
Added
buildEnvandbuildEnvRevisiontoFactorySandboxContext. Factory uses them to pass a factory's Build environment variables to template builds and setup steps without putting the values in the template identity. A provider should includebuildEnvRevision(a random id that changes whenever the values change) in its template identity so an edit rebuilds the template. Both fields are optional, so existing providers compile unchanged. (#29923) -
Add span-local error counts. (#30088)
Also in: @mastra/client-js@1.60.0, @mastra/observability@1.24.0
-
Add shared contracts for opt-in aggregated trace roots. OSS readers remain unsupported. (#30054)
Also in: @mastra/client-js@1.60.0, @mastra/server@1.83.0
-
Added shared text-search query contracts for Platform integration. (#29897)
-
Add optional review-status update methods for observability exporters. Unsupported exporters skip these updates safely. (#30075)
-
Added
maxChunkCharsto the semantic recall options. Messages longer than this many characters are split into chunks, and each chunk is stored as its own vector row, so long messages no longer fail to embed or lose the text past the embedder's input limit. The default is 16,384 characters, about 4,096 tokens of English text. Lower it for Chinese, Japanese, or Korean text, or code. (#29826) -
Scorer judges now accept the same
instructionsas agents, and dynamic instructions can read the run'srequestContext. You can keep a large shared input, such as a report, in a cached system message and change it per run. (#29387) -
Added direct feedback lookup to observability storage.
await storage.getFeedbackById(feedbackId)returns the record ornullwhen it is missing. (#29529)Also in: @mastra/clickhouse@1.30.0, @mastra/duckdb@1.19.0, @mastra/pg@1.36.0
-
Added
diffResponseMessagesand theResponseMessagesCursortype to@mastra/core/agent/durablefor durable agent integrations outside core, such as@mastra/inngest. They build the step events that carry only the response messages that changed since the previous step, whichobserve()and callers rebuild into the full list. (#29976) -
Added an experimental
storageVersionworkflow option. WithstorageVersion: 2, a run saves only what changed at each step instead of rewriting the whole run. This cuts storage writes for long workflows and loops, including loops whose state grows every iteration. (#27144) -
Added one-off and bounded schedules for agents and workflows. Pass
runAtinstead ofcronto fire a schedule once, or passendAtwithcronto stop a recurring schedule after a given time. Finished schedules use the existingcompletedstatus when they have no automatic fires remaining. Declarative workflow schedules (createWorkflow({ schedule })) acceptrunAtandendAttoo, and schedule hooks receivetrigger.kind: 'once'for a scheduled fire of a one-off agent schedule. (#25131)Also in: @mastra/client-js@1.60.0, @mastra/convex@1.9.0, @mastra/libsql@1.30.0, @mastra/mongodb@1.27.0, @mastra/mysql@0.17.0, @mastra/pg@1.36.0, @mastra/server@1.83.0, @mastra/spanner@1.12.0
-
Add optional context mode, timestamp, and response identity fields to HTTP feedback/score types. Preserve an explicit null feedback author in storage without substituting the span user; omitted legacy storage attribution remains supported. (#29289)
-
Added an optional
userargument to dynamic configuration callbacks. When server authentication is configured, callbacks forinstructions,model,tools,memory, and other dynamic options receive the authenticated end user normalized to a stableMastraUsershape (id,name?,email?,avatarUrl?,role?), so you can personalize agents without reading auth keys fromrequestContextyourself. (#28612) -
Added an
injectCatalogoption toSkillsProcessorso agents with many skills can leave the skills list out of the system message. WithinjectCatalog: false, the agent gets a short instruction to find skills with theskill_searchtool and load them with theskilltool. Theskill,skill_read, andskill_searchtools stay available for both workspace skills and skills configured on the agent. (#26316) -
Required a nonblank
traceIdin the feedback and score HTTP body schemas. Missing, null, empty, and whitespace-only IDs are rejected. Storage creation, batch APIs, and historical nullable records are unchanged. (#29287) -
Added an optional
persistenceLocationcapability to observability storage domains. Adapters can report'local'for embedded files, in-memory databases, and local replicas, or'remote'for datab...
November 4, 2026
Highlights
Knowledge v2: page-tree model, richer tools, and server-side sync APIs
Knowledge is now a first-class page tree (pages/notes/pins) with major tool upgrades: knowledge_list, line/section/grep-style reads in knowledge_read, list pinning (kind: 'list'), and a link index for counting/listing [[links]]. Apps can also sync external content via server APIs like knowledge.upsertPage()/removePage() and note upserts, with optional vector/hybrid search and chunked embeddings for very long pages.
Knowledge Explorer + read-only Knowledge HTTP/JS client APIs (and SSE change stream)
Mastra Server now offers opt-in read-only Knowledge routes (tree/page/graph/search/activity) plus an SSE events stream, with matching listKnowledge() / getKnowledge() APIs in @mastra/client-js. These routes are scope-gated (and FGA-aware) and enable building Knowledge browsing UIs outside Studio, including link-count/link-edge endpoints when the store supports the link index.
Durable agent continuity across API restarts (fencing + shared storage requirement)
Evented durable agents no longer stop when the API process shuts down: workers continue running, clean up, and free threads correctly, with improved run ownership isolation and safer cancellation semantics. To get true cross-process continuity you now need fencing-capable shared storage (e.g. PostgresStore from @mastra/pg) plus shared pubsub/lease infrastructure.
Background tasks + MCP: resumable “input required” flows that survive reloads
Suspended background tool calls can now be resumed after reload with persisted open-request metadata, concurrency-safe resume via suspendedAt, and optional completion notifications that wake idle threads; @mastra/client-js and @mastra/server add resume APIs. MCP also gains “answer task input from chat” behavior (suspends instead of failing when unhandled), plus richer tool results (images/audio/files) via MCPServer.toolResult.
Observability upgrades: streaming latency metrics + richer trace filtering
Trace/span aggregates now support timeToFirstToken.* and timeToFirstChunk.* so dashboards can chart streaming latency, gated by store capability. Advanced trace queries also gain requestId, serviceName, and experimentId filters across core and supported stores, with capabilities surfaced to clients and clearer 501s when a store is too old.
Breaking Changes
@mastra/temporal@0.5.0: Steps now validateinputSchemaandrequestContextSchemabefore running (can fail runs that previously “succeeded” with wrong input); setvalidateInputs: falseto opt out.- Experimental memory changes:
experimental_subconsciousand Memoryknowledgeoption are removed (now throw); Knowledge must be attached to the Agent, and older Subconscious/Coordinator constructors now throw. knowledge.reindex()is removed; useupsertPage/note writes (andreindexLinks()for link indexing) instead.- Factory (
@mastra/factory@0.27.0): removes work-item claiming APIs (claimKeyremoved; related methods/types removed) as part of allowing the same external item to exist in multiple projects.
Changelog
@mastra/core@1.82.0
Minor Changes
-
An agent's
scopesfunction can now narrow or replace a run's scopes. It receives the run's incoming scopes (from the call,defaultOptionsand request context) and returns the run's final set. A staticscopeslist is still added to the incoming scopes. The function must keep anyresource:orthread:scope it was given; dropping one throws. (#28353)const agent = new Agent({ id: 'support', name: 'Support', instructions: 'Answer support questions.', model, // Keep identity scopes, drop anything else the caller sent, and add the tenant's org. scopes: ({ scopes, requestContext }) => [ ...scopes.filter(scope => typeof scope === 'string' && /^(resource|thread):/.test(scope)), `org:${requestContext.get('orgId')}`, ], });
-
Added
requestId,serviceName, andexperimentIdfilters to advanced trace queries. Use them at trace scope or insidespans.some/spans.nonewitheq,ne,in,notIn,exists, andnotExists. (#29194)const result = await mastraClient.queryTraces({ timeRange: { from: '2026-11-01T00:00:00.000Z', to: '2026-11-02T00:00:00.000Z' }, where: { op: 'and', args: [ { op: 'eq', left: { path: 'requestId' }, right: { literal: 'req-42' } }, { spans: { some: { op: 'eq', left: { path: 'serviceName' }, right: { literal: 'payments' } } } }, ], }, });
-
Added
onDecisiontoClassifierProcessorfor writing guardrail decisions to your own audit log (#28985). It runs after each classification, including failed classifier calls, and receives the outcome (allowed,filteredoraborted), the answers or the error, the call duration, and the ids of the message, thread, resource or run. (#29070)new ClassifierProcessor({ classifier: safety, onResult, onDecision: async decision => { await auditLog.insert({ threadId: decision.ids.threadId, outcome: decision.outcome, answers: decision.result ? decision.result.answers : null, error: decision.result ? null : String(decision.error), }); }, });
abortinonResultnow accepts tripwire metadata, so the caller can tell which policy blocked the request:onResult: (answers, { abort }) => { if ((answers.unsafe.probability ?? 1) > 0.8) { abort('Blocked', { metadata: { policy: 'unsafe' } }); } };
A failed classifier call under
errorStrategy: 'strict'now sets tripwire metadata{ processorId, classifierId, code: 'CLASSIFIER_FAILED' }. On agents that aren't durable, stopping a streaming reply now cancels a classifier call that is still running. -
Fixed evented durable agents stopping when the API process shuts down. Workers now keep running the agent, finish cleanup, and free the thread, so the next message starts right away. (#28951)
Each run keeps its own ownership. Nested durable agents and runs that share a
RequestContextno longer pick up each other's ownership. Canceling a suspended run still notifies listeners, and a late cancel can no longer stop a newer run of the same agent.This needs storage that supports run fencing. Other shared setups keep their current behavior and log a warning once per agent: API shutdown can still stop remote workers. Single-process setups are unchanged.
To enable worker continuity across API loss, use a shared fencing-capable adapter, such as
PostgresStorefrom@mastra/pg, and a shared pubsub transport:new Mastra({ storage: new PostgresStore({ id: 'shared-storage', connectionString }), pubsub, agents: { agent: createEventedAgent({ agent, pubsub }) }, });
-
Added
getStepResultAsyncto workflow step, condition, sleep, and.map()contexts, and deprecatedgetStepResult. (#26999)getStepResultAsyncreturns the same value asgetStepResult, wrapped in a promise. Switching now prepares workflows for upcoming storage changes that load step results on demand instead of keeping every result in memory.getStepResultkeeps working and will be removed in a future major release.// Before const step2 = createStep({ execute: async ({ getStepResult }) => { const step1Result = getStepResult(step1); }, }); // After const step2 = createStep({ execute: async ({ getStepResultAsync }) => { const step1Result = await getStepResultAsync(step1); }, });
If your tests build step execute params by hand, add a
getStepResultAsyncfunction to them. -
Changes to an experimental, upcoming memory feature. (#28353)
experimental_subconsciousand the Memoryknowledgeoption are removed: setting either now throws, saying to attach Knowledge to the agent instead (knowledge: falseis still accepted).SubconsciousandKnowledgeSemanticIndexCoordinatorstay exported from@mastra/memoryso older code still loads, and throw when constructed.@mastra/core/knowledgeexportsKnowledgewith its option and result types and the Knowledge storage domain; the tool, pin and semantic-index helpers are internal.Agent
scopesare plain"<type>:<value>"strings that say who the run is; what the run may read or write comes from whoever consumes them, such as Knowledge's scope declarations.A message can carry its own scopes:
session.sendMessage({ content, scopes }). A run's scopes are fixed when it starts, so a message whose scopes differ from the running run's waits for it to end, then starts its own run in the same conversation.sendSignalreports this as{ action: 'blocked', reason: 'scope-mismatch', runId }.queueMessage,queueSignal,sendNotificationSignalandsteertakescopesthe same way: a queued or steering message, or a notification, whose scopes differ from the running run's starts its own run with its scopes after that run instead of joining it. A deferred notification keeps the scopes it was sent with. Scopes are set in process only: remote callers (the agent controller HTTP routes and@mastra/client-js) can't send them. To derive scopes from a request, give the agent ascopesfunction that reads trusted request context. When the thread's claimed owner runs in another process, a message with scopes isn't queued:sendMessagerejects with an error saying scopes don't cross processes. Messages without scopes still join that owner's run.Attach Knowledge with
new Agent({ knowledge, scopes }). Knowledge needsscopes, the scope types its runs may hold and who gets...
November 2, 2026
Highlights
MCP Tasks (Storage-backed, restart-safe long-running execution)
@mastra/mcp adds experimental Mastra-storage-backed MCP tasks so long-running tools/agents/workflows can return immediately with a task, be polled/answered/cancelled across instances, and survive restarts with shared storage and unified tracing.
Background Tasks for Non-Agent Owners (Retention, Concurrency, Queued Resume)
Mastra background tasks now support non-agent owners (e.g., MCP servers) with per-task expiresAt/metadata retention, per-owner concurrency overrides, and { onNoSlot: 'queue' } to queue resumes until capacity frees up (with storage adapters updated to persist the new fields).
Observability: Outcomes/Stop Reasons + Model/Provider + TTFT Across Queries and Stores
Observability now records execution outcome + typed stopReason (and context), exports model/provider, and measures timeToFirstTokenMs (TTFT); these fields are queryable/groupable in clients/servers and supported by major stores (ClickHouse/DuckDB/Postgres), enabling better failure analysis and model/provider cost/perf breakdowns.
Dashboards and Saved Views as Schemas + UI Rendering/Sharing
Core adds browser-safe schemas for custom dashboard definitions and saved observability list views, while @mastra/playground-ui introduces DashboardRenderer plus first-class Saved Views for Traces/Spans/Threads/Logs (URL↔view helpers, view picker, and host-provided persistence).
Agents + Workspaces: Authenticated Channel Attachments & Workspace File UX
Channel agents can now download authenticated binary attachments (e.g., Slack ZIP/DOCX) into channel-attachments/ with storage caps/policies, and text attachments (CSV/JSON/YAML/logs) are fetched and inlined as text; workspace write protection is now opt-in (via requireReadBeforeWrite) to reduce false StaleFileError failures.
Breaking Changes
@mastra/playground-ui: Metrics chart card API cleanup (e.g., removedChartCarddescription/summary, removed severalMetricsCard.*components, changed related table/legend APIs).@mastra/playground-ui: Removed fluid-hover utilities (useFluidHover,FluidHoverHighlight) andCommandList.highlightClassName.@mastra/core: Removed runtime-importable subpaths@mastra/core/network/vNextand@mastra/core/telemetry/otel-vendor(they pointed to files that no longer ship).@mastra/factory:defineBoard()andMastraFactoryboards/includeDefaultBoardsare deprecated in favor of stored boards APIs; code reading deprecated board fields (title/role/next) should move toname/runner.context/nextPhases.
Changelog
@mastra/core@1.81.0
Minor Changes
-
Added schemas for saved observability list views. Use
savedViewStateSchemato validate a saved traces, spans, threads or logs view: its filters, time range, sort, columns and page options. A service that only stores views can usesavedViewStateEnvelopeSchemainstead. It checks the version, the page and the 16 KiB size limit. Dashboards can reuse the filter shape throughfilterNodeSchema. It accepts any operator id, so new operators don't need a schema change. (#28715)import { savedViewStateSchema } from '@mastra/core/storage'; savedViewStateSchema.parse({ schemaVersion: 1, page: 'traces', filters: [{ fieldId: 'status', value: 'error' }], dateRange: { preset: 'last-24h' }, sort: { field: 'startedAt', direction: 'desc' }, });
-
Channel agents with a workspace can now open binary file attachments, such as ZIP and DOCX files, that need an authenticated download (for example Slack files). The file is downloaded through the adapter and saved under
channel-attachments/in the agent's workspace. The agent gets the workspace path instead of a private URL it can't open. (#28913)This covers attachments that
inlineMediadoesn't send to the model as file parts. PDFs are sent inline by default.When the workspace uses
mounts, the file is saved in the first writable mount other than/. Saving follows the workspace's write-file tool policy: ifmastra_workspace_write_fileis disabled or needs approval, the file isn't saved. Adapters whose links don't need a token also get the workspace path. For example, a Discord agent with a writable workspace now gets the workspace path instead of the CDN URL, which expires.The agent still gets the URL for files over 10 MiB, failed downloads, read-only workspaces, workspaces where writes are disabled or need approval, and agents without a workspace.
channel-attachments/is capped at 100 MiB per workspace so channel participants can't fill the disk. Once a file would go over the cap, the agent gets the URL instead and nothing is written. Change the cap withmaxAttachmentStorageBytes:new Agent({ // ... channels: { adapters: { slack }, maxAttachmentStorageBytes: 500 * 1024 * 1024 }, });
-
Added
setCookies()toMastraBrowser, so your app can sign a thread's browser in to a site the agent can't sign in to itself, such as a preview behind your own login. The cookie value never passes through the model. Cookies set before the browser opens are added when it does. (#28631)await browser.setCookies([{ name: 'my_app_session', value: token, domain: 'app.example.com', path: '/' }], threadId);
Browser providers that don't support cookies throw. Custom providers can add support by overriding
addCookiesToOpenBrowser()and callingaddPendingCookies(threadId)once a thread's browser opens, before it loads a page. -
Added factory-scoped sandbox environment. A deployment that hosts several factories can now give a credential to one factory's sessions only: every Factory sandbox provider accepts
envas a function of the session's sandbox context, which carries the newfactoryIdandfactoryNamefields. Scope secrets byfactoryId, because names can be edited and aren't unique. (#28767)new PlatformFactorySandbox({ env: ctx => (ctx.factoryId === PLATFORM_FACTORY_ID ? { PLATFORM_TOKEN } : {}), });
Platform and Docker sandboxes keep the environment they started with when they resume. When you remove a credential from a factory, recreate its running sandboxes.
Also in: @mastra/docker@0.13.0, @mastra/e2b@0.16.0, @mastra/factory@0.26.0, @mastra/platform-workspace@1.11.0
-
Added execution outcomes and typed stop-reason details to agent, model, tool, and workflow traces. Traces distinguish failures, cancellations, limits, guardrail blocks, and observer interruptions; TripWire errors now use the name
TripWireinstead ofError, including in string representations, stacks, and serialized errors, so they can be identified across workers. (#28703)Custom spans can record an outcome with a compact stop-reason context:
import { SpanType, type ObservabilityInstance } from '@mastra/core/observability'; function recordGuardrailBlock(observability: ObservabilityInstance, processorId: string) { const span = observability.startSpan({ name: 'policy check', type: SpanType.GENERIC }); span.end({ outcome: 'blocked', stopReason: 'guardrail', stopReasonContext: { processorId } }); }
The public
outcome,stopReason, andstopReasonContextfields now also allownull. Exporter and other consumer types must handlenullalongside an absent value; arbitrary custom outcome strings and context objects remain supported. Workflow events and durable snapshots can now include tripwire details, including processor-supplied metadata.Agent and generation spans also close when the internal workflow fails without a finish callback, while errors followed by a finish retain their final usage and output.
-
Added an optional
inputAcceptedfield toOutputResult, theresultargument ofprocessOutputResult. It is set only whenfinishReasonis'error', and istruewhen the run accepted the turn's input before it failed.MessageHistoryuses it to keep the user message of a failed turn that produced no output, so an accepted turn is not lost from history. Failed turns without the flag are still dropped, as before. (#28988)import type { Processor } from '@mastra/core/processors'; const processor: Processor = { id: 'accepted-turns', async processOutputResult({ messages, result }) { if (result.finishReason === 'error' && result.inputAccepted) { // The run failed after accepting this turn's input } return messages; }, };
-
Added an optional
getRoutes()method toSignalProvider. Routes it returns are added toserver.apiRoutesautomatically when the agent is registered, so webhook and OAuth routes for a signal provider no longer need manual server config. (#28575)class AcmeSignals extends SignalProvider<'acme'> { readonly id = 'acme' as const; getRoutes() { return [registerApiRoute('/acme/webhook', { method: 'POST', handler: async c => c.json({ ok: true }) })]; } } // Before: server: { apiRoutes: acme.getRoutes() } // After: just add the provider to the agent const agent = new Agent({ /* ... */ signals: [new AcmeSignals()] }); export const mastra = new Mastra({ agents: { agent } });
-
Fixed resumed runs losing the link to the run they continue. When you resume an agent, durable agent or workflow run, its span now sets
resumedFromSpanIdto the span id of the first run. This stays true after several resumes and after a durable agent recovers. A resumed run also keeps the external parent span of the run it continues. (#28849)A resume that passes only
tracingOptions.parentSpanIdnow stays in the original trace under the f...
October 28, 2026
Highlights
End-user (per-resource) connections in @mastra/connect
tools({ resourceId }) and new userConnectRoutes() enable user-scoped OAuth connections so agents can safely use each end user’s accounts (in addition to global connections), with built-in caching and hard enforcement that one user’s connection can’t serve another user.
In-chat “request connection” prompts for missing integrations
Resource-scoped tool resolution now includes a built-in request_connection tool that returns a structured connect-prompt payload (provider list + connection state) so chat UIs can render a connect card when the agent needs an unconnected provider.
Span aggregation API + store support (ClickHouse/Postgres/DuckDB)
New POST /observability/spans/aggregate plus aggregateSpans() in ClickHouse/Postgres/DuckDB enable fast grouped metrics over spans (counts, latency, error rate, tokens, cost) for agent runs, tools, workflows, and more—powering richer Metrics/observability experiences.
Unified, persisted provider-error classification (better retries + better UX)
@mastra/core/error adds classifyProviderError (and related helpers) and server streams now carry provider status/body/requestId/modelId/codes/reason so clients can show consistent, actionable error messages live and after reload; retry logic (StreamErrorRetryProcessor) now aligns with the same classification.
Run status + durable execution control improvements
Thread subscriptions can opt into withRunStatus: true to receive ordered thread-run-status outcomes without closing the subscription, and clients can explicitly cancel durable runs via agent.abortRun(runId) (disconnecting observers no longer aborts durable/evented execution by default).
Breaking Changes
queryTraceThreads()thread rows no longer includemodelorprovider, andpage.limitis now capped at 100 (default 25); larger limits fail validation.@mastra/connectconnect-session creation response changed:POST /connect/:provider/sessionsnow returnslaunchUrl(server-side verified launch route) instead ofconnectUrl.@mastra/code-sdkno longer registers built-in subagents by default; you must passsubagents: [...builtinSubagents]to keep prior behavior.@mastra/playground-uiTracesExplorersorting API changed to a single{ field, direction }object (replacingsortDirection/onSortDirectionChange).
Changelog
@mastra/core@1.80.0
Minor Changes
-
Added
agentModeltodataset.startExperiment()anddataset.startExperimentAsync()so you can run an agent experiment against a different model without editing or versioning the agent. The model is passed with each call the experiment makes: the registered agent, its sub-agents and other experiments running at the same time keep their own models, and the agent version is picked the same way as withoutagentModel. (#28165)await dataset.startExperiment({ targetType: 'agent', targetId: 'support-agent', agentModel: 'anthropic/claude-haiku-5-5', scorers: ['accuracy'], });
The experiment's
metadata.agentModeland each run's trace metadata record the model (for exampleanthropic/claude-haiku-5-5), so runs on different models can be told apart.agentModelworks only with agent targets and is rejected before an experiment record is created otherwise. -
Channel
formatErrornow receives the failed run's request context as a second argument, so the message posted to Slack, Discord, Teams or Telegram can link back to whatever the run belongs to. Formatters that take only the error keep working. When a stream fails with a raw provider payload instead of anError, the error passed toformatErrorkeeps that payload as itscause, so the formatter can still tell what failed. (#27811)channels: { adapters: { slack: { adapter: createSlackAdapter(), formatError: (error, { requestContext }) => { const ticketId = requestContext?.get('ticketId'); return ticketId ? `❌ ${error.message} (ticket ${ticketId})` : `❌ ${error.message}`; }, }, }, },
-
Added
classifyProviderError,providerErrorReasonandtoPersistedErrorIdentityto@mastra/core/error: one reader for model-provider failures (authentication, quota, rate limit, context overflow, rejected model or request, timeout, outage) that takes HTTP status and provider codes before wording. A status from the GitHub API or a storage call is not mistaken for the model provider's.providerRequestIdandproviderModelIdreturn the provider's request id and the model the failed request named, also from a wrapped cause, andPROVIDER_CALL_ERROR_NAMEmatches the name of the AI SDK's provider call error. (#28496)Saved error parts now also keep every provider code as
codesand the provider'sreasonsentence, so a reloaded thread classifies like a live one, including a failure the provider reported mid-stream.toPersistedErrorIdentity(error)returns that saved shape, so anything that sends an error to a client can send the same one. Thereasoncomes from the provider's response, including a stringdetailfield and a response wrapped as another error's cause; any other cause stays out of thread history, and a long reason is cut at 2,000 characters.Each kind names a recovery action. New kinds can arrive in minor releases, and no kind is renamed or removed within 1.x, so keep a default branch when you switch on one:
import { classifyProviderError } from '@mastra/core/error'; switch (classifyProviderError(error)) { case 'rate_limited': // wait, then retry break; case 'quota': // ask for more credit break; default: // show the provider's reason }
-
@mastra/core/workspacenow exportsgetMimeType, the extension-based lookupLocalFilesystemuses, so custom filesystem providers can fill inFileStat.mimeTypethe same way. (#28302)import { getMimeType } from '@mastra/core/workspace'; getMimeType('page.png'); // 'image/png' getMimeType('notes.unknown'); // 'application/octet-stream'
-
Fixed stored span previews to preserve intentionally empty strings instead of replacing them with payload content. (#28442)
Added
buildSpanPreviews()to generate input, output, and error previews from a processed span. Support is advertised by theobservability-span-previewscore feature flag.import { buildSpanPreviews } from '@mastra/core/observability'; const previews = buildSpanPreviews(span);
-
Added render-only tools that stream display calls and let agents and sub-agents continue without a client result. Use
renderOnly: truewhen the browser only needs to show the call: (#28082)import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const showCard = createTool({ id: 'show-card', description: 'Display a product card', inputSchema: z.object({ productId: z.string() }), renderOnly: true, });
-
Span records now carry nine native fields in their own columns:
externalParentSpanId,resumedFromSpanId,model,provider,outcome,stopReason,stopReasonContext,timeToFirstTokenMsanderrorCount. Exported spans can supply them, the storage exporters copy them as given, and a field the span does not set is stored asnull. This release only adds the fields; later releases fill them in. (#28389) -
Added an opt-in
onMessagechannel handler for messages that do not mention the bot. CalldefaultHandlerto run the agent, post its reply, and subscribe the thread. Unmentioned messages stay ignored when the handler is omitted orfalse. (#28285)import type { ChannelHandlers } from '@mastra/core/channels'; const handlers: ChannelHandlers = { onMessage: async (thread, message, defaultHandler) => { if (thread.channelId !== 'slack:C_SUPPORT') return; await defaultHandler(thread, message); }, };
Runs started through
onMessagereportchannel.eventTypeasmessage. Existing event types are unchanged. When a queued batch mixes mentioned and unmentioned senders, unmentioned senders go toonMessageonly if it is set. Otherwise they still reachonMention, as before.Fixes #27060.
-
Added a
textDisplaychannel adapter option. SettextDisplay: 'hidden'to stop posting the agent's reply text, for example when the agent replies through a tool you give it. Tool display, approval prompts, errors and typing status still show, and the text still reaches the agent's stream and memory. (#28067)channels: { adapters: { slack: { adapter: createSlackAdapter(), textDisplay: 'hidden', }, }, },
-
Agent run spans now show the
modelandproviderthe run was configured with, on regular, durable and evented agents, including resumed runs. Model step spans gained the same two fields for the model each step actually called. (#28485) -
Added opt-in shared-channel tool approvals while keeping requester-only approval as the default. Configure
new SlackProvider({ toolApprovers: 'anyone' })to allow channel members to approve or deny pending tools. (#28542)Also in: @mastra/slack@1.8.0
-
Added
get(pid, { release: false })to the sandbox process manager. It reads a process without dismissing it once it has exited, so one reader can look at a finished command's output without taking it from another, such as the agent that started it. The default is unchanged. (#27919)// Show a finished command's output without dismissing it con...
October 21, 2026
Highlights
MCP Events (poll/stream/webhooks + Studio inspection)
Experimental MCP Events (SEP-3415 draft) let MCP servers publish event streams that clients can poll, keep a stream open for, or receive via signed webhooks. Core/server/client add inspection APIs so Studio can list event types, stream occurrences, and test-emit events under mastra dev.
Observability Upgrades: Span Aggregation + “Time to First Chunk” + Richer Trace/Span Querying
New aggregateSpans() storage contract enables per-span analytics (e.g., p95 latency per tool, token/cost per agent) via the span-aggregate feature. Observability queries gain timeToFirstChunkMs fields, filtering, and sorting, plus expanded trace-scope fields (root entityId, root name, root spanType) and richer querySpans() context/parent/root-entity filters (with updated store capabilities to detect support).
Agent Controller Sessions Move to Session IDs (Multi-session per resource)
Sessions are now addressed by sessionId (not resourceId + scope) across core, server, and client-js, enabling many sessions per resource while keeping resourceId as the memory/thread owner key. New APIs include controller.getSession(sessionId), updated createSession()/deleteSession() signatures, and server routes under /agent-controller/:controllerId/sessions/:sessionId/....
Sandboxes Get a Native Filesystem API (Faster workspace operations)
WorkspaceSandbox can expose an optional filesystem for one-roundtrip reads/writes/listings instead of shelling out per operation. This lands across providers (e.g., E2B/PlatformSandbox) and is adopted by SandboxFilesystem in @mastra/code-sdk for faster file operations.
End-User Scoped Connect Integrations + Teams Channel
@mastra/connect adds per-resource (end-user) connection scoping for tools/credentials, preventing one user’s connections from serving another user’s requests, plus userConnectRoutes() to let authenticated end users connect their own accounts. Connect also adds Microsoft Teams as a supported channel.
Breaking Changes
- Agent controller sessions are keyed by
sessionId(scope removed): core/controller APIs, server routes, and client-jscontroller.session()/session.create()signatures updated. - Thread-row query contract changed: use
timeRange.traces/timeRange.threadsinstead oftraces.timeRange. querySpans()contract changed: removedmodel/providerfilters and droppedcost,inputTruncated,outputTruncated,errorTruncatedfrom responses (requires updated storage adapters).- Channel
resolveResourceId/resolveThreadIdcontexts now requirerequestContext(tests/manual calls must pass it).
Changelog
@mastra/core@1.79.0
Minor Changes
-
Breaking: Agent controller sessions are now addressed by their session id. Many sessions can share one
resourceId, soresourceIdonly says whose threads and memory a session works on. (#27336)- Added
controller.getSession(sessionId). Removedcontroller.getSessionByResource(resourceId, scope). - Removed the
scopeoption fromcreateSession(). Give each session its ownidinstead. Whenidis omitted it defaults to theresourceId, so existing one-session-per-resource code keeps working. deleteSession()takes{ sessionId }, andgenerateThreadTitle()takes an optionalsessionId.- The controller request context exposes
sessionIdnext toresourceId.
// Before await controller.createSession({ resourceId: 'repo-1', scope: 'run-a' }); const session = await controller.getSessionByResource('repo-1', 'run-a'); await controller.deleteSession({ resourceId: 'repo-1', scope: 'run-a' }); // After await controller.createSession({ id: 'run-a', resourceId: 'repo-1' }); const session = await controller.getSession('run-a'); await controller.deleteSession({ sessionId: 'run-a' });
createSessionthrows when an explicitresourceIddoesn't match the resource of the live session with that id, so a session id can't be used to take over another resource's session. - Added
-
aggregateTraces()can now group by the root span'sentityId, and trace queries and aggregates acceptentityIdas a trace-scopewherefield. Use it to split traces that have noentityName(for example, scorer runs recorded before scorers set one) by the primitive that produced them, then drill into a group by filtering on the sameentityId. (#27684)const { rows } = await client.aggregateTraces({ timeRange, where: { op: 'notExists', path: 'entityName' }, groupBy: ['entityId'], measures: ['count'], });
Trace-scope
entityIdreads the root span only. Insidespans.someandspans.none,entityIdstill matches any span in the trace. Stores that support the trace-scope field declare the newtrace-query-root-entity-idstorage feature.Also in: @mastra/clickhouse@1.26.0, @mastra/duckdb@1.15.0, @mastra/pg@1.32.0
-
Added the storage contract for span aggregation. Storage adapters can implement
aggregateSpans()and advertise thespan-aggregatefeature to answer questions like "p95 latency per tool" or "token spend per calling agent" over individual spans. (#27669)class MyObservabilityStore extends ObservabilityStorage { getFeatures(): readonly ObservabilityStorageFeature[] { return [...(super.getFeatures() ?? []), 'span-aggregate']; } // `plan` is already validated: tool calls grouped by name, with count and p95 duration async aggregateSpans(plan: TrustedSpanAggregatePlan): Promise<SpanAggregateResponse> { // e.g. { rows: [{ dimensions: { name: 'search' }, measures: { count: 42, 'duration.p95': 812 } }], truncated: false } } }
Span aggregates use the same request shape, measures, and limits as trace aggregates. Filters follow
querySpans(), so counts match the spansquerySpans()returns. Spans can be grouped by name, span type, and their own, parent, or root entity. Grouping by model or provider is not supported yet. Token and cost measures count usage on the span that recorded it. Adapters without the feature reject the call as not implemented. -
Fixed
DurableAgent.observe(runId)when attaching to a run from another process. (#26906)- Restored the agent's configured output processors, request context, and
returnScorerDatasetting, including when the observing process also runs durable steps. - Preserved live call-time processors, request context, and scorer data when another wrapper observes the run within the same Mastra instance.
- Behavior change: Cross-process
DurableAgent.observe(runId)now throwsDURABLE_AGENT_OBSERVE_AGENT_MISMATCHwhen the persisted run belongs to an agent with a different ID, matchingresume()andrecover(). Use the same agent ID in the observing process as in the process that started the run. - Made durable stream
onChunkcallbacks run serially and in order. - Made
onFinishwait for pendingonChunkcallbacks to finish, without waiting for transport acknowledgements.
Before: An observing process could use a different agent ID to attach to a run started by
researcher:const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher-observer' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId);
After: Use the starting agent's ID in the observing process. The previous configuration now throws
DURABLE_AGENT_OBSERVE_AGENT_MISMATCH:const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId);
- Restored the agent's configured output processors, request context, and
-
Sandboxes can now expose their own disk through an optional
filesystemproperty onWorkspaceSandbox. A provider with a file API answers reads, writes, and listings in one request each instead of a shell command per step. Paths are absolute inside the sandbox, and the provider decides containment. Fall back toexecuteCommand()when the property is absent: (#27859)const pkg = sandbox.filesystem ? await sandbox.filesystem.readFile('/workspace/app/package.json', { encoding: 'utf-8' }) : (await sandbox.executeCommand?.('cat', ['/workspace/app/package.json']))?.stdout;
-
Added batch experiment ID filtering to observability metric queries, so you can get token usage and cost for many experiments in one request. (#27255)
const result = await client.getMetricBreakdown({ name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'], aggregation: 'sum', groupBy: ['experimentId', 'name'], filters: { experimentIds: ['experiment-1', 'experiment-2'] }, });
Also in: @mastra/clickhouse@1.26.0, @mastra/client-js@1.56.0, @mastra/duckdb@1.15.0, @mastra/pg@1.32.0, @mastra/spanner@1.10.0
-
session.thread.transferOwnershipnow accepts an optionalthreadId, so you can transfer a thread without switching the session to it first. WithoutthreadIdit still transfers the active thread. (#27043)await session.thread.transferOwnership({ toOwnerId: 'user-456', threadId: 'thread-123' });
-
Channel
resolveResourceIdandresolveThreadIdhooks now receive the per-messagerequestContext. Values a channel handler sets before callingdefaultHandler, such as a tenant id, can be read directly in the resolvers instead of being looked up a second time. (#27436)channels: { handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.requestContext.set('t...
October 15, 2026
Highlights
Agent Scopes (multi-tenant identity + safer memory/thread routing)
Agents now support scopes (<type>:<value>) passed per call, configured on the agent, or injected by trusted middleware via MASTRA_SCOPES_KEY; Mastra merges all three. Memory can derive resource and thread from resource:/thread: scopes (with conflicts now returning HTTP 400), nested calls can choose their own thread, and resumed runs preserve their original scopes (no adding new ones).
Trace Aggregation API (server + client + React)
Mastra adds trace aggregation end-to-end: POST /observability/traces/aggregate is discoverable via GET /observability/capabilities (traceAggregate), clients get mastraClient.aggregateTraces(), and React adds useTraceAggregateAvailable(). Field discovery now returns an aggregate schema describing valid group-by dimensions and measures.
Typed Signal Queueing for Agents (queueSignal)
You can now queue a typed signal behind the active run—without degrading it into a plain user message—via Agent.queueSignal() / session support and the new server route POST /agents/:agentId/queue-signal. Runs triggered by a signal also record signal metadata on the AGENT_RUN span for better traceability.
Faster, more usable trace lists with stored input/output/error previews
Trace and span list APIs now return outputPreview and errorPreview (and spans add errorTruncated), backed by a shared preview format in @mastra/core/storage (buildPreview, PREVIEW_MAX_LENGTH). Stores (ClickHouse/Postgres/DuckDB) persist previews at write time so queryTraces()/querySpans() no longer need to read full payloads—improving latency, memory use, and enabling UIs like Playground’s new Traces “Output” column.
Better reliability for long-running & durable workflows
Workflow execution is more robust: .foreach() failures now correctly fail the run (instead of hanging or corrupting output), durable agents are correctly honored inside workflow steps across engines, resume behavior preserves suspendData after crashes, and invalid resume data now throws AGENT_RESUME_INVALID_DATA without consuming the suspended run.
Breaking Changes
buildInputPreview()andINPUT_PREVIEW_MAX_LENGTHare deprecated in favor ofbuildPreview()andPREVIEW_MAX_LENGTH.- Factory sessions no longer have modes (Plan/Explore removed); thinking defaults and related config endpoints now use a single global default, and
PUT /web/config/thinkingrejectsmodeDefaults. - Factory credential behavior changed: cross-scope credential fallback is removed; custom credential providers must move from
orgFirstto explicitcredentialScope/ policy resolution.
Changelog
@mastra/core@1.78.0
Minor Changes
-
Added
scopesto agents: the addresses a run acts as, written as<type>:<value>(for exampleorg:acme,resource:user-123,thread:conversation-123). (#26593)Setting scopes. Pass them per call on
stream,generate,network,sendMessage,queueMessageandsendSignal, set them on the agent, or set them from trusted middleware with the newMASTRA_SCOPES_KEYrequest context key. Mastra combines all three. Read an agent's configured scopes withagent.getScopes().Memory. Memory reads its resource and thread from
resource:andthread:scopes, in addition tomemory.resource,memory.threadand the existing request context keys. If they name different values, the call throws a 400 error instead of silently picking one. Runs with no scopes from any source behave as before.const agent = new Agent({ id: 'support', model: 'openai/gpt-5-mini', memory: new Memory(), scopes: ({ requestContext }) => { // Set by your auth middleware. Request bodies can fill unreserved keys, so only read trusted ones. const orgId = requestContext.get('org-id'); return orgId ? [`org:${orgId}`] : []; }, }); // Before await agent.generate('Hi', { memory: { resource: 'user-123', thread: 'conversation-123' } }); // After await agent.generate('Hi', { scopes: ['resource:user-123', 'thread:conversation-123'] });
Nested calls. Tools, processors and subagents see the run's other scopes (for example
org:acme) underMASTRA_SCOPES_KEY, but not itsresource:andthread:scopes, so nested agent calls can choose their own thread.Suspended runs. Suspended runs keep their scopes when resumed: a resume may repeat some or all of them but cannot add new ones.
Upgrading. Upgrade
@mastra/servertogether with this release. While an older server is registered, agents refuse scopes from the call and the request context with anAGENT_SCOPES_SERVER_OUTDATEDerror (HTTP 400); the agent's ownscopesoption still applies. This detection relies on the server registering itself with Mastra, which@mastra/serverhas done since1.0.0-beta.5. Servers older than that — and custom server adapters that never callmastra.setMastraServer()— are indistinguishable from no server at all, so they are not detected: upgrade them rather than relying on the guard.Runs suspended before this release have no recorded scope set; resuming one adopts the scopes supplied at resume time (the same trust checks as a fresh run apply) instead of rejecting them. Runs suspended after the upgrade record their set — including the empty set — and a resume may not add to it.
-
Added
traceAggregateDiscoverySchemaand an optionalaggregatefield on the trace-query fields response, describing the dimensions and measuresaggregateTraces()accepts. (#27185) -
Added
queueSignal()toAgentand AgentControllerSession. It queues a typed signal behind the active run likequeueMessage(), but keeps the signal type, tag, and attributes instead of turning it into a plain user message. (#27081)agent.queueSignal( { type: 'reactive', tagName: 'skill', attributes: { name: 'triage' }, contents: 'Triage the new issue.' }, { resourceId: 'user-123', threadId: 'thread-abc' }, );
Runs started by a signal now record it on the
AGENT_RUNspan asattributes.signal(id,type,tagName,attributes), so traces show which signal started a run. -
Memory can now contribute error processors to an agent.
MastraMemory.getErrorProcessors()works likegetInputProcessors()andgetOutputProcessors(): the agent appends what it returns to its resolvederrorProcessors, on both regular and durable agents. These processors run after configured and default error processors, and stay in place when a call passes its ownerrorProcessors. The base implementation returns none, so existing memory classes are unaffected. (#26029)class MyMemory extends MastraMemory { async getErrorProcessors() { return [new MyRecoveryProcessor()]; } }
-
Added one preview format for span input, output and error. Trace lists (
queryTraces(),listTracesLight()) now returnoutputPreviewanderrorPreviewnext toinputPreview, andquerySpans()rows adderrorPreviewanderrorTruncated. (#26567)New exports from
@mastra/core/storagebuildPreview(value)builds a preview from a decoded payload, andbuildPreviewFromJson(json)from stored JSON text. Both cut toPREVIEW_MAX_LENGTH(100) characters and end with…when cut.- Exported spans and span records accept optional
inputPreview,outputPreviewanderrorPreview, preserved through create/update record builders. Stores trim nonblank supplied previews and cap them like generated ones (100 characters plus…, never splitting a surrogate pair); missing or blank previews are generated from the payload.
import { buildPreview } from '@mastra/core/storage'; buildPreview({ messages: [{ role: 'user', content: 'Where is my order?' }] }); // 'Where is my order?' buildPreview({ message: 'Tool failed' }); // 'Tool failed'
Behavior changes
- For a message list, previews now show the last user message instead of every user message joined with
|. querySpans()previews are now at most 100 characters of readable text plus an ellipsis instead of the first 256 characters of raw JSON.
buildInputPreview()andINPUT_PREVIEW_MAX_LENGTHare deprecated. UsebuildPreview()andPREVIEW_MAX_LENGTH.buildInputPreview(input, maxLength)still caps its preview atmaxLength. -
Added a
beforeApprovalhook to tools that require approval. It runs before the agent pauses for approval and can reject the call by returning{ proceed: false, output }. The agent then skips the approval pause and does not execute the tool, andoutputgoes back to the model as the tool result. Invalid calls are returned to the model to fix, and people are only asked to approve valid ones. (#26927)const issueRefund = createTool({ id: 'issue-refund', inputSchema: z.object({ orderId: z.string(), amount: z.number() }), requireApproval: true, beforeApproval: async ({ orderId, amount }) => { const order = await orders.find(orderId); if (!order) return { proceed: false, output: { error: `Order ${orderId} not found` } }; if (amount > order.paid) return { proceed: false, output: { error: `Amount exceeds ${order.paid}` } }; }, execute: async ({ orderId, amount }) => refunds.issue(orderId, amount), });
The hook does not run again when the call resumes after an approval decision. If the hook throws, the error is logged and the call proceeds to the approval gate. Keep validating inside
executetoo, because state can change while the call is waiting for approval. -
Removed the 31-day limit on trace search time ranges. You can now search all of...
October 14, 2026
Highlights
Factory Sandbox Provider Contract + New Providers (Docker/E2B/Platform)
Mastra Factory now has a first-class FactorySandbox contract for sandbox providers, enabling tunable settings schemas, repository templates, and prebuilt environment builds. New *FactorySandbox implementations ship for Docker (@mastra/docker), E2B (@mastra/e2b), and Mastra Platform (@mastra/platform-workspace) to plug in with a single provider instance.
Factory Environment Builds + Multi-Repo Sessions
Factory can now build environment templates ahead of sessions (manual, scheduled cron, or push-triggered builds) when the sandbox supports builds, reducing first-session cold starts. Sessions also boot every repository in the environment (ordered, shared workspace root) and can push/open change requests across any environment repo via source_control_* tools with a repository slug.
Durable Agent Safety: Run Fencing Across Storage Backends
Multiple storage adapters (Cloudflare DO/D1, Convex, DynamoDB, DSQL, MongoDB, MSSQL, MySQL, OracleDB, Spanner, Redis/Upstash/Valkey) now support “run fencing” so a durable execution that lost ownership (e.g., crash recovery) can’t overwrite the recovered run’s threads/messages/workflow state—significantly improving correctness in distributed deployments.
Observational Memory Concurrency + New Conflict-Aware Storage Writes
A large set of fixes harden Observational Memory against overlapping buffering/activation/reflection across processes, adding “generation” protection (supersededBy) and new write-result APIs like commitActiveObservations({ applied, reason }) and appendBufferedObservations({ persisted, recordId }). Observational memory also blocks less often (blockAfter band), preserves correct ordering, and improves reflection fidelity for long-running threads.
Trace Linking & Cross-System Observability (Spans + MCP)
Spans now support links (including W3C traceparent helpers) and exporters propagate them (Datadog/Sentry/OTel bridge/exporter), enabling correlation across traces without joining them. @mastra/mcp additionally links client tool-call spans to server request spans bidirectionally, making it easy to navigate between agent traces and MCP server traces.
Breaking Changes
- Factory projects now support provider-instance sandboxes (
MastraFactoryConfig.sandbox) for settings/builds; existing callback form still works but lacks these capabilities. @mastra/serverworkflow HTTP routes no longer allow starting/resuming internal workflows (returns 404); durable agents must be invoked via agent routes (e.g.,POST /agents/:agentId/stream).- AgentController thread/session behavior changes include new suspension “address” claiming APIs and deprecations:
SessionRecord.modeId/modelId/state/pending,HarnessStorage.appendPendingItem/updatePendingItem/removePendingItem, andSessionSuspensions.resolveToolCallId(). @mastra/playground-ui:Dialog/AlertDialogremoved handle/render-style props; components are now controlled viaopen/onOpenChange.@mastra/client-js/@mastra/reactcompare-experiments APIs deprecateexperimentIdA/experimentIdBin favor ofexperimentIds(+ optionalbaselineId).
Changelog
@mastra/core@1.77.0
Minor Changes
-
AgentController
steerandfollowUpnow accept file attachments, matchingsendMessage. Attached files reach the model the same way whether the message is sent right away or queued behind an active run. (#26909)await session.followUp({ content: 'Then compare against this screenshot.', files: [{ data: screenshotBase64, mediaType: 'image/png', filename: 'expected.png' }], });
The
/steerand/follow-uproutes accept the samefilesfield and limits as/messages.Also in: @mastra/client-js@1.54.0, @mastra/server@1.77.0
-
Added
FactorySandbox, the contract a host implements to plug a sandbox provider into Mastra Factory. The bare callback that builds oneMastraSandboxper session still works, but it gives a provider no way to expose tunable settings, a prebuilt repository template or template builds. AFactorySandboxowns all four, and lives in@mastra/core/workspaceso provider packages implement it without depending on@mastra/factory. (#26467)Required:
providerandcreate, which returns the session'sMastraSandbox.Optional:
settings, a schema of what a user can tune (zod, JSON Schema or Standard Schema; every field optional, absent means the provider default; no settings when omitted);templatereturns the environment's repository template for a context and settings;buildsstarts, reads and lists template builds ahead of any session.import { FactorySandbox } from '@mastra/core/workspace'; import type { FactorySandboxContext } from '@mastra/core/workspace'; import type { PublicSchema } from '@mastra/core/schema'; interface Settings { region?: 'us' | 'eu'; } class MyFactorySandbox extends FactorySandbox<Settings> { readonly provider = 'my-cloud'; readonly settings: PublicSchema<Settings> = { type: 'object', properties: { region: { type: 'string', enum: ['us', 'eu'], default: 'us' } }, additionalProperties: false, }; create(ctx: FactorySandboxContext, settings: Settings) { return new MyCloudSandbox({ id: ctx.sessionId, sandboxId: ctx.sandboxId, template: this.template(ctx, settings), workingDirectory: ctx.workingDirectory, region: settings.region ?? 'us', }); } // Optional: the sandbox template image Factory can build ahead of time template(ctx: FactorySandboxContext, settings: Settings) { return myCloudTemplate(ctx, settings); } // Optional: build that template ahead of time and report on it readonly builds = { start: async (ctx: FactorySandboxContext, settings: Settings) => myCloud.startBuild(this.template(ctx, settings)), get: async (ctx: FactorySandboxContext, settings: Settings, buildId: string) => myCloud.buildStatus(buildId), list: async (ctx: FactorySandboxContext, settings: Settings) => myCloud.builds(this.template(ctx, settings).name), }; }
-
Added authenticated message authors to AgentController sessions. Pass
authortosendMessage,queueMessage, orsteer, and read it back withgetMessageAuthor. (#26549)await session.sendMessage({ content: 'Hi', author: { id: 'user-123' } }); const messages = await session.thread.listMessages({ threadId: session.thread.requireId() }); const message = messages.findLast(message => message.role === 'signal')!; const author = getMessageAuthor(message);
-
Added an optional
pricingfield to gateway provider configs. A custom gateway can now state what its models cost, in USD per 1 million tokens with the same keys as models.dev, so calls to private or reseller models get cost estimates. (#26875)async fetchProviders() { return { 'my-provider': { name: 'My Provider', models: ['model-1'], apiKeyEnvVar: 'MY_API_KEY', gateway: 'private', url: 'https://api.myprovider.com/v1', pricing: { 'model-1': { input: 0.5, output: 1.5 } }, }, }; }
-
Added
linksto spans: references to spans in other traces. Passlinkswhen starting a span to relate it to a span in another trace without joining that trace. Mastra storage saves the links with the span. (#26059)const span = getOrCreateSpan({ type: SpanType.GENERIC, name: 'handle request', links: [{ traceId: callerTraceId, spanId: callerSpanId }], mastra, });
Also added
parseTraceparent()andformatTraceparent()to@mastra/core/observabilityfor W3Ctraceparentvalues. -
Fixed observational memory losing context when a reflection, a buffered-observation write, and an observation activation overlap. The in-memory store now keeps buffered observations across a reflection, never lets activation drop a chunk written by someone else or move the observation cursor backward, and refuses observational memory lifecycle writes (buffered-observation appends, activation, active-observation commits, reflections) to a superseded generation. (#25906)
Storage gained two methods that report the outcome of a write, plus optional inputs, so callers can detect these cases:
commitActiveObservationswrites active observations and returns{ applied, reason }. It acceptsexpectedActiveObservationsand never writes to a superseded generation.appendBufferedObservationsappends a buffered chunk to the current generation and returns{ persisted, recordId }.createReflectionGenerationandswapBufferedReflectionToActiveacceptnewRecordId; the reflection applied when the returned record has that id.swapBufferedToActivereportsretired.- Observational memory records carry
supersededBy, set when a newer generation replaces them.
Before:
const memory = await storage.getStore('memory'); await memory.updateActiveObservations({ id: record.id, observations, tokenCount, lastObservedAt: new Date(), });
After:
const memory = await storage.getStore('memory'); const result = await memory.commitActiveObservations({ id: record.id, observations, tokenCount, lastObservedAt: new Date(), expectedActiveObservations: record.activeObservations, }); if (!result.applied) { // 'retired': a newer generation replaced this record. 'conflict': another writer changed it first. // Read the current record and observe again. }
...
October 11, 2026
Highlights
Durable agent run ownership + storage-level run fencing
Crash recovery is now safe across multiple instances: runs are claimed by the execution driving them, recovery skips live-claimed runs (or can force take over), and storage adapters can enforce “run fences” to reject writes from an execution that lost ownership—preventing message/thread/workflow state corruption.
Atomic working-memory merges on PostgreSQL (and explicit merge mode over HTTP)
Concurrent schema-based working memory updates no longer stomp each other in Postgres: with scope: 'resource', partial updates are deep-merged inside a row-locked transaction. You can also explicitly mode: 'merge' via the server/client APIs, and unsupported adapters now fail with a clear 400 instead of silently replacing records.
File-handling resilience + sandbox-first file workflows
Agents no longer get “stuck” when a provider rejects an attached file: the new default UnsupportedFileHandler retries with the file removed (or replaced by a sandbox path when available). For file-centric tool workflows, the new FileUploadProcessor can upload eligible user files into the workspace sandbox and pass the model a sandbox path instead of raw file content.
Observational Memory now supports native model: 'auto' (new default)
Observer/Reflector model selection can follow the active main model/provider automatically per call, while explicit model IDs stay pinned. This is now the default for OM (model, observation.model, reflection.model), with hooks (autoModels, resolveModel, resolveAutoModelId) to control how “auto” resolves in your environment.
New observability nesting + richer trace search
tracingOptions.nestUnderParent (and nestUnderRun()) lets you attach a run as a true child span under an earlier run in the same trace (e.g., LLM judges), preserving the parent run’s trace-level identity. Trace querying also gains word/phrase matches / notMatches operators (across stores/types), and metrics add a usageId to group token/cost rows per model call.
Breaking Changes
@mastra/playground-ui: old Metrics card views/components were removed in favor of the new self-contained Metrics cards/dashboard building blocks.@mastra/playground-ui:MetricsCardGroupno longer acceptsvariant;MetricsLineChartdroppedxAxisInterval/xAxisMinTickGap.@mastra/memoryObservational Memory defaults changed: OM now defaults tomodel: 'auto'(instead ofgoogle/gemini-2.5-flash), which may select a different provider/model depending on available credentials.
Changelog
@mastra/core@1.76.0
Minor Changes
-
Concurrent schema-based working memory updates no longer lose each other's fields when using PostgreSQL. With
scope: 'resource', the update tool now deep-merges partial updates inside a row-locked transaction, so writers in separate processes that change different fields all keep their changes. Other storage adapters keep the existing in-process behavior. (#25848)You can also merge from your own code or over HTTP:
await memory.mergeWorkingMemory({ threadId, resourceId, workingMemory: { city: "Berlin" } }); await client.updateWorkingMemory({ agentId, threadId, resourceId, workingMemory: JSON.stringify({ city: "Berlin" }), mode: "merge" });
Merges are rejected (HTTP
400from the server) when the storage adapter doesn't support atomic merges, instead of silently replacing the record.Also in: @mastra/client-js@1.53.0, @mastra/memory@1.37.0, @mastra/pg@1.31.0, @mastra/server@1.76.0
-
processLLMRequestnow receives the workspace of the step, the one the agent's tools use, so a processor can work with the agent's sandbox or filesystem before a model call. (#25814)const processor: Processor = { id: "my-processor", async processLLMRequest({ prompt, workspace }) { if (workspace?.hasSandboxConfig()) { // ... } } };
-
Added
threadMetadatato theonDelegationStartreturn value so delegated sub-agent threads can carry metadata such as a tenant ID. The metadata is applied when the sub-agent thread is created, which makes those threads findable with metadata-filtered queries. (#25200)await supervisor.generate("Summarize the report", { memory: { thread: "parent-thread", resource: "user-1" }, delegation: { onDelegationStart: () => ({ threadMetadata: { tenantId: "acme" } }) } }); await memory.listThreads({ filter: { metadata: { tenantId: "acme" } } });
-
Fixed durable agent crash recovery re-running runs that were still executing. Recovery restarted every run left in the
runningstate, even when another process was still driving it. Both executions then wrote to the same thread, and the original one could overwrite the recovered assistant messages (#23734). (#26418)Each run is now claimed by the execution driving it:
stream(),generate(),resume(), orrecover(). The claim is renewed while the run executes and lapses about 30 seconds after its process dies.recoverActiveRuns()skips runs whose claim is still live. Skipped runs are returned withstatus: 'skipped', areason, and, for a run live in another process, aretryAttime.mastra.recoverAllDurableAgents()counts skipped runs in a newskippedfield and checks each one again once its claim could have lapsed, so a run whose process crashed is still recovered.recover()throwsDURABLE_AGENT_RUN_ACTIVEfor a live run. Passforce: trueto take the run over. The execution that held it stops.recover()throwsDURABLE_AGENT_RECOVER_RUN_ACTIVE_LOCALLYfor a run the same process is still executing, even withforce: true. Previously it started a second execution of the run. Wait for the run to finish instead.- Starting a run with a
runIdthat another execution holds waits up to 5 seconds, then throwsDURABLE_AGENT_EXECUTION_CONFLICT. mastra.shutdown()releases runs still executing after the drain window, so the next boot recovers them right away.- Run streams drop events from an execution that lost its run. With a storage adapter that supports run fencing, that execution's writes are also rejected, so it can't overwrite messages, threads, or workflow state. A warning is logged when the workflows or memory store doesn't support run fencing: whenever runs are recovered, through
recoverActiveRuns()orrecover(), and, withrecovery: { durableAgents: 'auto' }, when a run starts.
const { recovered } = await durableAgent.recoverActiveRuns(); for (const run of recovered) { if (run.status === "skipped" && run.reason === "run-active") { console.log(`${run.runId} is still running. Check again at ${new Date(run.retryAt!)}`); } } // Take a run over even though another execution still holds it const stream = await durableAgent.recover(runId, { force: true });
See Run ownership for how claims are enforced and how recovery works across multiple instances.
-
Added
tracingOptions.nestUnderParentto attach a run as a child of an earlier run in the same trace, for example an LLM judge under the run it scores. The trace then keeps the name, input, output, and tags of the run that started it. The newnestUnderRun()helper builds these options from the earlier run's result. (#25691)import { nestUnderRun } from "@mastra/core/observability"; const turn = await assistant.generate("When does my order ship?"); await judge.generate("Grade this answer", { tracingOptions: nestUnderRun(turn) // same as: { traceId: turn.traceId, parentSpanId: turn.spanId, nestUnderParent: true } });
Runs that pass
parentSpanIdwithout the new option behave as before. -
Added
getModelReasoningOptionsto read which reasoning controls a model accepts, as published by models.dev: named effort levels, a thinking-token budget, or an on/off toggle. Returns[]when models.dev lists the model without reasoning controls, andundefinedwhen no data describes the model. (#26225)import { getModelReasoningOptions } from "@mastra/core/llm"; getModelReasoningOptions("anthropic/claude-sonnet-4-6"); // [{ type: 'effort', values: ['low', 'medium', 'high', 'max'] }, { type: 'budget_tokens', min: 1024 }] getModelReasoningOptions("anthropic/claude-haiku-4-5"); // [{ type: 'budget_tokens', min: 1024 }]
Use it to offer only the reasoning levels a model supports instead of hard-coding them per model.
-
Agents now keep working when the model rejects a file the user sent. Before, the model call failed, and since the file stayed in the thread, every later turn failed the same way, even a text-only one. (#25814)
A new default error processor,
UnsupportedFileHandler, takes the rejected file out of the prompt and calls the model again. When the agent's workspace has a sandbox, the file is uploaded there and the model gets its path, so the agent can still work on it with its tools. Otherwise, the model gets the[Attachment unavailable: <name>]placeholder Mastra already uses for attachments it can't use. Only the prompt changes: the stored message keeps the file, so a thread that already holds such a file works again on its next turn.It handles the refusals that provider SDKs, such as OpenAI's and Anthropic's, raise before the request goes out, and the errors a provider returns over HTTP for a request that holds such a file, like Gemini's
502. A rate limit or another error that asks to try again later keeps the file: the request is sent again unchanged. Every agent gets it, along...
October 7, 2026
Highlights
Span Query API (list completed spans across traces)
New core/server/client support lets you query individual completed spans (with filters, cursors, previews, and model cost) via storage.querySpans() / client.querySpans() / POST /api/observability/spans/query, enabling workflows like “show every failed tool_call in the last hour” without first finding traces.
Trace Aggregation with token + cost analytics (now across stores)
aggregateTraces() now supports token and cost measures (tokens.* sums/avgs and cost.sum/cost.avg with coverage + currency handling), and is implemented in the ClickHouse, DuckDB, and Postgres observability stores—making cost/tokens-first dashboards and “top spenders” queries a first-class capability.
Self-embedding vector stores for Semantic Recall (no client embedder required)
Semantic recall can now run against vector stores that generate embeddings themselves (MastraVector.isSelfEmbedding), including MongoDBVector via autoEmbed, so memory recall and message writes work without configuring a client-side embedder.
@mastra/connect 1.0: new channels/providers capabilities (Teams, Discord credential security) + better observability
@mastra/connect reaches stable with a reworked provider API (providers allowlists, glob filters, top-level defaults, dynamic tool merging), adds Microsoft Teams to channels(), moves Discord to a single API-key bot-token credential stored in encrypted credentials, and now records connect tool calls as trace spans for end-to-end observability.
Sessions now persist one active model per thread + optional “no initial thread”
AgentController sessions now keep a single current model across mode switches (updated switch APIs and thinking-level persistence), and you can start sessions without creating a thread (createInitialThread: false / createSession({ createInitialThread: false }) + session.thread.ensureId()), reducing empty-thread clutter.
Breaking Changes
@mastra/connect@1.0.0: discovered MCP tools no longer require approval by default; userequireApproval(replacesautoApproveTools) to opt in.@mastra/connect: API renameintegrations→providers; removed legacy surfaces like theconnect()alias oftools(),environment()sandbox credential surface, and several resolver helpers/exports.- Slack channel integration id changed in
channels()fromslack→slack-channels(Slack tools remain onslack). - AgentController model switching: removed
modeId/scope;session.model.switchsignature changed toswitch(modelId, options?)and sessions persist one model per thread (mode switches no longer auto-change models). @mastra/reacthooks: positional arguments replaced with a single object argument; all hooks acceptqueryOptions(TanStack Query), and hooks no longer guard on empty ids by default.
Changelog
@mastra/core@1.75.0
Minor Changes
-
Added the
StorageMemoryReftype so a stored agent'smemoryfield can reference a registered memory instance with{ type: 'id', memoryId }, or hold an inline config. Existing inline configs are still accepted. (#25850)Also in: @mastra/client-js@1.52.0, @mastra/server@1.75.0
-
Added support for several processes sharing one notification store, where only some of them can run a given thread. (#25741)
dispatchDueNotificationsaccepts aresourceIdto dispatch only that resource's due notifications. A delivery policy can returnhold: trueto leave a due notification pending for another dispatcher, without counting a delivery attempt.holdonly applies when a due notification is dispatched. When a notification is first sent, return an action that does not deliver it, such asdefer:import { defaultNotificationDeliveryDecision } from '@mastra/core/notifications'; const agent = new Agent({ // ... notifications: { deliveryPolicy: { decide: input => canRunHere(input.record.resourceId) ? defaultNotificationDeliveryDecision(input) : { action: 'defer', deliverAt: input.now, hold: true }, }, }, });
-
Added an explicit reconcile step for channel installations whose connect flow finishes outside the app (for example Discord's bot invite, which never redirects back). Channel providers can implement the new optional
reconcileInstallation(agentId)method, exposed overPOST /api/channels/:platform/:agentId/reconcileandclient.channels.reconcileInstallation(platform, agentId). The route requires the same write access as connecting, and returns the agent's fresh installation — ornullwhen the platform doesn't support reconciliation. Listing installations is now a pure read and never changes state. (#25993)const installation = await client.channels.reconcileInstallation('discord', 'my-agent'); // { id, platform, agentId, status: 'active', ... } once the invite completed
Also in: @mastra/client-js@1.52.0, @mastra/server@1.75.0
-
Added optional
title,websiteUrl, andiconsoptions toMCPServerConfig, exposed onMCPServerBase, so MCP server implementations can announce a display title, website, and icons to clients. (#25985) -
Added token and cost measures to
aggregateTraces(). Requests can now ask fortokens.input,tokens.output,tokens.total,tokens.reasoning, andtokens.cached(each as.sumor.avg), pluscost.sumandcost.avg, and use them inhavingandorderBy. (#25735)Usage is summed per trace first, then per group. Averages are per trace with token usage, so traces with no recorded usage don't pull them down. Any request for a
cost.*measure also returnscoston each row:cost.coverageis the share of traces with token usage whose cost was fully priced, andcost.unitis the currency. When a group mixes currencies,cost.sumandcost.avgarenullandcost.unitis"mixed". Groups whose measure isnullsort last and never satisfy ahavingcondition.import { parseTraceAggregateRequest, planTraceAggregate } from '@mastra/core/storage'; const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-06-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, groupBy: ['entityName'], interval: '1d', measures: ['count', 'tokens.input.sum', 'tokens.output.sum', 'cost.sum'], orderBy: { field: 'cost.sum', direction: 'desc' }, limit: 20, }), ); const { rows } = await storage.aggregateTraces(plan); // rows[0].measures → { count, 'tokens.input.sum', 'tokens.output.sum', 'cost.sum' } // rows[0].cost → { coverage: 0.96, unit: 'usd' }
TokenMetrics, the names of the token usage metrics, is now exported from@mastra/core/observability. -
Semantic recall now works with a vector store that generates embeddings itself, so a self-embedding store needs no client-side
embedder. (#25009)Added
isSelfEmbeddingtoMastraVector. It defaults to false, so every existing store is unaffected. A store that embeds text itself overrides it to return true, and semantic recall against that store needs noembedder:const memory = new Memory({ storage, vector: new MongoDBVector({ id: 'vec', uri, dbName, autoEmbed: { model: 'voyage-4' } }), options: { semanticRecall: true }, });
A configured
embedderstill takes precedence, so adding one to the example above returns to client-side embedding.Messages embedded by the store are kept in an index named
memory_messages_selfembed, separate from the indexes holding client-supplied vectors.Configuring semantic recall with neither an embedder nor a self-embedding store now says so, naming both options.
-
Added a storage API for querying individual completed spans with filters, cursors, bounded previews, and model cost. (#25791)
Shared cursor, timeout, and resource-limit errors now say "query" so they also describe span queries accurately. Error codes are unchanged.
import { planSpanQuery } from '@mastra/core/storage'; const result = await storage.querySpans( planSpanQuery({ timeRange: { from: '2026-10-01T00:00:00Z', to: '2026-10-02T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'tool_call' } }, }), );
-
Changed AgentController sessions to keep one active model instead of a separate model for each mode. Switching modes no longer changes the model automatically, and
session.model.switchno longer acceptsmodeIdorscope. Themodel_changedevent no longer includesmodeIdorscope; consumers should read itsmodelIdand currentthinkingLevelfields. When an existing thread is first reopened, its active legacy per-mode selection is copied tocurrentModelId, a migration marker is stored, and obsoletemodeModelId_*keys are removed. Later model selections persist as the thread's authoritativecurrentModelId. Usesession.model.setfor an in-memory selection that should not be persisted or emit a model-change event. (#25997)Before
await session.model.switch({ modelId: 'openai/gpt-5.6', modeId: 'build' }); await session.mode.switch({ modeId: 'plan' }); // Restored the plan mode model.
After
await session.model.switch('openai/gpt-5.6'); await session.mode.switch({ modeId: 'plan' }); // Keeps openai/gpt-5.6 active.
-
Added
traceIdto agent stream chunks, next torunId. Clients that call/streamcan now link a run to its trace without switching to/generate. The field is undefined when tracing is disabled. Customdata-*chunks (for example, fromwriter.custom()) are left exactly as w...
October 1, 2026
Highlights
Tool Context Can Read the Full Conversation (agent.getMessages())
Tools now have access to the current conversation state via context.agent.getMessages() in both standard and durable agent loops, including remembered messages and in-run responses—enabling richer, context-aware tool behavior without changing the existing input-only messages field.
Observational Memory History Search (Filtering, Ordering, Record Lookup)
getObservationalMemoryHistory gained group filtering (groupId), explicit ordering (sortDirection), and direct lookup by recordId, with adapter capability signaling via supportsObservationalMemoryHistorySearch (notably affecting multiple DB adapters; Convex requires a server function redeploy for the new filters to apply).
Memory Recall Can Page Through Observation Groups Around Search Hits
Recall now supports paging “before/after” from a matched groupId, including groups condensed by reflection and buffered-but-not-yet-activated groups—improving investigation workflows without requiring re-indexing, alongside clearer, chronologically ordered search results and better first-turn retrieval guidance.
Playground UI: New Accessible Form Field Primitives (Field, Fieldset, Form, SearchInput)
A new set of form-building components standardizes label/description/error wiring without manual id/htmlFor, improving accessibility and consistency; includes new Form and SearchInput, plus deprecations of the older *FieldBlock approach to move toward a single, safer pattern.
Breaking Changes
@mastra/playground-ui:anchorTraceIdremoved fromThreadViewByTrace,TraceThreadPanel, andThreadTrace;ThreadTrace.LoadMoreSentinelremoved. UsepageSizeandonLoadOlderonThreadTraceto control pagination/loading older turns.
Changelog
@mastra/core@1.74.0
Minor Changes
-
Added
agent.getMessages()to tool execution context in standard and durable agent loops. Tools can read the current conversation, including remembered messages and in-run responses, without changing the existing input-onlymessagesfield. (#25525)execute: async (input, context) => { const messages = context?.agent?.getMessages?.() ?? []; return { messageCount: messages.length }; };
The getter reflects message-list removals, but not transient transforms applied only to the provider prompt. Treat returned messages as read-only.
-
Added group filtering and generation ordering to observational memory history. For example,
getObservationalMemoryHistory(threadId, resourceId, 1, { groupId, sortDirection: "ASC" })finds the earliest retained record containing a group in active observations or persisted buffered chunks. Adapters advertise support throughsupportsObservationalMemoryHistorySearch. PassrecordIdto read one record by ID; it only matches records for the requested thread or resource. (#25525)Convex users need to redeploy their Mastra server functions for these filters to apply.
Also in: @mastra/convex@1.7.0, @mastra/libsql@1.25.0, @mastra/mongodb@1.21.0, @mastra/mysql@0.12.0, @mastra/oracledb@0.5.0, @mastra/pg@1.29.0
Patch Changes
-
Update provider registry and model documentation with latest models and providers (
ac54c46) -
Fixed chat channel agents ignoring mentions of their current display name after being renamed. For example, a Slack app renamed from
acme-bottohelpernow responds to@helperin channels, instead of only recognising its original username. The bot's current profile name is looked up once through the adapter and exposed asbotDisplayNameon the channel context. (#25652) -
Raise transitive security dependency floors (dompurify, js-yaml, @ai-sdk/provider-utils) and bump nested/template deps (nodemailer, fastify, hono, ajv) for the 2026-10-01 Vanta remediation pass. (#25694)
@mastra/factory@0.19.1
Patch Changes
-
Repository-backed Slack sessions can now recover from a failed
ghauthentication attempt without restarting the session. Refresh GitHub access, then retry the failed command: (#25545)github_refresh_token({}) -
Fixed Slack-triggered Factory runs failing with "No usable credential" after a server restart. Slack mentions, DMs, and thread replies now load the linked user's model credentials before the run starts, instead of relying on that user having opened the web UI first. (#25653)
@mastra/memory@1.35.0
Minor Changes
-
Page through observation groups. Recall can now read the original observation groups around a search hit, including groups condensed away by reflection and buffered groups that haven't been activated yet. Existing records work without re-indexing. (#25525)
recall({ mode: 'observations', groupId: 'group-id-from-search', direction: 'after', limit: 5 });
Easier-to-read search results. Search results are dated, listed oldest first, and mark where groups may be hidden between hits. Hits already in the agent's context come back as short references, so more new hits fit. When a long group is shortened, its excerpt starts at the line that best matches the query.
Better recall guidance. With retrieval enabled, the agent gets recall guidance from the first turn, including in read-only runs. It explains how to search, page, and confirm details against source messages, treats what the user said as authoritative and what the assistant proposed as a suggestion, and labels reflected groups as lossy summaries.
@mastra/playground-ui@60.2.0
Minor Changes
-
Added
Fieldcomponents for building form fields:Field,FieldLabel,FieldDescription,FieldError,FieldContent,FieldItem,FieldsetandFieldsetLegend, from@mastra/playground-ui/components/Field. They are built on Base UI Field and Fieldset. Put a label and any control inside aField, and the label names the control. The description and error are linked to it too, and the control is marked invalid, with no ids to pass around. The part names follow shadcn/ui, but unlike shadcn,FieldLabelneeds nohtmlForand the control noid. Code copied from shadcn that still passes them keeps working. (#25317)// Before <TextFieldBlock name="email" label="Email" helpText="Used to sign in." errorMsg={errors.email?.message} required /> // After <Field invalid={Boolean(errors.email)}> <FieldLabel required>Email</FieldLabel> <Input {...register('email')} /> <FieldDescription>Used to sign in.</FieldDescription> <FieldError>{errors.email?.message}</FieldError> </Field>
This works for
Input,Textarea,InputGroupInput,InputGroupTextarea,Select,Combobox,Checkbox,Switch,RadioGroupandCodeEditor. For a control that is not built on Base UI, such as a custom popover trigger, spreaduseFieldControlAria()on it to get the same label, description and invalid wiring. To hide a label and keep it for screen readers, use<FieldLabel className="sr-only">. To put the label beside the control, use<Field orientation="horizontal">: the row is centred, and when the label comes first the control sits at the far end, so a settings toggle needs no extra classes. Useorientation="responsive"to stack it on small screens. For a radio group, use<Fieldset render={<RadioGroup />}>with aFieldsetLegend, and wrap each option in aFieldItem.A
Fieldholds one control. When one title covers several controls, use aFieldsetwith aFieldsetLegend(it takesrequiredlikeFieldLabel), and name each control with its ownaria-label. For a settings row, use the newSettingsFieldsetRow:<SettingsFieldsetRow label="Releases" description="Where new issues go."> <SelectTrigger aria-label="Factory for Releases">…</SelectTrigger> <SelectTrigger aria-label="Board for Releases">…</SelectTrigger> </SettingsFieldsetRow>
A control that has its own
aria-labelkeeps that name inside aFieldtoo, the same wayaria-labelwins over a native<label>.Labels are styled by the
Field: a label beside aCheckbox,Switchor radio shows a pointer cursor, so nocursor-*or color classes are needed. To disable a field, putdisabledon theField,FieldItemorFieldsetrather than on the control: the control is disabled through it, and only then does the label show it can't be clicked. For compact rows, use<FieldLabel size="smaller">.Added
Form(@mastra/playground-ui/components/Form), built on Base UI Form, with one standard gap between fields. Put a<FieldError />with no children in a field that usesrequired,type="email",minormax: when a user submits an invalid value, the form stops, focuses that field and shows the browser's message there.Added
SearchInput(@mastra/playground-ui/components/SearchInput), a search box with a hidden label, a search icon and a clear button that puts focus back in the field. It updates on every keystroke. PassonClosefor a search that collapses: the button then reads "Close search", stays visible and callsonCloseafter clearing.ListSearchis now built on it and adds the 300 ms debounce and the Cmd/Ctrl+Shift+F shortcut. Emptying aListSearch, by typing or with the clear button, now callsonSearch('')right away. Both render<input type="search">, so tests find them by rolesearchboxinstead oftextbox.TimePickerta...