Repository navigation
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 first run, and uses that id as its external parent. Before, it started a new trace.await agent.resumeGenerate({ approved: true }, { runId, toolCallId, tracingOptions: { parentSpanId: callerSpanId } }); // The resumed run stays in the first run's trace. // resumedFromSpanId is the first run's span id, externalParentSpanId is callerSpanId.
The
resumedFromSpanIdoption ofgetOrCreateSpanis deprecated. UseresumeFromwith the saved tracing context instead. -
Span aggregates can now group by
modelandproviderand count their distinct values withcountDistinct.modelandcountDistinct.provider. Stores opt in with the newspan-aggregate-model-dimensionsfeature. (#28908)const { rows } = await mastraClient.aggregateSpans({ timeRange, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'model_generation' } }, groupBy: ['provider', 'model'], measures: ['count', 'tokens.total.sum', 'cost.sum'], });
-
Span queries and span aggregates now accept
modelandproviderfilters. Stores opt in with the newspan-query-model-fieldsfeature. (#28859)const { spans } = await mastraClient.querySpans({ timeRange, where: { op: 'eq', left: { path: 'provider' }, right: { literal: 'anthropic' } }, });
-
Fixed the browser failing with
ECONNREFUSEDwhen launched again on a profile it had used before. (#28675)Before launching Chrome on a local profile, Mastra now clears the launcher logs left by earlier launches (
chrome-err.log,chrome-out.log), which made the next launch connect to the old, dead DevTools port. It also removes a Chrome profile lock left by a browser that is no longer running. Stagehand does this for every thread-scoped browser too, not only on the first launch. Remote browsers (cdpUrl, Browserbase) are not affected.If a browser that is still running holds the profile, launch now fails with a
BrowserProfileInUseErrorthat names the profile and the holding process:import { BrowserProfileInUseError } from '@mastra/core/browser'; try { await browser.launch(); } catch (error) { if (error instanceof BrowserProfileInUseError) { // Another Chrome is using the profile: close it, or use a different profile. console.error(error.message); } throw error; }
Added
browser.retire()andbrowser.killProcesses()for code that replaces one browser with another.close()lets the browser relaunch the next time something uses it;retire()closes it for good, so a tool call still holding the old browser gets an error instead of starting an untracked Chrome.killProcesses()force-kills the browser's processes whenclose()fails or hangs. It works on macOS and Linux; it cannot force-kill Chrome on Windows.// Replace oldBrowser with newBrowser without leaving its Chrome running. const retired = oldBrowser.retire(); const timedOut = await Promise.race([ retired.then(() => false), new Promise<boolean>(resolve => setTimeout(() => resolve(true), 5000)), ]).catch(() => true); if (timedOut) oldBrowser.killProcesses();
A browser that finishes closing after another browser took its profile no longer deletes the new browser's lock.
A lock whose holder is a live local process that has not started listening yet (Chrome creates the lock first) is no longer removed as stale; Mastra waits up to 3 seconds for it.
Also in: @mastra/stagehand@0.4.0
-
Added schemas for custom dashboard definitions. These are schemas only:
@mastra/coreadds no storage methods for dashboards, and no store persists them. (#28714)Layout
- A definition lays out rows of widgets on a 12-column grid.
- Each widget stores its own trace aggregate, span aggregate, metrics, or scores queries. Queries have no time range; the page supplies it.
- A view can set its own
description,scope, anddrilldown, overriding the widget's. A drilldown has anactiontarget and aclicktarget.
Queries
- A query can have an optional
id. It starts with a letter, uses only letters, digits,_and-, and is unique within its view. Column ids are also unique within a view. - Set
compare: 'previousWindow'on any query to compare with the previous window. - A scores query without
scorerIdcovers every scorer, with one series per scorer.
Filters
defaults.filtersand drilldownfiltersuse the same filter node schema as saved views (filterNodeSchema).- Filter groups can nest at most 12 levels deep (
DASHBOARD_MAX_FILTER_DEPTH). - The page's filters are combined with each query's own.
whereclauses are ANDed. If both set the same metrics or scores filter field, the values must agree (arrays intersect). A contradiction returns no data.
Validation
dashboardDefinitionSchemavalidates a whole definition.dashboardDefinitionEnvelopeSchemachecks only what a service storing dashboards needs: a knownschemaVersion, at most 20 widgets, 1 to 8 queries per view, at most 64 KiB serialized, unique widget ids, andkindandviewson every widget. New widget kinds can be stored without upgrading that service.
New widget kinds
- Widget
kindaccepts any non-empty string. Viewoptionsare passed through for each kind to validate, so new widget kinds need no@mastra/coreupgrade. DASHBOARD_WIDGET_KINDSlists the built-in kinds:kpis,table,timeseriesandscores.
Versions
- Adding an optional field keeps
schemaVersionthe same. - A breaking change bumps
schemaVersionand adds an upgrade step tomigrateDashboardDefinition. That function brings any stored version up to the current one before parsing.
Browser apps
- Import the dashboard schemas from
@mastra/core/storage/dashboards. It loads no Node built-ins and uses no Node globals such asprocessorBuffer. @mastra/core/storagereaches agent code that readsprocesswhen it loads, so it fails in the browser.
import { dashboardDefinitionSchema } from '@mastra/core/storage/dashboards'; const result = dashboardDefinitionSchema.safeParse({ schemaVersion: 1, rows: [ { widgets: [ { id: 'agents', span: 6, kind: 'table', views: [ { queries: [ { source: 'traceAggregate', measures: ['count', 'errorRate'], groupBy: ['rootEntityName'], }, ], }, ], }, ], }, ], });
-
Added
outcomeandstopReasonto advanced span and trace queries. You can now filter, group, and discover values by how a span ended and why, separately fromstatus. Span and trace results also returnstopReasonContext. (#28884)At trace scope the fields read the root span. Use
spans.someto find traces where any span hit a limit:const { traces } = await client.queryTraces({ timeRange, where: { spans: { some: { op: 'eq', left: { path: 'outcome' }, right: { literal: 'limit' } } } }, });
A missing outcome means "not recorded", never success. Stores opt in through the new
span-query-outcome-fieldsandtrace-query-outcome-fieldsfeatures. -
Trace, span and thread queries can now return and filter by
timeToFirstTokenMs, the time from the start of the model call to its first streamed chunk. Trace and span queries can also sort by it. Trace rows use the root span's value, and thread rows use the first trace's root agent. Missing values sort last. Stores that support it declare thetrace-query-time-to-first-tokenandtrace-query-time-to-first-token-sortfeatures. (#28699)const page = await storage.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, orderBy: [{ field: 'timeToFirstTokenMs', direction: 'desc' }] }), ), );
Durable agents now carry the time to first token of the model call that streamed the run's first chunk across steps, so their generation and agent run spans report it too.
A model call that fails before a fallback model, or a durable agent's retry, takes over now ends its model inference span with that error.
-
Added support for background tasks owned by code other than agents, such as MCP servers. (#28425)
Per-task retention. Tasks accept optional
expiresAtandmetadatafields. When thecleanupoption is set, cleanup deletes a finished task once its ownexpiresAtpasses instead of usingcompletedTtlMsorfailedTtlMs. Pending, running, and suspended tasks are never deleted.metadatais stored with the task and not read by the manager.await mastra.backgroundTaskManager.enqueue({ agentId: 'mcp-server:ops', toolName: 'mcp-server:ops:deploy', toolCallId, runId, args, expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), metadata: { ttlMs: 86_400_000 }, });
Per-owner concurrency.
perOwnerConcurrencyoverridesperAgentConcurrencyfor specific owners, keyed byagentId. Each value must be a number of at least 1. These tasks still count towardglobalConcurrency.new Mastra({ backgroundTasks: { enabled: true, globalConcurrency: 60, perOwnerConcurrency: { 'mcp-server:ops': 50 } }, });
Queued resume.
resume()still throws when no concurrency slot is free. Pass{ onNoSlot: 'queue' }to keep the task suspended and resume it once a slot frees up. The queue is held in memory by the calling process, so a queued resume is lost if that process exits; the task stays suspended and can be resumed again. A queued resume is dropped if the task is cancelled or resumed elsewhere first.await mastra.backgroundTaskManager.resume(taskId, resumeData, { onNoSlot: 'queue' });
Storage adapters need a matching release to store
expiresAtandmetadata. With an older adapter, cleanup detects that expiry is unsupported and keeps using only the global retention settings, as before. -
Added the experimental
getTasksInfo()andgetTasksInspector()methods to MCP servers, so tools like Studio can see which tools run as MCP tasks and list, cancel or answer a server's tasks. (#28629)const inspector = server.getTasksInspector(); const { tasks } = (await inspector?.list({ status: 'input_required' })) ?? { tasks: [] }; if (tasks[0]) await inspector?.cancel(tasks[0].taskKey);
Patch Changes
-
Fixed resuming suspended agent runs across server instances when a process retains an earlier run segment. (#29061)
-
Update provider registry and model documentation with latest models and providers (
9f03685) -
Fixed recovered durable agents skipping configured completion checks (#29037)
-
Fixed background tasks staying locked to a worker after it shut down gracefully. (#28719)
-
Fixed provider-reported cost on model generation spans when an agent run pauses on a tool approval or
suspend()and then resumes. (#28955) -
Fixed
@mastra/core/loop/serverfailing to load with a module-not-found error. (#29056) -
PROCESSOR_WORKFLOW_FAILEDnow keeps the underlying error as itscause. (#28831) -
Fixed
POST /agents/:agentId/runs/:runId/abortanswering{ aborted: false }when it reached a server that does not own the run. (#28522)Also in: @mastra/inngest@1.11.0, @mastra/server@1.81.0
-
Flush observability exporters before closing storage on Mastra shutdown, so final spans persist when using DuckDB. (#28656)
Also in: @mastra/code-sdk@1.17.0, @mastra/duckdb@1.17.0
-
Fixed
output.requestandonFinish.requestfor durable and evented agents. (#28674) -
Fixed agents treating
observationalMemory: { enabled: false }as if observational memory were on. (#28922) -
Fixed
resumeStream()on evented workflows rejecting itsresultwith "promise 'result' was not resolved or rejected when stream finished". (#28871) -
Added an
expectedSuspendedAtcondition to background task updates. (#28590) -
Added a
pinToWorkeroption tobackgroundTaskManager.enqueue()for tasks whose work is already running in the current process. (#28507) -
Fixed nested evented workflows running without the caller's
workflows:executepermission on the child workflow. (#28669) -
Added the internal support
@mastra/memoryneeds to delete a thread's event stream when the thread is deleted. (#28968) -
Fixed durable runs stopping when a lease renewal queued behind other storage writes still belongs to the same execution. (#28936)
-
Fixed trace trees for agents whose model call is rejected and handled by an error processor (for example a retry on a rate limit). (#28745)
-
Inngest durable agents no longer block memory writes after
modelSettings.timeout.totalMsexpires. (#28926)Also in: @mastra/inngest@1.11.0
-
Fixed thread-message listeners accumulating while durable runs wait for approval. (#29046)
-
Fixed tracing for runs that continue in another process. (#28832)
-
Fixed supervisor agents losing track of which subagent tool paused. (#28991)
-
Improved
Mastradeployer compatibility: thedeployeroption now accepts any object that implements theIDeployerinterface (exported from@mastra/core/deployer), not only subclasses ofMastraDeployer. (#28428) -
Retry policies that match bad-request errors no longer retry a 400 that rejects the model or a parameter, such as
model_not_found. (#28786) -
Fixed thread stream topics using far more memory than needed for agents without memory, such as the observational memory observer and reflector. (#28746)
-
Fixed channel tool approval actions on tool calls whose ID contains a colon. (#28728)
-
Inngest durable agents now save the accepted user message before the run starts, as the other durable engines do. (#28747)
Also in: @mastra/inngest@1.11.0
-
Clarify that
FileStat.pathis a path within the filesystem namespace. (#28819) -
Fixed
subscribeToThread({ withInitialHistory: true })hanging or rejecting when the lease backend (for example Redis) is slow or unavailable, which also keptAgentControllersessions from opening. (#28924) -
Fixed provider-executed tool results that stream partial values, such as OpenAI
image_generationpartial images with AI SDK v6. (#28903)Also in: @mastra/ai-sdk@1.11.3
-
Fixed signals and notifications that arrive while a run is finishing. (#29015)
-
Fixed AgentController sessions getting stuck as "running" after a failed tool answer. (#29005)
-
Fixed workspace sandbox tools (
execute_command,get_process_output,kill_process) sending an empty tool result to the model when abeforeToolCallhook blocked the call with{ error: reason }. (#28919) -
Fixed channel agents being unable to read text attachments such as
.txt, CSV, JSON, YAML or log files shared in Slack. (#28901) -
Studio's model provider picker now lists Mastra Gateway even when
MASTRA_GATEWAY_API_KEYisn't set. (#28824) -
Publish tool suspension and approval events only after the parked tool call is saved to memory, so a client that reacts to the event — resuming it, or aborting the run — finds the tool call already persisted instead of racing the save still in flight. (#28839)
-
Limit memory used when publishing stalls. (#28652)
-
Added the saved view schemas (
savedViewStateSchema,savedViewStateEnvelopeSchema,SAVED_VIEW_STATE_MAX_BYTESand related types) to the browser-safe@mastra/core/storage/dashboardsentry, so browser apps can validate a saved view before sending it. (#29001) -
Fixed agent runs that hit
modelSettings.timeout.totalMslosing the user's message from memory. (#28668) -
Updated the AI SDK v5 compatibility dependencies so they resolve @ai-sdk/provider-utils 3.0.42, which blocks
.internalhostnames in download URL validation and encodes provider-returned identifiers in request paths. (#28716)Also in: @mastra/agent-builder@1.1.32, @mastra/longmemeval@1.1.40
-
Fixed
LocalSandbox.executeCommand(command, args)on native Windows. (#28900) -
Workspace tools now save their
data-workspace-metadatachunks to the current response message. (#28853) -
Workspace file-modification protection is now off by default and only applies when
requireReadBeforeWriteis enabled. (#28935) -
write_filenow names the real file location when the filesystem maps an absolute path somewhere else. (#28902)
@mastra/agent-browser@0.6.0
Minor Changes
-
Added
setCookies()support toAgentBrowser. Cookies set before a thread's browser opens are added before it loads any page, so pages open signed in. (#28631)await browser.setCookies([{ name: 'my_app_session', value: token, domain: 'app.example.com', path: '/' }], threadId);
-
browser_screenshotcan now also save the screenshot as a PNG in the agent's workspace. Pass apathand the agent can attach or share the file afterwards without setting up its own browser. Missing folders are created. Saving follows the workspace's write-file settings, so nothing is saved when writing is turned off or needs approval. If the screenshot can't be saved, for example because the agent has no workspace, the model still gets the image along with the reason. (#28636){ "path": "screenshots/home.png" }
@mastra/agent-builder@1.1.32
Patch Changes
-
Fixed
require('@mastra/agent-builder')failing with a module-not-found error. (#29056) -
Improved type declaration formatting. (#28726)
Also in: @mastra/code-sdk@1.17.0, @mastra/deployer@1.81.0, @mastra/factory@0.26.0, @mastra/memory@1.40.0
@mastra/ai-sdk@1.11.3
Patch Changes
- Added
delegatedToolCallIdanddelegatedToolNametodata-tool-call-suspendedanddata-tool-call-approvalparts. (#28991)
@mastra/braintrust@1.3.25
Patch Changes
-
Fixed Braintrust cost for model calls with a provider-reported price, such as OpenRouter and Vercel AI Gateway. (#28954)
-
Spans keep
timeToFirstTokenMsout of Braintrust metadata, as withcompletionStartTime; Braintrust already derives time to first token itself. (#28699)
@mastra/browser-firecrawl@0.3.0
Minor Changes
-
Added
setCookies()support toFirecrawlBrowser. Cookies set before a thread's browser opens are added before it loads any page, so pages open signed in. (#28631)await browser.setCookies([{ name: 'my_app_session', value: token, domain: 'app.example.com', path: '/' }], threadId);
@mastra/browser-viewer@0.3.0
Minor Changes
-
Added
setCookies()support toBrowserViewer. Cookies set before a thread's browser opens are added before it loads any page, so pages open signed in. (#28631)await browser.setCookies([{ name: 'my_app_session', value: token, domain: 'app.example.com', path: '/' }], threadId);
@mastra/clickhouse@1.28.0
Minor Changes
-
Added support for filtering and grouping span and trace queries by
outcomeandstopReason, and returnedoutcome,stopReason, andstopReasonContexton span and trace results. Trace-level values come from the root span. (#28884)With one of these stores configured, query traces whose root span hit a limit:
const { traces } = await client.queryTraces({ timeRange, where: { op: 'eq', left: { path: 'outcome' }, right: { literal: 'limit' } }, });
Also in: @mastra/duckdb@1.17.0, @mastra/pg@1.34.0
-
Span aggregates can group by
modelandprovider, and count distinct models and providers. Like themodelandproviderfilters, grouping reads only the dedicatedmodelandprovidercolumns, not span attributes. Spans exported before@mastra/observabilityfilled the columns group undernull; existing spans aren't backfilled. (#28908)const { rows } = await mastraClient.aggregateSpans({ timeRange, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'model_generation' } }, groupBy: ['model'], measures: ['count', 'tokens.total.sum'], }); // [{ dimensions: { model: 'claude-sonnet-5-5' }, measures: { count: 12, 'tokens.total.sum': 48210 } }, ...]
Also in: @mastra/duckdb@1.17.0, @mastra/pg@1.34.0
-
Span queries and span aggregates can filter by
modelandprovider. These filters read only the dedicatedmodelandprovidercolumns, not span attributes.@mastra/observabilityfills the columns only when a span has both amodeland aprovider, and versions before it filled them left them empty, so those spans count as missing:eq,inandexistsdon't match them, whilene,notInandnotExistsdo. Results still returnmodelandproviderfor them, read from their attributes. (#28859)const { spans } = await mastraClient.querySpans({ timeRange, where: { op: 'eq', left: { path: 'provider' }, right: { literal: 'openai' } }, });
Also in: @mastra/duckdb@1.17.0, @mastra/pg@1.34.0
-
ClickHouse trace, span and thread queries now return and filter by
timeToFirstTokenMs, and trace and span queries can sort by it, slowest or fastest first. Traces use the root span's value. Traces and spans without a value come last. (#28699)orderBy: [{ field: 'timeToFirstTokenMs', direction: 'desc' }];
Patch Changes
-
Fixed span timestamps being stored as 9999-12-31 on ClickHouse 26.8 and later. (#28011)
-
Trace query
spans.someandspans.nonefilters onmodelandprovidernow also match the dedicated span columns. (#28859)Also in: @mastra/duckdb@1.17.0, @mastra/pg@1.34.0
@mastra/client-js@1.58.0
Minor Changes
-
Workspace routes now report
capabilities.hasDynamicFilesystemwhen the filesystem comes from a resolver. File routes can't browse that filesystem, so Studio's workspace page now explains that the filesystem is resolved per request instead of showing a file tree that fails to load, and the skill dialog warns admins that skill files can't be written to that workspace. (#29027)const info = await client.getWorkspace('my-workspace').info(); if (info.capabilities?.hasDynamicFilesystem) { // The filesystem is created per request by a resolver, so the /fs routes can't browse it }
Also in: @mastra/react@1.14.0, @mastra/server@1.81.0
-
Added
getMcpServerTasks(),findMcpServerTask(),cancelMcpServerTask()andanswerMcpServerTaskInput()for an MCP server's tasks. (#28629)const { tasks, nextCursor } = await client.getMcpServerTasks('reports', { status: 'input_required' }); await client.answerMcpServerTaskInput('reports', tasks[0].taskKey, { approve: { action: 'accept', content: { ok: true } }, });
-
Added
outcomeandstopReasonfilters and groupings toquerySpans(),aggregateSpans(),queryTraces(), andaggregateTraces(), withoutcome,stopReason, andstopReasonContexton results. CheckspanQueryOutcomeFieldsandtraceQueryOutcomeFieldsfromgetObservabilityCapabilities()first: (#28884)const { capabilities } = await client.getObservabilityCapabilities(); if (capabilities.spanQueryOutcomeFields) { await client.querySpans({ timeRange, where: { op: 'eq', left: { path: 'outcome' }, right: { literal: 'limit' } } }); }
Patch Changes
-
Exported the saved view and filter types (
SavedViewState,FilterNodeand related types) from@mastra/client-js. (#28715) -
getObservabilityCapabilities()returns thespanAggregateModelDimensionsflag. (#28908) -
getObservabilityCapabilities()returns thespanQueryModelFieldsflag. (#28859) -
Added the dashboard definition types (
DashboardDefinition,DashboardWidget,DashboardQuery, and related types) to the exports. (#28714) -
queryTraces(),querySpans()andqueryTraceThreads()results now includetimeToFirstTokenMswhen the store supports it, andqueryTraces()andquerySpans()accept it inorderBy. (#28699)
@mastra/cloudflare@1.8.0
Minor Changes
-
Added storage for the optional background task fields
expiresAtandmetadata, plus filtering byexpiresAt. Existing tables and collections gain the new fields automatically when the store initializes, and existing tasks keep using the global retention settings. Requires the matching@mastra/corerelease. (#28425)Convex users must redeploy their Convex schema after upgrading, because the adapter now writes the
expiresAtandmetadatafields.Also in: @mastra/cloudflare-d1@1.5.0, @mastra/convex@1.8.0, @mastra/dynamodb@1.5.0, @mastra/lance@1.6.0, @mastra/libsql@1.28.0, @mastra/mongodb@1.25.0, @mastra/mssql@1.11.0, @mastra/mysql@0.15.0, @mastra/pg@1.34.0, @mastra/upstash@1.6.0
Patch Changes
-
Fixed a late background task write overwriting a newer suspension. (#28590)
Also in: @mastra/cloudflare-d1@1.5.0, @mastra/convex@1.8.0, @mastra/dynamodb@1.5.0, @mastra/lance@1.6.0, @mastra/libsql@1.28.0, @mastra/mongodb@1.25.0, @mastra/mssql@1.11.0, @mastra/mysql@0.15.0, @mastra/pg@1.34.0, @mastra/spanner@1.11.0, @mastra/upstash@1.6.0
-
Fixed upgraded D1 and Durable Object stores deleting finished background tasks early. (#28911)
Also in: @mastra/cloudflare-d1@1.5.0
@mastra/code-sdk@1.17.0
Minor Changes
-
Added control over rich scorer-result storage. Hosts can retain the safe default or enable their configured storage for Studio score access. (#28873)
createMastraCode({ disableScoreStorage: false });
Patch Changes
-
Export
CODE_AGENT_IDfrom@mastra/code-sdk/constants, the id of the coding agent Mastra Code registers. (#28604) -
Fixed OpenAI Codex (ChatGPT sign-in) threads failing with
Item with id 'rs_…' not foundafter sitting idle. (#28786) -
Mastra Code now closes its browser when it exits, when it relaunches, and when
/browserreplaces or disables it, so a leftover Chrome no longer keeps the browser profile locked. (#28675) -
Mastra Code file edit and write tools no longer fail when a file was read or changed outside the
viewtool (for example withcat,sed -ior a formatter). (#28935) -
Fixed Factory sessions failing with a sign-in error when a provider such as OpenAI Codex or Anthropic rejects a personal OAuth token before its saved expiry. (#28904)
Also in: @mastra/factory@0.26.0
@mastra/connect@1.3.0
Minor Changes
-
Added a Microsoft Excel toolset with 27 tools for drive discovery, workbook files, worksheets, ranges, tables, names, charts, and search. (#26283)
import { Agent } from '@mastra/core/agent'; import { tools } from '@mastra/connect'; const excelAgent = new Agent({ id: 'excel-agent', name: 'Excel agent', instructions: 'Use the connected Microsoft Excel tools to manage workbooks.', model: 'openai/gpt-5-mini', tools: tools({ providers: ['microsoft-excel'] }), });
-
Added a Microsoft Word toolset with 18 tools for drive discovery, document creation, copying, updating, deletion, moving, sharing, and export. (#26282)
import { Agent } from '@mastra/core/agent'; import { tools } from '@mastra/connect'; const wordAgent = new Agent({ id: 'word-agent', name: 'Word agent', instructions: 'Use the connected Microsoft Word tools to manage documents.', model: 'openai/gpt-5-mini', tools: tools({ providers: ['microsoft-word'] }), });
-
Added a Microsoft Outlook toolset with 30 tools for messages, replies, drafts, attachments, mail folders, calendars, and events. (#26281)
import { tools } from '@mastra/connect'; const outlookTools = await tools({ providers: ['outlook'] })(); const inbox = await outlookTools.outlook_list_messages.execute({ folderId: 'inbox', limit: 10 });
-
Added a SharePoint Online toolset with 53 tools for sites, lists, list items, columns, content types, pages, permissions, drives, and drive items with binary content download. (#26284)
import { Agent } from '@mastra/core/agent'; import { tools } from '@mastra/connect'; const sharepointAgent = new Agent({ id: 'sharepoint-agent', name: 'SharePoint agent', instructions: 'Use the connected SharePoint Online tools to manage sites and lists.', model: 'openai/gpt-5-mini', tools: tools({ providers: ['sharepoint-online'] }), });
Patch Changes
-
Fixed provider generation so aliased integration templates can be discovered and added with
add-provider. (#26282) -
Fixed delegated Microsoft Word, Excel, and PowerPoint drive discovery so
get-user-drivedefaults to the connected user's OneDrive, while regenerated tools retain supported request and UUID behavior. (#26282) -
Fixed two problems in provider tool requests. (#26283)
@mastra/daytona@0.11.5
Patch Changes
- Updated
@daytonaio/sdkto ^0.226.1. (#28975)
@mastra/deployer@1.81.0
Patch Changes
-
mastra devandmastra startnow run the server with Node's--enable-source-mapsflag whenbundler.sourcemapis enabled, so stack traces point at your source files instead of.mastra/output/index.mjs. (#29054) -
Fixed
bundleroptions insrc/mastra/index.tsbeing ignored when a project has file-based agents (src/mastra/agents/<name>/). (#29054) -
Improved compatibility between
@mastra/deployerdeployers andMastra: theBundlerandDeployerbase classes no longer extend@mastra/core'sMastraBundlerat build time; they implement theIBundler/IDeployerinterfaces instead. (#28428)
@mastra/deployer-cloud@1.81.0
Patch Changes
-
Fixed the minimum
@mastra/corepeer dependency to>=1.81.0-0 <2.0.0-0. (#28428)Also in: @mastra/deployer-cloudflare@1.2.40, @mastra/deployer-netlify@1.2.40, @mastra/deployer-sandbox@0.3.25, @mastra/deployer-vercel@1.2.40, @mastra/temporal@0.4.19
@mastra/duckdb@1.17.0
Minor Changes
-
DuckDB trace, span and thread queries now return and filter by
timeToFirstTokenMs, and trace and span queries can sort by it, slowest or fastest first. Traces use the root span's value. Traces and spans without a value come last. (#28699)orderBy: [{ field: 'timeToFirstTokenMs', direction: 'desc' }];
@mastra/e2b@0.16.0
Patch Changes
-
Updated sandbox provider SDKs to their latest releases:
e2bto ^2.61.0,modalto ^0.11.1 and@vercel/sandboxto 2.9.2. (#28975)Also in: @mastra/e2b-desktop@0.1.11, @mastra/modal@0.7.4, @mastra/platform-workspace@1.11.0, @mastra/vercel@1.6.3
@mastra/elysia@0.1.21
Patch Changes
-
The Elysia and Koa adapters no longer log 4xx errors thrown by route handlers as server errors, matching the Hono, Express, and Fastify adapters. (#28925)
Also in: @mastra/koa@1.7.25
@mastra/evals@1.10.10
Patch Changes
- Fix tool-call accuracy scores to account for missing and repeated calls even when the judge returns fewer evaluations than actual tool calls. (#28771)
@mastra/factory@0.26.0
Minor Changes
-
Board phases can now say who runs them and when they start.
defineBoard()acceptsname,descriptionandnextPhases, and working phases accept arunner({ agentId, skill, context, instructions }),autoStartwith an optionalautoStartWhenrule, and anexitToolthat ends the phase: once it succeeds, the agent moves the card to one ofnextPhaseswithfactory_transition_work_item.autoApproveandautoApproveWhenlet a phase accept asubmit_planplan without a person. (#28604)planning: { name: 'Planning', kind: 'working', runner: { context: 'build', skill: 'plan' }, // a skill that ends by calling submit_plan autoStart: true, autoStartWhen: LOW_EFFORT, exitTool: { id: 'submit_plan', autoApprove: true, autoApproveWhen: LOW_EFFORT }, nextPhases: ['execute', 'canceled'], },
Existing board declarations keep working:
defineBoard()maps the deprecatedtitle,roleandnexttoname,runner.contextandnextPhasesand logs a warning listing them. The board it returns has only the new fields, so code readingboard.title,phase.titleorphase.rolemust readnameandrunner.contextinstead. The board catalog keeps sendingtitleandrolefor older clients. It also sends each working phase'sautoStartWhenandexitTool.autoApproveWhen, so a client can tell which cards a phase starts or approves on its own.The built-in Work and Review boards now use the new fields, with
nextPhasesinstead ofoutcomes.Integration rule contexts describe the card's phase with
nextPhasesandcontextbesideoutcomes;rolestays as a deprecated alias ofcontext. Built-in phases declare no outcome names, so rules that testedcontext.phase?.outcomesshould testcontext.phase?.nextPhases. -
Custom boards can now run your own skills. Pass skills to
new MastraFactory({ skills }), or addfactory-skills/<name>/SKILL.mdto your app, and name one in a phase'srunner.skill. Your skill replaces a built-in skill of the same name. (#28823)import { createSkill } from '@mastra/core/skills'; const releaseNotes = createSkill({ name: 'release-notes', description: 'Draft release notes for a work item', instructions: 'Group the release notes by package and end with a one-line summary.', }); const notesBoard = defineBoard({ id: 'notes', name: 'Notes', initialPhase: 'backlog', phases: { backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['drafting'] }, drafting: { name: 'Drafting', kind: 'working', runner: { skill: 'release-notes' }, nextPhases: ['published'] }, published: { name: 'Published', kind: 'terminal' }, }, }); new MastraFactory({ ...config, boards: [notesBoard], skills: [releaseNotes] });
Settings › Skills now lists your app's skills and shows the skill each custom phase runs. The board catalog sends each working phase's
skill. The constructor warns when a phase names a skill that does not exist, or runs a review skill outside thereviewcontext. -
Factory app skills can live in
src/mastra/factory-skills/<name>/SKILL.md, next to the rest of your Mastra code, instead of undersrc/mastra/public/factory-skills.mastra devreads the folder directly andmastra buildcopies it into the build output. Thepublic/folder still works; when both exist,src/mastra/factory-skillswins. (#28838)const releaseBoard = defineBoard({ id: 'release', initialPhase: 'backlog', phases: { backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['drafting'] }, drafting: { name: 'Drafting', kind: 'working', // Runs src/mastra/factory-skills/release-notes/SKILL.md runner: { context: 'writer', skill: 'release-notes' }, nextPhases: ['published'], }, published: { name: 'Published', kind: 'terminal' }, }, });
-
Organization admins can now create, edit and delete a project's own boards through the API. Saved boards pass the same checks as
defineBoard(), and every change sends back the revision it read, so two people can't overwrite each other's edits. (#28878)curl -X POST "$FACTORY_URL/web/factory/projects/$PROJECT/boards" \ -H 'content-type: application/json' \ -d '{ "key": "bugs", "name": "Bugs", "phases": [{ "key": "reported", "name": "Reported", "kind": "resting" }, { "key": "fixed", "name": "Fixed", "kind": "terminal" }] }'
GET /web/factory/projects/:id/boards/:boardKeyreturns a board's definition,PATCH …/boards/:boardKeyreplaces it withexpectedRevision, andDELETE …/boards/:boardKey?expectedRevision=ndeletes it. Factory refuses edits that would strand cards (409 phase_in_use,409 board_in_use) and runner skills that don't exist (422 unknown_skill).The built-in Work and Review boards and boards passed to
new MastraFactory({ boards })are read-only here: creating, editing or deleting one returns409 code_board, and the catalog andGETreport them witheditable: false.defineBoard()and theboardsandincludeDefaultBoardsoptions ofMastraFactoryare deprecated. They keep working, their boards are not stored, and Factory logs one deprecation warning per process. Apps that use only the built-in boards see no warning. See the "Stored boards" and "Migrating fromdefineBoard()" sections of the README. -
Each project's boards are now the built-in Work and Review boards and the boards you pass to
new MastraFactory({ boards }), followed by any boards the project created for itself, which are stored in its database. Code boards are served exactly as your code defines them, are never written to the database, and are read-only. When a stored board has the same key as a code board, the code board wins. (#28877)Existing apps need no change, and Factory writes nothing to the database at startup.
The board catalog (
GET /web/factory/projects/:id/boards) now reports each board'ssource(builtin,apporstored), whether it iseditable(falsefor code boards) and anyproblems, such as a runner skill that does not exist. -
Added opt-in rich score persistence for Factory deployments so trace scores can appear in Mastra Studio. Storage remains disabled by default to avoid unbounded score growth. (#28873)
new MastraFactory({ storage, disableScoreStorage: false });
-
Added label filters to Jira intake routing, so one Jira project can feed several factories. Each factory's binding takes either all issues or only issues carrying its labels. Label filters take precedence over the all-issues binding; an issue carrying labels from two filters goes to the binding created first. Each issue is filed in exactly one factory. (#28605)
PUT /web/intake/bindings { "integrationId": "jira", "sourceId": "10000", "factoryProjectId": "<payments factory>", "board": "work", "labels": ["payments"] } DELETE /web/intake/bindings?integrationId=jira&sourceId=10000&factoryProjectId=<payments factory>
Overlapping labels, or a second all-issues binding, are rejected with
409 binding_conflict. Labels decide placement only when an issue is first filed; relabelling an issue does not move its card. Linear, GitHub, GitLab and incident.io bindings are unchanged: a source still feeds one factory.Upgrading: existing bindings move to new storage on first start and keep taking all issues, so nothing changes until you add a label filter. If you roll back to an earlier version, bindings return to how they were when you upgraded; changes made since, including label filters, are not carried back.
-
Added custom model provider creation during Factory setup, with immediate provider and model catalog refresh for organization and personal flows. (#28706)
-
Planning and Building on the Work board now run in one
worksession, so the builder keeps the plan's reasoning, branch and sandbox. When the planner moves the card to Building, the build prompt is queued onto its own run. (#28721)This replaces the fallback that let a build borrow the plan or triage session. Triage no longer hands its session to later phases. Runs and API calls that name the
planrole still start Planning, in aplansession the build then continues in; cards planned before this change keep using theirplansession the same way. Self-review keeps its ownreviewsession.invokeSkilldecisions acceptstartsAfterOwnMove: trueto start a run even when the agent of the same session moved the card. Custom board phases with arunner.skillset it when a card enters from another phase, so moving a card between phases that share a context now starts the next phase's skill.phases: { building: { kind: 'working', name: 'Building', runner: { context: 'builder' }, onEnter: { issue: context => ({ type: 'invokeSkill', role: 'builder', idempotencyKey: `${context.ingress.id}:build`, prompt: 'Build the approved plan.', // The planner shares this session and moved the card itself; start the build anyway. startsAfterOwnMove: true, }), }, }, },
Patch Changes
-
Made the work board's polling cheaper on large projects. (#28889)
-
Restored
title,roleandnexton boards returned bydefineBoard(). (#28914) -
Fixed three Jira intake routing problems when factories share a Jira project: (#28870)
-
The latest account-related failure card in the Factory web chat now names the credential the session runs on. (#28704)
-
The activity line under the transcript now follows the whole run instead of disappearing at the first output. (#28834)
-
Anonymous callers can no longer browse files, list artifacts or resolve codebase paths in a Factory deployment with authentication enabled; those routes now require a signed-in user. (#28829)
-
A reply's footer (model, copy button and time) now appears once the last word of the whole reply is on screen, including earlier steps of a reply split across several messages. (#28834)
-
More failed-run cards in the Factory web chat now come with a button that fixes them: (#28695)
-
Updated Factory's browser tab icon to use the same black tile as Studio and Platform in both light and dark mode, including the session status icons. (#28840)
-
Signal, system reminder and skill rows in the transcript now read like the agent's activity lines: same left edge, same type and weight, a readable name instead of the signal's id, and the message as prose instead of monospace. (#28834)
-
Factory triage now handles Jira, incident.io, and GitLab cards. (#28775)
-
Factory no longer reports a stalled sandbox as "git is not installed". (#28792)
-
Linking a GitHub repository to a Factory is faster: it now looks up the one selected repository instead of listing every repository the GitHub App can see. (#28551)
-
Fixed Factory intake losing or reviving routing in edge cases: (#28971)
-
Browser screenshots now show as images in the session transcript instead of their raw base64 data. (#28503)
-
Brought back the + next to Sessions in the Factory sidebar, beside the properties menu. (#28761)
@mastra/fastembed@1.3.6
Patch Changes
-
Fixed custom FastEmbed models failing to load when
tokenizer_config.jsonstorespad_tokenas a serialized AddedToken object (as exported by older Transformers versions). (#28893) -
Direct
FlagEmbeddingandSparseTextEmbeddingcalls now throw a clear error when a custom ONNX model returns a different number of output rows than input texts, instead of returning the wrong number of embeddings or failing with an unrelated TypeError. (#28892)
@mastra/inngest@1.11.0
Minor Changes
- Added execution outcomes and stop-reason details to Inngest agent and workflow traces. Separate resumed-segment tracing and termination-cause preservation across workers are not yet supported; missing causes can produce
unknownoutcomes. A run deadline received by the caller may not be recorded aslimit / timeoutwhen another worker owns finalization. (#28703)
Patch Changes
- Inngest durable agents now report time to first token on their generation and agent run spans. (#28699)
@mastra/koa@1.7.25
Patch Changes
- Koa no longer prints 4xx errors or 501 Not Implemented errors to
console.error. (#28925)
@mastra/lance@1.6.0
Patch Changes
- Fixed the
LanceVectorStore.create()type soidis optional when you pass connection options. (#28969)
@mastra/langsmith@1.4.1
Patch Changes
- Spans keep
timeToFirstTokenMsout of LangSmith metadata, as withcompletionStartTime, which already drives thenew_tokenevent. (#28699)
@mastra/livekit@0.5.3
Patch Changes
- Fixed a build failure when
@livekit/protocolresolves to a different version than the onelivekit-server-sdkdepends on. (#28672)
@mastra/mcp@2.7.0
Minor Changes
-
Added Mastra-storage-backed tasks to
MCPServer(experimental). Closes #28337. (#28521)- Shared tasks. A server registered on a Mastra instance with background tasks enabled keeps its tasks in Mastra storage. Any instance that shares the storage can check, answer, cancel, and run a task.
- Survives restarts. Answers outlive the instance that took them. Each instance picks up tasks left by stopped instances as soon as the server is registered.
- Cleans up after itself. Expired tasks fail, and finished tasks are deleted when their TTL ends.
- Limits apply everywhere.
maxOpenTasksPerClientandmaxOpenTaskscount tasks across all instances. - Private by default. Storage never keeps task ids or bearer tokens. A readable
metadata.clientLabelshows which client sent a task. - One trace per task. The call that creates a task, each run of the tool, and later answers and cancels share one trace. Polls aren't traced.
- Opt-in recovery. When the instance running a task stops, the task fails by default. Set
recover: trueto run the tool again on another instance. Only use it for tools that are safe to repeat.
const server = new MCPServer({ id: 'reports', name: 'Reports', version: '1.0.0', tools: { buildReport }, tasks: { tools: { buildReport: { mode: 'always', recover: true } } }, }); export const mastra = new Mastra({ storage, backgroundTasks: { enabled: true }, mcpServers: { reports: server }, });
-
MCP tasks in Studio (#28629)
MCP servers with
tasksenabled now report which tools run as tasks (alwaysorauto, whether clients must support tasks to call them, and whether they recover after a restart), and give Studio an admin view to list tasks (filtered by status, tool or client, page by page), find one by its task id, and cancel or answer them. Cancelling or answering from Studio goes through the same path as the protocol'stasks/cancelandtasks/update. The view shows hashed task and client keys and a readable client label, never raw task ids or credentials. Custom task stores can opt in by implementingMCPTaskStoreAdmin(list,updateByKey,cancelByKey); stores without it are reported as not listable.The MCP client now writes an
mcp-taskprogress entry (MCPTaskProgress) to a tool's output stream when its call continues as an MCP task and whenever the task's status changes, so UIs can show the remote task id and status while it runs.import type { MCPTaskStore, MCPTaskStoreAdmin } from '@mastra/mcp'; // A custom store opts into Studio's task list by also implementing MCPTaskStoreAdmin. class MyTaskStore implements MCPTaskStore, MCPTaskStoreAdmin { // ...the MCPTaskStore methods, plus: async list(options) { /* newest first, with nextCursor when more match */ } async updateByKey(taskKey, inputResponses) { /* 'done' | 'ignored' | 'not_found' */ } async cancelByKey(taskKey) { /* 'done' | 'ignored' | 'not_found' */ } }
-
Added experimental support for answering tool calls with MCP Tasks (
io.modelcontextprotocol/tasks). A long-running tool can now hand the client a task right away; the client polls for the result, so the work isn't limited by HTTP timeouts. Clients that don't support tasks keep getting normal answers. (#28420)const server = new MCPServer({ name: 'Reports', version: '1.0.0', tools: { buildReport, lookupCustomer }, tasks: { tools: { buildReport: 'always', lookupCustomer: { mode: 'auto', syncWindowMs: 1000 }, }, }, });
Tasks are bound to the caller that created them, can be cancelled (which aborts the tool's
abortSignal), and expire after 24 hours by default. A tool that callssuspend()inside a task asks the client for input, and runs again withresumeDataonce the client answers. Tasks are kept in memory by default; passtasks.storeto keep them elsewhere. Each client can hold up to 100 open tasks and the server up to 1000 (tasks.maxOpenTasksPerClient,tasks.maxOpenTasks). Past that, an'always'tool answers with an error and an'auto'tool answers synchronously. The in-memory store also caps how many tasks it keeps, finished ones included, at 1000 per client and 10000 in total (tasks.maxRetainedTasksPerClient,tasks.maxRetainedTasks), refusing new tasks the same way.Task runs use the request context stored when the task was created, with the bearer token removed, and get no
context.mcp. The only exception is the first run of an'auto'tool, which started under the request. Read cancellation fromcontext.abortSignal. -
Added MCP tasks for workflows and agents served by
MCPServer(experimental). Thetasks.workflowsandtasks.agentsoptions letrun_<workflow>andask_<agent>answer with a task, using the same modes as tools. (#28619)const server = new MCPServer({ id: 'reports', name: 'Reports', version: '1.0.0', workflows: { nightly }, agents: { researcher }, tasks: { workflows: { nightly: 'always' }, agents: { researcher: { mode: 'auto', syncWindowMs: 1000 } }, }, });
Workflows: a workflow run starts once per request, and the task follows that run. A suspended step with a
resumeSchemathe client can fill in asks for input through the task, and the answer resumes the run. Other suspended steps leave the taskworkinguntil the run is resumed some other way. Cancelling the task cancels the run, even while it waits for input. A run that fails completes the task withisError: true. With tasks kept in Mastra storage under a stableMCPServerid, when a server stops mid-run, Mastra's workflow recovery continues the run on the next instance and the task follows it. Tasks kept in memory end with the server.Agents: an agent task runs the agent's
generate()call as the task's work. Cancelling the task aborts that call, and agent tasks supportrecover. An agent that pauses for tool approval completes the task with an error, since tasks can't answer approvals yet.tasks.defaultnow also applies to workflows and agents not listed intasks.workflowsortasks.agents, andtasks.requiredtakes the names clients call, such asrun_nightly. -
Added experimental support for MCP tasks in
MCPClient. A server can now answer a long-running tool call with a task instead of holding the request open. Mastra waits for the task and returns its final result, so tool calls keep the same shape. (#28343)Turn it on per server:
const mcp = new MCPClient({ servers: { reports: { url: new URL('https://example.com/mcp'), tasks: { maxWaitMs: 30 * 60_000 }, inputRequests: async ({ params }) => ({ action: 'accept', content: { ok: true } }), }, }, });
- A task that needs input is answered through your
inputRequestshandler and sent back withtasks/update, without calling the tool again. - Aborting the tool call or reaching
maxWaitMscancels the remote task and ends the call right away, even while a poll or your input handler is still running. A cancelled or failed task becomes a tool error. - Polling rides out a server restart or deploy: the client reconnects and retries with backoff for up to 5 minutes before failing the call.
- On an agent with background tasks enabled, these tools run as background tasks, so the agent keeps working while the remote task runs.
- The tool call span carries
mcpTaskKeymetadata (a hash of the task id) to match it with the server's spans for that task. mcp.tasks.get,mcp.tasks.updateandmcp.tasks.cancelread, answer and cancel a task directly.
Tasks are off unless
tasksis set, so existing servers behave as before. See #26000. - A task that needs input is answered through your
Patch Changes
- Fixed MCP task tool calls failing when a server sends a valid ISO 8601
createdAtthat JavaScript can't parse, such as20261029T120000Z. (#28899)
@mastra/memory@1.40.0
Minor Changes
-
Added compact observer failure details and a reference to the failed memory-operation span when Observational Memory interrupts an agent. Observer context-window exhaustion is recorded as a limit, while the affected agent records an interruption. (#28703)
Failure markers in streams and persisted messages, and tripwire metadata, now include
observerFailurewith the observer span reference and failure classification.
Patch Changes
-
Deleting a thread with
memory.deleteThread()now also deletes the thread's live event stream from the Mastra pubsub (for example, Redis). (#28968) -
Fixed observational memory search scanning the whole vector table on PostgreSQL. (#29058)
-
Fixed observational memory re-embedding every earlier observation group on each cycle when vector retrieval is enabled. (#28150)
@mastra/observability@1.23.0
Minor Changes
-
Added execution outcomes and stop-reason details to span lifecycle methods and both Mastra exporters. Mastra-generated stop-reason context contains compact facts and span references; custom context is copied and safely serialized before export. (#28703)
Custom producers retain support for arbitrary outcome strings and context objects, including when spans are rebuilt. Framework-generated context omits raw signals, tripwire payloads, and other unbounded diagnostic data. Model chunk content remains downstream of output processors.
Custom spans can record how they ended:
span.end({ outcome: 'limit', stopReason: 'max_steps', stopReasonContext: { maxSteps: 5 } });
To end a different span and its open descendants after cancellation:
runSpan.end({ endTree: true, outcome: 'abort', stopReason: 'caller_cancelled' });
Processed
MODEL_CHUNKspans are now children ofMODEL_STEP, independent of output-processor buffering. Agent-streamMODEL_INFERENCEspans retain provider timing and usage but no longer record response output; use processed chunks orMODEL_GENERATIONoutput instead. Public native outcome fields also allownull, which exporter consumers must handle.Ended spans ignore subsequent
end(),error(), and outcome updates, preserving the exported result. Anullor absent outcome means no outcome was recorded; useendTimeto determine completion. -
Exported spans now include
modelandproviderwhen the span's own attributes have both. Storage and exporters receive them as top-level fields. An agent run carries the model it started with, and model steps and inferences carry the model that each call used. (#28849) -
Model and agent spans now report
timeToFirstTokenMs: the milliseconds from the start of the model call to its first streamed chunk. UnliketimeToFirstChunkMs, it leaves out the work Mastra does before calling the model, such as input processors. (#28699)The value is measured once on the model inference span that streamed the chunk, and model step, model generation and agent run spans carry the value of the nearest such inference below them, as a
timeToFirstTokenMsattribute. Spans that never streamed a chunk have no value. A fallback model attempt now opens its own model inference span, named after the model it calls, so its value and label are its own, and the failed attempt's span ends with the error. When an in-process agent retries a failed model call, the retry happens inside one inference span, so the value includes the failed attempt and the retry wait.
Patch Changes
- Update bundled model pricing with the latest prices from models.dev (
9f03685)
@mastra/otel-exporter@1.5.0
Minor Changes
-
Added explicit
custom.endpoints.tracesandcustom.endpoints.logsURLs, passed through without rewriting paths or query parameters. Omitted signals are not exported. Configure eitherendpointorendpoints, never both; this is enforced in TypeScript and at runtime. The singularendpointoption retains its legacy behavior. (#28820)Added opt-in
metadataMappingto copy selected metadata into top-level span attributes. Existing exports remain unchanged without a mapping:new OtelExporter({ provider: { custom: { endpoints: { traces: 'https://collector.example.com/api/otel/traces', logs: 'https://collector.example.com/events', }, }, }, metadataMapping: { userId: 'user.id', tenantId: 'tenant.id' }, });
Mapped values are strings; objects and arrays are JSON-encoded. Namespaced
mastra.metadata.*attributes are retained.
@mastra/pg@1.34.0
Minor Changes
-
PostgreSQL trace, span and thread queries now return and filter by
timeToFirstTokenMs, and trace and span queries can sort by it, slowest or fastest first. Traces use the root span's value. Traces and spans without a value come last. (#28699)orderBy: [{ field: 'timeToFirstTokenMs', direction: 'desc' }];
Patch Changes
-
Fixed
PgVector.query()skipping HNSW indexes when a filter orminScorewas set (#28949). (#29058) -
Experiments and their results now keep the run times they were given. (#28836)
-
Fixed
PostgresStoreVNextobservability dropping every span, metric, log, score, and feedback event once the day partitions created byinit()ran out. (#28680) -
Fixed
PgVector.createIndex()timing out withmetadataIndexeswhen the connection pool has a single connection (pgPoolOptions: { max: 1 }). (#29058) -
Fixed a startup crash when multiple processes, such as an API and a worker, initialize the same empty Postgres schema at the same time. (#28670)
@mastra/playground-ui@67.0.0
Minor Changes
-
ActivityStretchLinenow shows theAgentIndicatorin place of its icon. While its tool calls run, the indicator takes the shape of the call in progress (a terminal for a command, an eye for a read, a magnifier for a search), then rests as the agent icon once they finish. A line that is already finished when it mounts draws a still icon instead. (#28834)A live line draws before its first row lands. Its new
currentprop (ActivityCallState[]) lets the line name the running call while that call's row is still held back. The line reads in medium weight and no longer draws a rule under itself, so consecutive activity lines keep one rhythm.A live line writes its first words in from the left. When what it says changes, the old words roll up and blur away while the new ones roll into place, and the line glides to its new width. A line that is already finished when it mounts never moves, and opening one shows its rows without fading them in. Words too long for the line fade out at its right edge instead of ending in an ellipsis.
Only a top-level activity line keeps its disclosure chevron visible. Rows nested inside another activity show theirs on hover or keyboard focus, and take no room until then.
-
Activity details now read in the body font at the label's size, so a row reads as one phrase.
ActivityDetailno longer takesfont, andActivityHeadlineandActivityItemno longer takedetailFont. Drop the prop: (#28834)// Before <ActivityDetail font="mono">{path}</ActivityDetail> <ActivityItem {...item} detailFont="mono" /> // After <ActivityDetail>{path}</ActivityDetail> <ActivityItem {...item} />
Wrap a transcript in
ActivityWorkingDirectoryto show paths under the agent's working directory relative to it, both on screen and in the row's accessible name:import { ActivityWorkingDirectory } from '@mastra/playground-ui/components/ai/activity'; <ActivityWorkingDirectory path="/sandboxes/7b45/mastra">{transcript}</ActivityWorkingDirectory>; // "/sandboxes/7b45/mastra/packages/core/package.json" reads "packages/core/package.json"
Use
useRelativePaths()to rewrite your own text the same way. -
Added
AgentIndicator, an animated glyph that shows what an agent is doing. It rests as the agent icon and morphs between one pose per kind of work:idle,memory,thinking,reading,searching,finding,browsing,editing,running,deleting,skill,tool,waiting,done,error,contextandnotice. It draws incurrentColor, sizes withclassName, and holds still when the user prefers reduced motion. Passstillto keepidleandwaitingfrom breathing when the indicator marks something that is over. (#28834)import { AgentIndicator, agentGlyph } from '@mastra/playground-ui/components/AgentIndicator'; <AgentIndicator state="running" className="size-4" />; const RunGlyph = agentGlyph('running'); <RunGlyph className="size-4" />;
agentGlyph(state)returns any pose as a still, Lucide-compatible icon, drawn from the same frames the indicator morphs through. Tool, signal, skill, reasoning and memory rows now use these glyphs, so a row's icon and the live indicator always show the same shape.presentToolreturns the call'sposenext to itsicon. -
Added
ContinuingandUnwatchedto the arrival primitives.Continuingmoves something the reader already sees into a new place without fading it in, and cancels the fade-in of any arriving element around it.Unwatchedmarks content whose fade-in its parent plays, so nothing inside it fades in on its own. AShimmerinside anotherShimmernow keeps its own text visible while it moves. (#28834)import { Continuing, Unwatched } from '@mastra/playground-ui/components/Arrival'; // The run's indicator steps from the tail onto the line that now describes it, without fading in again <Continuing> <ActivityStretchLine members={members} live /> </Continuing>; // A folded body whose disclosure plays the entrance for everything inside it <Unwatched>{body}</Unwatched>;
-
Unified metrics chart card headers on one style: a medium-weight title with the card's total on the right. Descriptions, labelled summaries, card tabs and numbers in chart legends are gone; a card with several views switches them from its title. (#28860)
Breaking: removed
ChartCardpropsdescriptionandsummary. Usetotal(andisLoading).MetricsCard.Description,MetricsCard.TitleAndDescription,MetricsCard.Summary,MetricsCard.Tabs,MetricsCard.Tab, andMetricsCard.Title'sweightprop (titles are always medium).aggregateonMetricsLineChartSeriesand thedataprop ofMetricsLineChartLegend.MetricsKpis.
// Before <ChartCard title="Latency" description="Duration percentiles." summary={<MetricsCard.Summary value="4.6s" label="P95" />} toolbar={<MetricsCard.Toolbar><MetricsCard.Tabs value={view} onValueChange={setView}>…</MetricsCard.Tabs></MetricsCard.Toolbar>}> // After <ChartCard title="Agent latency" total="4.6s" titleMenu={{ value: view, onValueChange: setView, options: [{ value: 'agents', label: 'Agent latency' }, …] }}>
Added
ChartCardvariant="hero"andMetricsCard.Headline, for one large chart that is the page's subject: a label over a large total, the change against the previous period beside it, and controls on the right.totalcan take one value per series ([{ label, value, color }]), each after a marker in its series' color, for a card charting a few different things. Resources and threads uses it.AgentRunsCardswitches to Failure rate from its title (onViewErrors,onFailureTimeRangeClick).
Changed
- Chart cards in a grid row end at the same height, and their charts line up in every state (loading, empty, error).
- On ranges that start and end at the same time of day (last 24 hours, 3 days, 7 days), small charts label their ends by date alone ("Oct 28", "Oct 29"), and those labels reach the plot's edges.
- A chart's tooltip stays above the chart cards that follow it on the page.
- "View in traces" buttons use the Traces icon, card action buttons are smaller and line up with the title, and a card's title menu closes once a view is picked.
- A series with a single point gets a dot; other series draw lines only.
-
Added Outcome and Stop reason columns to the Traces and Spans tables in Studio. Outcome shows how the run ended (for example
Limit), and Stop reason shows why (for examplemax_steps), with the stop reason context on hover. Rows without a recorded outcome show their error-based status in the usual colors, with a note that the outcome was not recorded. (#28881)Added Outcome and Stop reason filters to the Traces and Spans filter bars, including an "is not recorded" option for runs without an outcome. The filters appear only when the storage can filter on these fields.
Filter bar fields can now relabel an operator with
operatorLabels, for example{ notExists: 'is not recorded' }. -
Added helpers that turn an observability list page's URL into a saved view and apply a saved view back to the URL (
savedViewFromSearchParams,applySavedView). Parts of a view the page no longer supports, such as a removed filter field or column, are dropped one by one and the rest still applies. The spans and threads pages now keep their time range, filters and sort in the URL, so they survive a reload and can be shared. (#28715)import { applySavedView, savedViewFromSearchParams, } from '@mastra/playground-ui/domains/observability/saved-view-state'; // Save the current page state as a view. const view = savedViewFromSearchParams('spans', new URLSearchParams(window.location.search)); // Later, apply it: `params` is the URL to navigate to. const { params } = applySavedView(view, new URLSearchParams(window.location.search));
-
SignalActivityandSkillMessagetakeappearance="inline"to read like the agent's own activity lines. An inline signal is one line: its icon, a readable name (factory-environmentreads as "Factory environment"), and the mode in plain words. It opens onto the message as prose, with line breaks kept and never in monospace, and a message short enough to fit stays on the line, wrapping on a narrow screen instead of being cut off.ActivityLabeltakeswrapfor that. (#28834)<SignalActivity kind="state" label="factory-environment" mode="snapshot" message="1 repository at /workspace" appearance="inline" />
-
Added a full-width histogram of counts and errors over time to the Traces and Spans pages, between the filter bar and the list. It follows the list's filters and time range, and only appears when the server supports trace or span aggregates. (#28816)
Drag across the histogram to zoom in: the list and the chart switch to a custom time range covering the selected bars.
MetricsStackedBarChartexposes this as an opt-inonZoom({ start, end })prop that reports the first and last selected column.Other charts can reuse the same drag-to-select behavior from
@mastra/playground-ui/primitives/charts/drag-selection. It works with bar and line charts (passlayout="point"toDragSelectionBandfor line charts).const selection = useDragSelection({ onSelect: ({ start, end }) => zoomTo(start, end) }); <ComposedChart className={selection.className} onMouseDown={selection.onMouseDown} onMouseMove={selection.onMouseMove} onClick={state => { if (selection.consumeReleaseClick()) return; // regular point click }} > <DragSelectionBand selection={selection} count={data.length} /> {/* series */} </ComposedChart>;
When the Traces, Spans, or Threads time range differs from the default, the Time chip shows a × that resets it to the last 7 days.
TraceTimeRangeChipexposes this as an opt-indefaultPresetprop.Chart tooltips now render above the page, so a short chart (like the histogram above a list) or a clipping container no longer cuts them off.
The Columns picker is now an icon button with a "Columns" tooltip, placed before the filters.
<TracesExplorer.Body toolbar={toolbar} chart={<TracesExplorer.Histogram />}> {content} </TracesExplorer.Body>
-
Added time to first token (TTFT) to the Traces, Spans and Threads lists in Studio, alongside time to first chunk (TTFC). (#28898)
Traces: TTFT is a new optional column, sortable like TTFC. Both timings can now be used as filters, on the trace itself or on any of its spans.
Spans: TTFC and TTFT are new optional columns, sortable, and both can be used as filters.
Threads: the list now has a column picker (messages, TTFC, TTFT and more), saved per project and in saved views. Both timings can be used as filters.
Span details: the Timing section now shows the span's time to first chunk and time to first token when it streamed, so the root span shows the same values as the lists.
Trace timeline: a trace opened from the Traces list shows its time to first chunk and time to first token as two dashed rows, both ending on the first chunk, under a "Metrics" row above the spans. Metrics is closed by default; open it to see the timings. You can see at a glance how much of the wait came from the model and how much from earlier steps.
-
Improved the time range filter on the traces and spans pages. (#28894)
- Custom ranges now show the date and time, e.g.
Oct 21, 2:05 PM – 4 PM, instead of the date only. - Clicking a single histogram bar now zooms into that bar's time span, just like dragging across bars.
- Clearing the time range after zooming into the histogram now returns to the range you had before the zoom (for example "Last 14 days" or a custom range), instead of always jumping to "Last 7 days". Clearing again resets to the default range.
- Custom ranges now show the date and time, e.g.
-
Added
DashboardRenderer(@mastra/playground-ui/domains/dashboards), which renders a stored dashboard definition on a 12-column grid. It ships four widget kinds that rebuild today's Metrics cards from JSON:kpis,timeseries,tableandscores. Each widget runs its own queries with the page's time range and filters, and a widget that fails to parse, query or render shows its own error while the rest of the dashboard still renders. (#28724)When the time range, a filter or the view picked from a card's title menu changes, a widget shows a skeleton until its own answer arrives instead of the previous figures, so a slow request never shows one range's numbers under another. A widget re-reading an answer it already has keeps it on screen, dimmed. KPI cards now set
aria-busywhile their reading loads or updates.The renderer reads the page's time range and filters from
MetricsProvider, so render it inside one. Hosts can add their own widget kinds without changing the renderer:import { z } from 'zod/v4'; import { DashboardRenderer, defineWidgetKind } from '@mastra/playground-ui/domains/dashboards'; import { MetricsProvider } from '@mastra/playground-ui/domains/metrics'; const noteKind = defineWidgetKind({ kind: 'note', optionsSchema: z.object({ text: z.string() }), component: ({ views }) => <p>{views[0].options.text}</p>, }); <MetricsProvider preset={preset} filterTokens={tokens} onPresetChange={setPreset} onFilterTokensChange={setTokens}> <DashboardRenderer definition={definition} scope="project" kinds={[noteKind]} onDrilldown={(page, scope) => open(page, scope)} /> </MetricsProvider>;
The
@mastra/corepeer range is now>=1.81.0-0 <2.0.0-0: the renderer imports@mastra/core/storage/dashboards, which earlier core releases don't ship. -
Added saved views to the Traces, Spans, Threads and Logs pages. A host stores the views by wrapping the page in a
SavedViewsProvider; without one the pages look as before. (#29001)import { SavedViewsProvider, SavedViewNameTakenError } from '@mastra/playground-ui/domains/observability/saved-views'; const source = { list: page => api.listSavedViews(page), create: input => api.createSavedView(input), // throw SavedViewNameTakenError on a duplicate name update: (id, patch) => api.updateSavedView(id, patch), remove: id => api.deleteSavedView(id), }; <SavedViewsProvider source={source} queryKeyScope={['project', projectId]} // Optional: views the user may only apply (no rename, delete or save changes) canEdit={view => view.scope !== 'org'} > <SpansPage /> </SavedViewsProvider>;
A searchable view picker sits at the top right of the page header, next to the Traces / Spans / Threads switch. Picking a view sets the page URL and columns exactly as setting the same filters, time range and sort by hand would; saving stores what the URL holds. Parts of an older view the page no longer supports are skipped. Traces hosts place
TracesExplorer.SavedViewsand logs hosts placeLogsSavedViews.Hosts whose list pages open on a range other than the last 7 days pass it as
defaultDatePresettouseObservabilityListFiltersand to the saved views, so that range is left out of the URL and a view of the last 7 days reopens on the last 7 days:const filters = useObservabilityListFilters(urlState, { defaultDatePreset: 'last-24h' }); <ObservabilitySavedViews page="spans" {...urlState} defaultDatePreset="last-24h" />;
Fixed filter chips that stayed on screen after the page replaced its filters (for example when switching views), and a combobox trigger icon that rendered larger than the trigger text.
useSavedViewsnow reports a failed save aserror(wasstorageError), andSavedViewEditortakes asaveCaptionfor the note shown next to Save view. A saved-views controller'srenamenow returns whether the view was renamed (booleanorPromise<boolean>), so a rename form can stay open with the error when the name is taken. -
Added a shared
SkillSimpleFormskill editor (name, description, and Markdown instructions, with an optional read-only mode) at@mastra/playground-ui/domains/skills/skill-simple-form, so apps other than Studio can show and edit skills with the same component. (#28103)import { SkillSimpleForm } from '@mastra/playground-ui/domains/skills/skill-simple-form'; <SkillSimpleForm name={name} onNameChange={setName} description={description} onDescriptionChange={setDescription} instructions={instructions} onInstructionsChange={setInstructions} />;
-
Hover highlights no longer glide between items. DropdownMenu, ContextMenu, Select, Combobox, Command, CommandPalette, FilterBar, PropertyFilter, Pagination and DataList rows each paint their own hover and keyboard highlight, and switch instantly. The current page in Pagination keeps its raised pill without sliding. (#28757)
Removed
@mastra/playground-ui/hooks/use-fluid-hover(useFluidHover,useRegisterFluidHoverItem) and@mastra/playground-ui/components/fluid-hover-highlight(FluidHoverHighlight). Give each row its own hover style instead.- The
highlightClassNameprop ofCommandList. Style the row radius onCommandIteminstead.
// Before const hover = useFluidHover(listRef); <div ref={listRef} className="relative" {...hover.handlers}> <FluidHoverHighlight hover={hover} className="rounded-lg bg-fill" /> {items.map((item, i) => ( <Row key={item.id} ref={el => hover.registerItem(i, el)} /> ))} </div> // After <div> {items.map(item => ( <Row key={item.id} className="rounded-lg hover:bg-fill" /> ))} </div>
// Before <CommandList highlightClassName="rounded-lg">{items}</CommandList> // After <CommandList>{items}</CommandList>
-
Added
METRICS_DASHBOARD, the built-in Metrics dashboard as a dashboard definition. Pass it toDashboardRendererto render the Metrics page: token usage and agent runs (with its failure rate view), latency and distinct IDs, then the breakdown, on org and project pages alike. The definition is frozen; copy it withstructuredClonebefore customising. (#28734)import { DashboardRenderer, METRICS_DASHBOARD } from '@mastra/playground-ui/domains/dashboards'; <DashboardRenderer definition={METRICS_DASHBOARD} scope={scope} onDrilldown={openDrilldown} />;
-
Cleaned up the tables in metrics cards: one table for figures, and a share bar that is only a chart. (#28762)
Replaced
MetricsShareListwithShareBarShareBarshows each row's share of a total: the bar, then the column headers, then a compact legend row per segment. Sorting, the no-bar mode and header placement options are gone; useMetricsTablefor a sortable table of figures.// Before <MetricsShareList rows={rows} columns={columns} valueLabel="Spend" headerPlacement="below-strip" /> // After <ShareBar rows={rows} columns={columns} valueLabel="Spend" />
MetricsTableis ready for full tablespageSizeadds pagination, andisLoadingshows skeleton rows under the real header instead of the empty message.- A column's
descriptionexplains it in a tooltip on the header text, marked by a dotted underline. - A third click on a sorted column turns sorting off, back to
defaultSortor the given order. isRowActivehighlights a row, for example the one a page is filtered to.- Headers now line up with their columns, figure columns share spare width with the card, and every table uses the same 34.5px rows.
<MetricsTable aria-label="Agent runs" columns={[ { id: 'name', header: 'Agent', width: 'minmax(12rem,3fr)', cell: row => row.name }, { id: 'runs', header: 'Runs', align: 'end', description: 'Runs that started in this range.', sortValue: row => row.runs, cell: row => formatCount(row.runs), }, ]} rows={rows} getRowKey={row => row.name} defaultSort={{ column: 'runs', direction: 'desc' }} pageSize={13} isLoading={isLoading} empty="No agent runs in this range." />
New pieces for table rows
SegmentedNameshows a name in parts, such asopenai › gpt-5. The traces timeline uses it for span names.SeriesMarktakes an optionalcolor: without one it is a neutral tile, for tables without a chart.ProviderMarkputs a provider logo in it.
The metrics Breakdown card now uses
MetricsTable, with an icon before each agent, workflow, tool or model name, and each model's provider. -
Added a chip to tool-call cards for calls that continue as MCP tasks. It shows the task's status and elapsed time, with a button to copy the MCP task id. The chip appears for MCP servers the client accepts tasks from: (#28629)
const mcpClient = new MCPClient({ servers: { reports: { url: new URL('https://reports.example.com/mcp'), tasks: {} }, }, });
Patch Changes
-
Added an
inlineappearance toActivityand aflushoption toActivityContent, for activity lines that sit inside prose. (#28709) -
Added
ActivityStretchLineunder@mastra/playground-ui/domains/chat/activity. (#28709) -
Improved the span details panel in Studio traces so each span says what it did before showing its raw payloads. (#28687)
-
Improved metrics chart resizing. (#28700)
-
Fixed markdown tables, lists and paragraphs losing their cell padding, borders, bullets and spacing when a notice's stylesheet loaded before the app's Tailwind entry. (#28834)
-
Redesigned the Studio span details panel so the most useful information is easier to find, especially in narrow columns. (#28856)
-
Shortened the pagination "Previous" label to "Prev" so it matches the length of "Next". (#28825)
-
Chart tooltips on Metrics, Traces and Spans no longer change width as you move between points. (#28833)
-
Improved hover on metrics charts (
MetricsLineChart,MetricsStackedBarChart): (#28701) -
Message footers read at caption size. (#28834)
-
Removed unused internal chart code while preserving the public component API. (#28737)
-
Added
autoScrollStartFollowingandonAutoScrollFollowingChangetoScrollArea. (#28709) -
Added Model and Provider filters to the Spans page. (#28994)
-
Fixed focus rings showing after mouse clicks. (#28694)
-
Security: bump
zodto ^4.6.7 (fixes advisory affecting zod <4.6.6). (#28983) -
Improved the trace span views in Studio: (#28867)
@mastra/react@1.14.0
Patch Changes
-
Added
threadsas a column preferences scope, so the Threads list can save its own column selection. (#28898) -
Experiment details now load instantly from cache when navigating back to an experiment, instead of showing a loading state while refetching. (#28813)
-
useTraceQuerykeeps new traces in order when the list is sorted bytimeToFirstTokenMs. (#28699)
@mastra/redis-streams@0.8.0
Minor Changes
-
Idle topic streams now expire after 7 days by default. Previously
streamIdleTtlMsdefaulted to0, so a pubsub created without it never expired any stream. A production deployment built that way accumulated thousands of never-expiring conversation streams until Redis ran out of memory. (#28968)Every write still refreshes the TTL, so a topic in active use never expires. If a grouped worker can be offline for more than 7 days while publishers are quiet, and must replay the backlog when it comes back, raise the TTL or turn expiry off:
// Before: streams created by this pubsub never expired // After: they expire after 7 days without a write new RedisStreamsPubSub({ url }); // To keep the old behavior, turn expiry off explicitly new RedisStreamsPubSub({ url, streamIdleTtlMs: 0 });
Check your eviction policy before upgrading. Under a
volatile-*policy, Redis only evicts keys that have a TTL. Streams used to have none, so they were safe from eviction; now they can be evicted under memory pressure, losing events. Setmaxmemory-policytonoeviction.Existing streams created without a TTL only get one on their next write. See the reference docs for a runbook to reclaim them.
The pubsub also logs a warning once after connecting if the server has
maxmemoryset with any policy other thannoeviction. Evicting policies (Amazon ElastiCache defaults tovolatile-lru) delete live streams, consumer groups, and lease keys under memory pressure.Also in: @mastra/valkey-streams@0.6.0
Patch Changes
- Fixed
subscribe()hanging forever when Redis accepts the connection but never answers, for example a frozen server or a proxy that has stopped forwarding. (#28941)
@mastra/server@1.81.0
Minor Changes
-
Added
spanQueryOutcomeFieldsandtraceQueryOutcomeFieldsto the observability capabilities response. Span and trace query, aggregate, and discovery routes return501when a request usesoutcomeorstopReasonagainst a store that does not support them. (#28884)const { capabilities } = await client.getObservabilityCapabilities(); if (capabilities.spanQueryOutcomeFields) { // outcome and stopReason predicates, groupBy, and countDistinct are available on span routes }
-
Added Studio routes for an MCP server's tasks: (#28629)
GET /mcp/:serverId/taskslists tasks newest first, 50 per page by default and at most 200. Filter bystatus,surfaceorclient, and passnextCursorfor older tasks.POST /mcp/:serverId/tasks/findfinds one task by its MCP task id, sent in the body.POST /mcp/:serverId/tasks/:taskKey/cancelcancels a task.POST /mcp/:serverId/tasks/:taskKey/inputanswers a task's input requests.
Listing and finding need
mcp:read. Cancelling and answering needmcp:write. Server list and detail responses includetasks_capabilitywhen a server runs tools as tasks. Sessions scoped to a resource get no task list, since MCP clients and Mastra resources are separate identities.const res = await fetch('/api/mcp/reports/tasks?status=input_required'); const { tasks, nextCursor } = await res.json(); if (tasks[0]) await fetch(`/api/mcp/reports/tasks/${tasks[0].taskKey}/cancel`, { method: 'POST' });
-
The span aggregate route accepts
modelandprovideringroupByandcountDistinctwhen the store supports them.getObservabilityCapabilities()reports support asspanAggregateModelDimensions. Against an older store, these requests return501before storage runs. (#28908)const { capabilities } = await mastraClient.getObservabilityCapabilities(); if (capabilities.spanAggregateModelDimensions) { const { rows } = await mastraClient.aggregateSpans({ timeRange, groupBy: ['rootEntityName'], measures: ['count', 'countDistinct.model'], }); }
-
Span query and span aggregate routes accept
modelandproviderfilters when the store supports them.getObservabilityCapabilities()reports support asspanQueryModelFields. Against an older store, these filters return501before storage runs. (#28859)const { capabilities } = await mastraClient.getObservabilityCapabilities(); if (capabilities.spanAggregate && capabilities.spanQueryModelFields) { const { rows } = await mastraClient.aggregateSpans({ timeRange, where: { op: 'eq', left: { path: 'provider' }, right: { literal: 'anthropic' } }, groupBy: ['name'], measures: ['count'], }); }
-
Trace, thread, span and aggregate queries now accept
timeToFirstTokenMspredicates, and trace and span queries can sort by it. The newtraceQueryTimeToFirstTokenandtraceQueryTimeToFirstTokenSortcapabilities tell you whether the configured observability store supports them. When it doesn't, the request returns501with a clear message before the store runs. (#28699)
Patch Changes
-
Agent generate and stream routes now respond with HTTP 400 instead of 500 when the request context fails the agent's
requestContextSchema. (#28918) -
Fixed
GET /api/workspacesandGET /api/workspaces/:workspaceIdreportinghasFilesystem: falseandhasSandbox: falsefor workspaces whose filesystem or sandbox is a resolver function. (#29027) -
Preserve zero-length message context ranges in memory search and use the default range when messageRange is omitted. (#28769)
@mastra/spanner@1.11.0
Minor Changes
-
Added storage for the optional background task fields
expiresAtandmetadata, plus filtering byexpiresAt. Existing tables gain the new columns automatically when the store initializes. (#28425)Fixed background task updates failing with
this.database.runUpdate is not a function, which stopped background tasks from running, suspending, or completing on Spanner.
@mastra/stagehand@0.4.0
Minor Changes
-
Added
setCookies()support toStagehandBrowser. Cookies set before a thread's browser opens are added before it loads any page, so pages open signed in. (#28631)await browser.setCookies([{ name: 'my_app_session', value: token, domain: 'app.example.com', path: '/' }], threadId);
Patch Changes
- Fixed the Stagehand browser not noticing when its Chrome is killed outside Mastra (for example with
killor a crash). (#28794)
@mastra/vercel@1.6.3
Patch Changes
- Fixed the default sandbox instructions to distinguish Mastra's unavailable Workspace mounting tools from Vercel Sandbox's native FUSE support. (#28729)
Other updated packages
Dependency updates only: @mastra/arize@1.3.28, @mastra/arthur@0.4.28, @mastra/auth-better-auth@1.1.10, @mastra/datadog@1.4.20, @mastra/deepeval@0.1.22, @mastra/editor@0.16.6, @mastra/express@1.5.25, @mastra/fastify@1.5.25, @mastra/google-cloud-pubsub@1.1.6, @mastra/hono@1.7.23, @mastra/laminar@1.3.30, @mastra/langfuse@1.6.4, @mastra/mcp-docs-server@1.3.11, @mastra/nestjs@0.2.40, @mastra/next@0.2.39, @mastra/opencode@0.1.40, @mastra/otel-bridge@1.5.20, @mastra/posthog@1.4.5, @mastra/sentry@1.2.30, @mastra/tanstack-start@0.2.39, @mastra/turso@0.1.19