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 which role on them:new Knowledge({ storage, scopes: { ...knowledgeMemoryScopes() } })declares Memory'sthread:/resource:scopes and anorg:scope;{}declares none. A run holding a scope that isn't declared fails. Tools are off by default:tools: truegives the read and write tools,tools: { read: true }the read tools only, andpinnedContext: true(or{ maxPins, maxTokens }) delivers pinned pages every turn and addsknowledge_pin.knowledge.readScopeFor(principals)returns what holders of those scopes can read, for host code reading the store directly.knowledge.explainAccess(runScopes, { scope } | { page })says why a run can or can't reach a scope or page; it's host-only, so never expose it through an agent tool or an unauthenticated route.knowledge.onChange(listener)reports each committed write made throughknowledge.getStorage()(tools included) by id, for live UIs; re-read each page under the viewer's read scope before showing it. A scope declaration can carry adisplayLabelfor UIs, andknowledge.scopeDeclaration(key)returns a declaration's display fields.Knowledge is a tree of pages. The tools are five:
knowledge_search,knowledge_read(a page with its notes, or a scope's pages),knowledge_page(op: 'write' | 'move' | 'merge'),knowledge_note(op: 'add' | 'restamp' | 'remove') andknowledge_pin(op: 'pin' | 'unpin'). Notes are at most about 250 tokens and page descriptions about 100; long text goes in a page body.Search is keyword search by default. Give Knowledge a
vectorstore and anembedderandknowledge_searchblends keyword and semantic results; pages and notes written through the tools are embedded as they're written. Every semantic hit is re-checked against what the run can read before it's ranked, so hidden content never fills result slots. Content written straight to the store isn't embedded.Also in: @mastra/memory@1.41.0
-
Added
knowledge_list, a Knowledge read tool that lists the page tree under a starting point, so an agent can see how a scope or section is organized before searching. Each entry has the refknowledge_readaccepts, its name and child count; passdescriptions: trueto add page descriptions. It goes up to 3 levels deep, takes alimitand acursorfor wide levels, and says when deeper levels were cut off. A result also stops at the same token cap as aknowledge_readbody;moresays how many entries were left out and gives thecursorthat continues exactly where it stopped, at any depth. Scopes the run can't read are left out.tools: { read: true }includes it. (#29207)// Agent tool call knowledge_list({ from: 'docs:acme#guides', depth: 2 }); // → { from: { ref: 'docs:acme#guides', name: 'Guides' }, // entries: [{ ref: 'docs:acme#guides/observability', name: 'Observability', depth: 1, children: 2 }, …] }
Knowledge storage adapters gained
countChildPages({ parentIds, read }), which counts the readable child pages under each parent in one query.Also in: @mastra/libsql@1.29.0, @mastra/mongodb@1.26.0, @mastra/mysql@0.16.0, @mastra/pg@1.35.0
-
Agents can pin a listing of the Knowledge page tree with
knowledge_pin. Passkind: 'list'and an optionaldepth(1 to 3, default 1) to pin the pages under a page instead of the page itself: (#29263){ "op": "pin", "kind": "list", "depth": 2, "page": "docs:acme#guides", "audience": ["org:acme"] }A page can hold a page pin and a listing pin at once; each is pinned and unpinned on its own, and pinning a listing again sets its new depth. Stored listings follow the same rules as config listings: only pages the run can read, one pin slot, shared
pinnedContext.maxTokens. A stored listing that repeats a config listing is shown once.Knowledge storage adapters store a pin
kind(pageorlist) and a list pin'sdepth. Adapters report this with thesupportsPinKindscapability. With a storage adapter from before this release, page pins keep working and pinning a listing is refused before anything is stored, with a message to upgrade the adapter. -
Pin a listing of the Knowledge page tree into an agent's context. A scope's
pinnedContextnow also takes{ list, depth? }entries, so every run holding the scope sees the pages under a scope or page (ref, name and child count each) without callingknowledge_list: (#29263)scopes: { 'org:$orgId': { pinnedContext: [{ list: 'docs:$orgId#guides', depth: 1 }] }, }
A listing shows only pages the run can read, updates when a listed page is added, renamed or removed, takes one pin slot and shares
pinnedContext.maxTokenswith pinned pages. A listing cut short ends with aknowledge_listpointer to the rest. -
knowledge_readreads long pages by line. A body that fits in one read (1,000 tokens) comes back whole asbody. A longer one comes back as numberedlines(12│text) with therangethey cover and the page'slineCount, ending at the last whole line that fits, andmoresays where to read on withfrom: { line }. A line too long for one read, such as a wide table row, is cut by column (from: { line, col }). An agent can also ask for the body'soutline(headings with their line numbers and sizes), read onesection, ormatchone or more terms like grep: only the lines that contain them, withcontextlines before and after (default 2). (#29207)knowledge_read({ page: 'docs:acme#models/anthropic', outline: true }); knowledge_read({ page: 'docs:acme#models/anthropic', section: 'contextManagement' }); knowledge_read({ page: 'docs:acme#models/anthropic', match: ['excludeTools', 'cacheControl'], context: 3 }); knowledge_read({ page: 'docs:acme#models/anthropic', from: { line: 120 } });
Every Knowledge read result now lists anything it cut short in
more, with how to get the rest: the body, notes, child pages (continue withknowledge_list), notes that mention the page (newmentionedInCursor), and search results at their limit.A whole-page read keeps a page's notes, and the notes on other pages that mention it, only while each list stays under 1,000 tokens. A longer list is left out and counted in
more; read it on its own withnotes: trueormentionedIn: true. A read withcursorormentionedInCursornow returns only those notes, without repeating the body.Every part of a body a read returns comes with its
range.knowledge_pagetakes arangewithexpectedVersionand replaces only those lines withbody; an emptybodyremoves them. A range may be the one a read returned or a smaller one inside it, but not wider than one read from its first line shows, so text the writer never saw can't be overwritten. A body write without a range is refused when the stored body is longer than one read returns, and so is a body that still carries the read's line numbers.const { page } = await knowledge_read({ page: 'docs:acme#models/anthropic', section: 'Setup' }); knowledge_page({ op: 'write', page: page.ref, body: editedSetup, range: page.range, expectedVersion: page.version });
The per-result cap is configurable:
tools: { read: { maxTokens: 3000 }, write: true }sets how many estimated tokens oneknowledge_readbody window or match result, and oneknowledge_listresult, returns (default 1,000, at least 100). The write guard for line ranges follows the same cap.const knowledge = new Knowledge({ scopes, tools: { read: { maxTokens: 3000 }, write: true } });
-
Added vector and hybrid workspace search for vector stores that generate embeddings themselves, so no
embedderis needed. (#27022)With MongoDB Automated Embedding, MongoDB embeds each file when it is indexed and the search string when you query:
const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), vectorStore: new MongoDBVector({ id: 'search', uri, dbName, autoEmbed: { model: 'voyage-4' } }), }); await workspace.rebuildSearchIndex(['docs']); await workspace.search('how do I reset a password?');
The index is named
<workspace id>_search_selfembedunless you setsearchIndexName. Workspaces that pass anembedderkeep working unchanged. -
Added opt-in Editor ownership and versioned persistence for static model fallback lists. (#27287)
new Agent({ id: 'support-agent', name: 'Support agent', instructions: 'Help users resolve support requests.', model: [ { id: 'primary', model: 'openai/gpt-4o' }, { id: 'fallback', model: 'openai/gpt-4o-mini' }, ], editor: { model: true }, });
Also in: @mastra/editor@0.17.0
-
Added time to first token and time to first chunk measures to trace and span aggregates, so dashboards can chart streaming latency next to duration. (#29055)
The new measures are
timeToFirstToken.avg,.min,.max,.p50,.p90,.p95,.p99, and.count, plus the same set fortimeToFirstChunk. Traces and spans without a recorded value are skipped instead of counted as 0, and.countreports how many had one. Stores opt in through theaggregate-time-to-first-measuresfeature. -
Suspended background tool calls can now be answered after a reload. (#28998)
Open request saved on the tool call. While a deferred background tool is suspended, the assistant message that holds the call keeps the open request in
metadata.suspendedTools[toolCallId](withsuspendPayload,taskId, andsuspendedAt). The entry is removed when the task resumes or is cancelled, so a chat that reloads the thread shows only requests that are still open.Resumed tools see what they asked for. A resumed background tool now receives the payload it suspended with in
context.agent.suspendPayload, the same as a foreground tool:execute: async (input, context) => { const { resumeData, suspendPayload, suspend } = context.agent; if (!resumeData) return suspend?.({ question: 'Approve the refund?' }); // suspendPayload is { question: 'Approve the refund?' } };
Resume only the suspension you read.
BackgroundTaskManager.resume()takes an optionalsuspendedAt, and refuses the resume when the task has resumed or suspended again since:await mastra.backgroundTaskManager.resume(taskId, { approved: true }, { suspendedAt: task.suspendedAt });
The agent reports a deferred task's result after its stream ends. When a deferred background task finishes and no
untilIdlestream is open for its thread, the agent now gets abackground-tasknotification signal on the thread. An idle thread wakes and the agent reports the outcome; a run already active on the thread gets the signal on its next model request. Before, the result was only written to memory and waited for the next user message. This coversstream()calls withoutuntilIdleand tasks resumed after they suspended, for example an MCP task answered from the chat. Streams usinguntilIdleare unchanged: they still run their own follow-up turn, and no signal is sent. Cancelling or aborting anuntilIdlestream still means no reply for the tasks it was waiting for.Opt in to ending woken runs after their turn. A run that a signal starts on an idle thread still stays open until the background tasks it dispatched finish. Set the temporary
wokenRunsEndAfterTurnoption to end it after its own turn instead; each task then wakes the thread when it finishes. A signal's ownifIdle.streamOptions.untilIdletakes precedence either way.new Mastra({ backgroundTasks: { enabled: true, wokenRunsEndAfterTurn: true } });
In the next major release, ending after the turn becomes the only behavior and
wokenRunsEndAfterTurnis removed. PassuntilIdle: trueinifIdle.streamOptionsto keep a woken run open; channels and agent controller sessions already do.Turn off the completion signal. Set
notifyAgentOnFinish: falsein thebackgroundTasksconfig when your app sends its own completion signal, for example fromonTaskComplete. WithwokenRunsEndAfterTurnalso on, your app must report the tasks of woken runs itself. -
Added
knowledge.upsertPage(),knowledge.removePage(),knowledge.addNote(),knowledge.updateNote()andknowledge.removeNote()so apps can sync outside content, such as docs, into Knowledge from server code. A page is identified by its scope and key path, so running a sync again updates the same pages and skips unchanged ones. Notes on synced pages are kept, pages are embedded when Knowledge has a vector store, and change listeners are told. (#29159)await knowledge.upsertPage({ scope: 'docs:acme', path: 'guides/install', name: 'Install', body: markdown }); await knowledge.removePage({ scope: 'docs:acme', path: 'guides/old' }); const note = await knowledge.addNote({ scope: 'docs:acme', path: 'guides/install', text: 'Needs Node 20.' }); await knowledge.updateNote({ id: note.id, text: 'Needs Node 22.' }); // returns a note with a new id
-
Fixed a recovered thread run losing its thread lease when the execution it replaced finished. Previously, when a run was recovered in another process, the original execution's exit released the shared lease while the recovered execution was still running, so the thread looked free and a new run could start on it. Each execution now holds the lease under its own generation, so only the live execution can renew or release it. (#28947)
Also fixed run IDs that contain the internal handover marker, or look like an encoded lease owner, being misread as a different run. Any caller-provided run ID now round-trips unchanged.
Upgrading shared deployments
Drain active thread runs and restart API and worker processes together. Older processes do not recognize the new lease owner format: they cannot see a run as live, even while it is renewing its lease. Follow-ups that an older process forwards to a run on this release are still delivered.
Patch Changes
-
Moved the agent stream layer (stream topics, event publishing, replay cache setup and abort requests) out of
DurableAgentinto a shared internal module. (#28416) -
Fixed resuming suspended agent runs across server instances when a process retains an earlier run segment. (#29065)
-
Update provider registry and model documentation with latest models and providers (
4d3ebbd) -
Fixed thread aborts for durable agents on multi-server deployments. (#28345)
Also in: @mastra/server@1.82.0
-
Updated
chatand the Chat SDK adapters to ^4.44.0, so all Mastra channel packages resolve the samechatversion. (#29285)Also in: @mastra/factory@0.27.0, @mastra/slack@1.8.1, @mastra/telegram@0.2.5
-
Fixed "Tenant model credential policy could not be resolved: the request has no authenticated user" errors that recurred on every message in a session with a pending tool approval. (#29231)
-
Fixed durable agents (
durable: true) ending themodel_generationtrace span withoutresponseIdandresponseModel. (#29180) -
Improved
validateStepRequestContexttyping. (#29080) -
Fixed skills not loading on Windows when the workspace
basePathuses a short folder name such asC:\Users\ABCDEF~1\..., whichos.tmpdir()can return. (#29078) -
Fixed a signal sent to a thread while an
agent.generate()turn was running starting a second run that could not see that turn. (#28953) -
Fixed metadata filters in the in-memory store returning records whose nested metadata had different keys from the filter when one side contained an
undefinedvalue. (#29101) -
Improved how Knowledge reads fenced code and headings in page bodies, to match CommonMark: (#29260)
-
Knowledge notes returned by
knowledge_note,knowledge_readandknowledge_searchnow carrymentions, mapping each[[ref]]in the note to the page id it resolves to, so a chat can link them. (#28942) -
knowledge_listnow asks the store for only the child scopes the agent can read, and counts them in one call per level, instead of reading every child scope and dropping the unreadable ones. (#29260) -
Knowledge pages too long for the embedder are now searchable by meaning. (#29218)
-
Knowledge page and note writes now retry a failed embed or vector write a few times over about 10 seconds before giving up. (#29288)
-
Added read-only page-store readers to
@mastra/core/knowledgefor browsing UIs:listKnowledgeTreeChildren(one level of the page tree, child scopes first),readKnowledgePage(a page with its readable ancestors, notes, pin, referenced pages and notes that mention it),readKnowledgePageGraph(a bounded page graph with parent and mention edges, limits inDEFAULT_KNOWLEDGE_PAGE_GRAPH_LIMITS; mentions come from every note on a walked page until a graph-widemaxNotesbudget runs out, which setstruncated),searchKnowledgeExplorerPagesandreadKnowledgePageActivity. (#28362) -
Knowledge tool results now name each page by the ref
knowledge_readaccepts. (#29207) -
Fixed pinned Knowledge listings stopping at 100 pages per level. (#29298)
-
GET /knowledge/:knowledgeKey/pages/:pageIdalso opens a page by the reference tools name it with: a scope address (site:mastra) orscope#key/path(site:mastra#topics/observability). (#29215)Also in: @mastra/server@1.82.0
-
Sped up
readKnowledgePageGraph, which loads the Studio Knowledge graph, on large page trees. (#29288) -
Added an index of the
[[links]]in Knowledge page bodies, so a host can count and list which pages link where without loading every page. (#29260) -
Added
readKnowledgePageLinkCountsandreadKnowledgePageLinksto@mastra/core/knowledge: exact link counts for any set of pages and one page's links with the pages at their other ends, read from the store's link index and filtered by the caller's read scope. (#29260) -
Knowledge page paths ignore leading, trailing and doubled slashes, so
docs:acme#models/gateways/names the same page asdocs:acme#models/gateways. (#29207) -
pinnedContext.maxTokensno longer has a 2,000-token ceiling. (#29257) -
Fill pinned Knowledge listings level by level within their token budget. (#29263)
-
The
knowledge_readtool now tells the model that a read from a line runs until the page ends or the read's token cap is reached, and thatlimitcaps notes, children and mentioning notes, not lines. (#29258) -
Removed
knowledge.reindex(). (#29256) -
Fixed read-only Knowledge runs filling storage with empty scopes. (#29288)
-
knowledge_searchnow finds pages that match any word of the query, not only pages that match every word. (#29207)Also in: @mastra/libsql@1.29.0, @mastra/mongodb@1.26.0, @mastra/mysql@0.16.0, @mastra/pg@1.35.0
-
knowledge_searchhandles queries agents commonly write. (#29207) -
Knowledge tree child counts now say when they were capped. (#29216)
Also in: @mastra/playground-ui@68.0.0
-
Each page in a Knowledge tree level (
GET /api/knowledge/:knowledgeKey/tree,listKnowledgeTreeChildren) now carrieschildCount: how many readable pages, and for a scope how many readable child scopes, sit directly under it. (#29216)Also in: @mastra/server@1.82.0
-
Stored spans now use the service name from the Observability config when the span metadata does not set one. (#29201)
-
Fixed
getStepResultandgetStepResultAsyncreturningnullfor the loop body inside.dowhile()and.dountil()conditions on the evented workflow engine. (#26999) -
Fixed approving a tool loaded by
ToolSearchProcessorafter a server restart. (#29251) -
Fixed
run.cancel()leaving suspended nested workflow runs suspended after the parent was canceled. (#29170) -
Fixed
Property '#private' is missingwhen passing a voice provider such asnew OpenAIRealtimeVoice()to an agent'svoiceoption or toCompositeVoice(#29016). (#29074) -
Fixed signal-started runs to use the
ifIdle.streamOptions.runIdyou provide. (#29152) -
Fixed progress from tools wrapped with
createStepfrom@mastra/core/workflows/eventednever reaching the workflow stream. (#29165) -
Fixed controller tool status for validation failures and explicit error results, including delegated tool results. (#28812)
-
Fixed directly resumed nested workflows in the evented engine so they continue suspended parent runs. (#25979)
-
Fixed type declarations for bundled AI SDK packages. (#29171)
-
Fixed retained workflow handles and abort listeners after durable agent runs settle (#29009) (#29065)
-
Fixed dataset experiments that target a workflow so their traces carry the experiment ID. (#29245)
@mastra/agent-builder@1.2.0
Minor Changes
-
Added
createAgentBuilderWorkflows(), which returns a new set of agent-builder workflows on every call. A workflow uses the storage of the last Mastra instance that registered it and keeps its unfinished runs, so processes that run several Mastra instances should give each one its own set instead of sharingagentBuilderWorkflows.createAgentBuilderTemplateWorkflow(),createWorkflowBuilderWorkflow()andcreatePlanningAndApprovalWorkflow()build a single workflow the same way. The existing exports are unchanged. (#29253)import { createAgentBuilderWorkflows } from '@mastra/agent-builder'; import { Mastra } from '@mastra/core'; const mastra = new Mastra({ workflows: createAgentBuilderWorkflows() });
@mastra/arize@1.3.29
Patch Changes
-
Fixed
import '@mastra/arize',import '@mastra/arthur', andimport '@mastra/laminar'(when installed next to@mastra/otel-exporter) failing in ESM projects with "The requested module '@opentelemetry/api' does not provide an export named 'SeverityNumber'". (#29265)Also in: @mastra/arthur@0.4.29, @mastra/laminar@1.3.31
@mastra/clickhouse@1.29.0
Minor Changes
-
Added
requestId,serviceName, andexperimentIdfilters to advanced trace queries at trace scope and insidespans.some/spans.none. No migration is needed. (#29194)Also in: @mastra/duckdb@1.18.0, @mastra/pg@1.35.0
-
Added support for
timeToFirstToken.*andtimeToFirstChunk.*measures in trace and span aggregates. Rows without a recorded value are skipped, never counted as 0. (#29055)import { planSpanAggregate } from '@mastra/core/storage'; const observability = await storage.getStore('observability'); if (observability?.getFeatures().includes('aggregate-time-to-first-measures')) { const { rows } = await observability.aggregateSpans( planSpanAggregate({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-02T00:00:00.000Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'model_inference' } }, measures: ['timeToFirstToken.p50', 'timeToFirstToken.p95'], }), ); }
Also in: @mastra/duckdb@1.18.0, @mastra/pg@1.35.0
@mastra/client-js@1.59.0
Minor Changes
-
Added the
aggregateTimeToFirstMeasuresobservability capability. Trace and span aggregate requests fortimeToFirstToken.*ortimeToFirstChunk.*measures now return501against stores that can't compute them, and field discovery omits those measures for such stores. (#29055)const { capabilities } = await mastraClient.getObservabilityCapabilities(); if (capabilities.traceAggregate && capabilities.aggregateTimeToFirstMeasures) { const { rows } = await mastraClient.aggregateTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-02T00:00:00.000Z' }, groupBy: ['entityName'], measures: ['timeToFirstChunk.p95', 'timeToFirstChunk.count'], }); }
Also in: @mastra/server@1.82.0
-
Added
client.resumeBackgroundTask()to answer a suspended background task from a client. (#28998)const task = await client.getBackgroundTask(taskId); await client.resumeBackgroundTask(taskId, { approved: true }, { suspendedAt: task.suspendedAt });
-
Added read-only Knowledge routes on the Mastra server (
GET /knowledge,/knowledge/:knowledgeKey/tree,/pages/:pageId,/graph,/search,/activity) and matchinglistKnowledge()/getKnowledge(key)methods on the JS client. (#29069)GET /knowledge/:knowledgeKey/eventsis a Server-Sent Events stream of the writes the caller can read, as{ kind, op, pageId?, scopeId?, at }; ids the caller can't read are left out. The server ends each stream after five minutes so a reconnect re-checks the caller's access; reconnect when it ends. Follow it from the client withgetKnowledge(key).events({ signal }).The routes are opt-in: they are not part of the default route set, so register them explicitly before they are served.
import { KNOWLEDGE_ROUTES } from '@mastra/server/server-adapter'; new Mastra({ server: { apiRoutes: [...KNOWLEDGE_ROUTES] }, });
What they return depends on whether the app configures auth. Without auth they read every scope, page and note in the instance. With auth they return only what the signed-in caller can read: set
MASTRA_KNOWLEDGE_PRINCIPALS_KEYon the request context inauthorizeto the caller's Knowledge principals (or'all'for an admin), and a caller with no mapping is refused with 403.import { MASTRA_KNOWLEDGE_PRINCIPALS_KEY } from '@mastra/server/server-adapter'; authorize: async (path, method, user, context) => { if (path.startsWith('/api/knowledge')) { context.get('requestContext').set(MASTRA_KNOWLEDGE_PRINCIPALS_KEY, [`team:${user.orgId}:${user.teamId}`]); return true; } return appAuthorize(path, method, user, context); // keep your existing decision for other routes };
The routes are gated by a new
knowledge:readpermission. With an FGA provider configured, instances are checked by their registered key:GET /knowledgelists only the instances the caller can read, and the other routes refuse any other instance with 403.Scope pages in tree, page, graph and search responses carry their declaration's
displayLabelwhen the declaration sets one, with the address's$paramsfilled in ("$orgId policy"onpolicy:$orgIdshows as"acme policy"forpolicy:acme), so the explorer can caption a scope by its kind.The graph response also lists
grants: the declared access grants onto the scopes it shows (principal scope address, its page id when it has one, itsprincipalLabel, the target scope's page id and the role), so the explorer can draw who reaches what. Only principal scopes the caller can read are included; an owner view ('all') also gets declared principals nothing was written to yet. Servers on a core without grant support leave the field out.Also in: @mastra/server@1.82.0
Patch Changes
-
Added
traceQueryContextFieldsto the observability capabilities response type. (#29194) -
Added persistent model-list editing through the existing agent model reorder and update APIs for agents that opt into Editor ownership. (#27287)
Also in: @mastra/dsql@1.5.6, @mastra/mssql@1.11.1, @mastra/mysql@0.16.0, @mastra/oracledb@0.5.6, @mastra/server@1.82.0, @mastra/spanner@1.11.1
-
Added
linkCounts(pageIds)andpageLinks(pageId, { direction })to the Knowledge client resource, for the new link count and page link routes. (#29260) -
Fixed request headers being joined with client headers when the names differ only in case. (#29073)
@mastra/code-sdk@1.18.0
Minor Changes
-
Added native Google Vertex AI model support using Application Default Credentials. Configure
GOOGLE_VERTEX_PROJECTandGOOGLE_VERTEX_LOCATION, then select a Vertex model in a mode: (#28850)const code = await createMastraCode({ modes: [{ id: 'build', name: 'Build', default: true, defaultModelId: 'google-vertex/gemini-3.8-flash' }], });
-
Mastra Code no longer ships the experimental Subconscious: the Subconscious activity view and
ask_memorytool are removed, andMASTRACODE_EXPERIMENTAL_SUBCONSCIOUSdoes nothing. (#28353)Knowledge is opt-in. In the TUI, turn on Experimental Knowledge in
/settingsand restart: the code agent gets the Knowledge tools (search, read, write, pin) and pinned context every turn, stored with Mastra Code under a sharedorg:localscope. Hosts attach their own Knowledge the way an Agent does, withknowledgeandscopes, or keep it off withknowledge: false:const code = await createMastraCode({ knowledge: companyKnowledge, scopes: ['org:acme', 'team:acme:support'], });
Scopes say who the run is; what it may read or write comes from the Knowledge instance's scope declarations. Factory sessions always add
org:andproject:scopes, so a Factory Knowledge instance must declare both. Knowledge registers on the mounted Mastra under itsid; passknowledge: { key, instance }to use another key.Also in: @mastra/factory@0.27.0
Patch Changes
-
Reading the code agent's model without a controller session no longer logs "Tenant model credential policy could not be resolved". (#29147)
-
The code agent's
scopesfunction now keeps the run's incoming scopes (identity and per-message scopes) and adds the host's and the Factory session's scopes after them, matching the new Agentscopesfunction contract. (#28353) -
Fixed loading sparse settings files rewriting them with every default. (#28798)
-
Added
mastracode --version(alias-v), which prints the installed version (for example0.49.0) and exits without starting the TUI. (#29226) -
Fixed one stalled MCP server keeping every MCP server stuck on "connecting...". (#29028)
-
Fixed gateway-prefixed OpenAI and Anthropic model aliases losing web search tools and prompt guidance. (#28802)
-
Fixed missing traces and logs for Factory deployments on Mastra Platform. (#29229)
-
Fixed final headless usage to report cumulative token counts for the run instead of only the last model step. (#28800)
@mastra/connect@1.3.1
Patch Changes
- Agents can now continue a task as soon as an end user connects a provider. (#29210)
@mastra/deployer@1.82.0
Patch Changes
-
Fixed
mastra buildwriting the wrong dependency when the app imports a package subpath that ships its ownpackage.json, such asread-excel-file/nodeorpreact/jsx-runtime. (#29076) -
Fixed
mastra dev --root <dir>failing withENOENT ... .mastra/.build/entry-0.mjswhen the current directory has no.mastrafolder. (#29156) -
Fixed
mastra buildinstalling different dependency versions than your pnpm project resolves. (#29249) -
Fixed
mastra buildin monorepos packing and installing workspace packages that are already bundled into the output, along with all of their dependencies. (#29172)
@mastra/deployer-sandbox@0.3.26
Patch Changes
- Fixed sandbox deploys reporting a healthy server when the sandbox's port was private. (#29227)
@mastra/discord@1.3.3
Patch Changes
-
Fixed apps installing two copies of
chatwhen they use@mastra/discordor@mastra/teamswith@mastra/core. (#29285)Also in: @mastra/teams@0.1.4
@mastra/e2b@0.16.1
Patch Changes
-
Fixed killing a background process leaving the processes it started running. (#29238)
Also in: @mastra/platform-workspace@1.12.0
@mastra/elysia@0.1.22
Patch Changes
- The Elysia adapter now keeps every value of a repeated query parameter (
?tag=a&tag=breads as['a', 'b']), matching the other adapters. (#29069)
@mastra/evals@1.10.11
Patch Changes
@mastra/evals/vitestnow also supports Vitest 5. (#29026)
@mastra/factory@0.27.0
Minor Changes
-
Added Google Vertex AI as an operator-enabled deployment model provider. Enable it to let Factory tenants use the deployment's Application Default Credentials: (#28850)
const factory = new MastraFactory({ deploymentModelProviders: ['google-vertex'], // ...other Factory configuration });
-
Added separate work identity, provider sources and board placement to Factory.
WorkItemRownow describes the work itself; useCardfor board placement andWorkItemSourceRowfor provider facts. Existing card IDs are preserved, and Factory-created work items have no external sources. (#28959)// Before import type { WorkItemRow } from '@mastra/factory'; function stages(item: WorkItemRow) { return item.stages; } // After import { primarySource, type Card } from '@mastra/factory'; function stages(card: Card) { return card.stages; } function providerUrl(card: Card) { return primarySource(card)?.url ?? null; }
Removed
feedActivityAtfrom public cards and API responses. Card lists still exposecommentCount; the feed activity clock is internal storage state and is no longer part of the card contract.Factory copies existing cards automatically at startup. Inspect reported skipped IDs before rollout. The old
work_itemstable is left unchanged, but writes after cutover are not copied back: rolling back to an older build loses visibility of those writes.MASTRA_FACTORY_WORK_ITEM_SPLIT=dry-runis supported only before cutover and refuses startup when migrated board items already exist. -
Work and Review boards no longer use transition policies. Each phase's
nextPhasesnow says where a card can go, for people, agents and board rules alike, and Work's Planning ends withsubmit_planas its exit tool. (#29039)Work board moves. Cards follow the Work flow instead of moving anywhere:
From To Intake Triage, Planning, PR review, Done, Canceled Triage Intake, Planning, Done, Canceled Planning Building, Intake, Done, Canceled Building Review, Planning, Done, Canceled Review Building, Intake, Done, Canceled PR review Intake, Done, Canceled Done PR review Canceled Intake, PR review This applies to people too: you can no longer drag a card from Intake or Triage straight into Building, or from Intake into Review; Review (self-review) is reached only from Building, and a card self-review parks in Intake resumes through Planning. Pull request cards only sit in Intake, PR review, Done or Canceled, and only pull request cards enter PR review. When work counts as finished (no open pull request, no review asking for changes) is now in the build and self-review instructions rather than enforced by the board.
Plans always wait for a person. The project's Auto-approve plans setting is removed. Every plan on the Work board waits for a person, who moves the card on or approves
submit_planin a chat they are signed in to. Projects that had the setting on now see plans wait. Custom boards can still approve plans without a person withexitTool: { id: 'submit_plan', autoApprove: true, autoApproveWhen }. The project update API still acceptsautoApprovePlansfor one release and ignores it.factory_update_work_itemandPATCH /work-items/:idno longer takeplansPreapproved, and cards no longer reportplansPreapprovedAt: the per-card pre-approval never skipped a plan on this board.Triage. Factory no longer records a triage classification or acceptance. The triage agent sends open non-bugs back to Intake, and a person moving the card on is the approval. On boot, Factory moves cards still waiting in Triage under the old rule back to Intake once. The
triageTypeargument offactory_transition_work_itemis ignored.Deprecated.
transitionPolicyondefineBoard()still runs, and logs one warning per board.triageTypeandaccepton anallowresult are now ignored; before, an intent the actor wasn't allowed to make failed the transition with arule_error. Express the flow withnextPhasesandexitToolinstead:// before transitionPolicy: context => { if (context.fromStage === 'backlog' && context.toStage === 'done') { return { type: 'reject', code: 'invalid_transition', reason: 'Build it first.' }; } }, // after, on the backlog phase nextPhases: ['build'],
-
Each Factory project now has intake routes, an ordered list managed through the API. The first enabled route whose rule matches a new card decides the board and phase it lands on. Routes apply to each new card that arrives from GitHub, GitLab, Linear, Jira or incident.io, and to each card created without a board. A card a route placed stays on that board when its labels or source binding change later. (#29109)
Routes use the same rule format as
autoStartWhen, plus a newauthor.trustedfact. The fact istruefor cards whose author has write access, and for cards Factory wrote itself.curl -X POST "$FACTORY_URL/web/factory/projects/$PROJECT/intake-routes" \ -H 'content-type: application/json' \ -d '{ "name": "Trusted to Triage", "when": { "operator": "AND", "conditions": [{ "field": "author.trusted", "operator": "equals", "value": true }] }, "target": { "boardKey": "work", "phaseKey": "triage" } }'
Every project starts with a default route,
Outside authors wait in Intake. It files GitHub and GitLab issues from authors outside the write-access circle in Work › Intake, and admins can edit, disable or delete it like any other route. Nothing starts on a card resting in Intake on its own, and comments, edits and pushes on it no longer start work. Any session, a person's or an agent's, can move it on, and that move starts the next phase's run.Behavior change: trust now only decides where a card arrives. Factory no longer holds every run on an outside author's card for a person. Agents can now move an outside author's card out of Intake, which Factory used to refuse. Once such a card is out of Intake, a maintainer's push re-reviews it, like any card. To keep a phase from auto-starting for outside authors, add
author.trusted equals trueto itsautoStartWhen. Runs on untrusted cards still use isolated memory. Route changes are audited asfactory.intake_route.*. See "Intake routes" in the README. -
The same Jira, Linear or incident.io item can now be filed in more than one Factory project. Before, the first project to file an item held it for the whole org, and other projects that ingested it got no card. Each project now keeps its own card. Within a project the card is still reused when the item is seen again, after the card finishes and when the issue reopens. A Linear card is also found after the issue's identifier changes, for example when the issue moves to another team. (#29059)
When intake routing sends an item to a different project, the new project files its own card and the previous project's card stays where it is.
Breaking changes
claimKeyis removed from theupsertLinkedWorkItemdecision type. Rules that still return it, and decisions saved before this release, keep working: the field is accepted and ignored.WorkItemClaimConflictError,getByClaimKey,claimWorkItem,releaseProjectClaimsandWorkItemRow.claimKeyare removed.
Drop
claimKeyfrom linked-item decisions your rules return:// Before return { type: 'upsertLinkedWorkItem', source: 'jira-issue', sourceKey: issue.id, claimKey: `jira:issue:${issue.id}` /* … */, }; // After return { type: 'upsertLinkedWorkItem', source: 'jira-issue', sourceKey: issue.id /* … */ };
Remove calls to
getByClaimKey,claimWorkItemandreleaseProjectClaims. To find a project's card for an item, usegetByProjectSource.The
claim_keycolumn stays in the database. Values already stored there are left in place and no longer affect anything. -
Session agents can now share a dev server or preview they started in their sandbox with the
factory_sandbox_port_urltool. It returns only the shareable link and signs the session's browser in directly, so the agent can open the preview itself. (#28501) -
Factory Triage now runs once per card. Editing a GitHub or GitLab issue, or commenting on it, no longer starts Triage again on its card. Triage still runs when a person moves the card into Triage or starts the run. (#29230)
To restore the previous re-triage on new comments (Work cards in Triage or Planning), add your own rule for the event:
import { GithubIntegration } from '@mastra/factory/integrations/github/integration'; new GithubIntegration({ appId, privateKey, clientId, clientSecret, slug, rules: { issueCommentCreated: context => { if (context.board !== 'work' || context.item?.source !== 'github-issue' || !context.item.url) return; // Factory's own comments must not restart the run that wrote them. if (context.actor.type === 'github' && context.actor.factoryAuthored) return; const stage = context.item.stages.length === 1 ? context.item.stages[0] : undefined; if (stage !== 'triage' && stage !== 'planning') return; return { type: 'invokeSkill', role: 'triage', skillName: 'factory-triage', idempotencyKey: `${context.ingress.id}:factory-triage`, arguments: `Re-triage GitHub issue (${context.item.url}) after a new comment.`, }; }, }, });
-
Organization admins can now edit any Factory skill for one Factory from Settings › Skills, without redeploying. This covers built-in, folder, and code skills, including the review skills. Sessions that start after a save use the edited instructions; other Factories keep the original. An edited skill shows an Edited badge, the editor notes when the original changed since the edit, and Revert to original removes the edit. Other members can view skills but not change them. Saves and reverts are recorded in the audit log as
factory.skill.updatedandfactory.skill.reverted. (#29042) -
Added normalized source facts to Factory cards and the API so boards show provider keys, titles, states, labels, and authors without extra provider lookups. Read source facts from
card.workItem: provider labels, author, state, and key are no longer oncard.metadata. (#28961)// Before card.metadata.labels; card.metadata.author; // After card.workItem.labels; card.workItem.author; card.workItem.state; card.workItem.key;
-
Added separate provider work item and board item storage so source refreshes do not invalidate pending board moves. Existing card IDs stay unchanged. Migration skips conflicting sources instead of blocking startup. (#28961)
Board items now retain comment counts maintained by the comments domain, so listing cards, transitions, and session cleanup no longer load comment bodies. Recounts invalidate cached board lists without changing placement revisions, and migration recopies preserve refreshed counts. The internal feed activity clock is no longer returned on cards. Open pull request refreshes also avoid adding empty reconciliation markers that would invalidate pending rules.
import { primarySource } from '@mastra/factory'; // Provider facts are normalized on the work item. card.workItem.labels; // syncCard checks the source revision, not the work-item revision. await workItems.syncCard({ orgId, id: card.id, userId, expectedSourceRevision: primarySource(card)?.revision, patch: { metadata: { labels: ['bug'] } }, }); // Factory process state and board revision stay on the card. card.metadata; card.revision;
-
Pull requests and merge requests are now reviewed on the Work board. A new pull request gets its own card in Work Intake; its Review action moves it into Work's new PR review phase (
pr-review), where the independent reviewer runs (factory-reviewfor GitHub pull requests andfactory-gitlab-reviewfor GitLab merge requests by default; override them throughfactory-skills). New projects, and existing ones the first time their intake routes are read, get a second default intake route,Pull requests go to PR review, that files pull requests straight into PR review; an outside author's pull request still waits in Intake by the first default route. Merging moves the card to Done and closing it unmerged to Canceled. A pull request card can only be in Intake, PR review, Done, or Canceled; Work's Review phase stays the builder's self-review, and only pull request cards enter PR review. UsepullRequestReviewPhase(board)from@mastra/factory/boardsto find where a pull request card is reviewed (pr-reviewon Work,reviewon the Review board). (#28978)Cards already on the Review board stay there and keep working unchanged, and the Review board is still installed.
Because a pull request's card and the card that authored it can now share the Work board, tell them apart by source rather than by board.
@mastra/factory/boardsexports two helpers for this:import { isPullRequestCard, reviewsPullRequest } from '@mastra/factory/boards'; // Before: context.board === 'review' if (reviewsPullRequest({ board: context.board, item: context.item })) { // The pull request's own card, on Work or on the Review board. } // True for github-pr and gitlab-pr cards, wherever they are filed. if (context.item && isPullRequestCard(context.item)) { // A pull request's own card, not the card that authored it. }
-
Improved provider ingestion so source facts refresh independently of board stages. Read normalized facts from
card.workItemwhile Factory trust and automation state remain on the card. (#28960)const [card] = await workItems.list({ orgId, factoryProjectId }); console.log(card.workItem.key, card.workItem.state, card.workItem.labels);
This changes the metadata read contract: provider labels are no longer on
card.metadata.// Before const labels = card.metadata?.labels; // After const labels = card.workItem.labels;
Patch Changes
-
Cards without an issue number, such as manually created cards and Slack threads, now show where they came from (Manual, Slack) in the board list instead of leaving the number column blank. (#29246)
-
Linear sources saved from a workspace that is no longer connected no longer show up in intake settings as long encoded IDs. (#29292)
-
Fixed shell commands that exit with a failure code reading as successful in the Factory transcript. (#28963)
-
The Knowledge page works again on the page store. (#28362)
-
Fixed Factory pages loading slowly while several boards show a repository's open pull requests, as Work Intake and the Review board do. (#29248)
-
A board tool rule that moves a work item back into its current stage with
reenter: truenow runs that stage's entry rules, as documented. (#29104) -
Hover in the Factory sidebar is now instant. (#29244)
-
Factory's chat now draws Knowledge tool calls (
knowledge_search,knowledge_read,knowledge_page,knowledge_note,knowledge_pin) and pinned Knowledge as cards, the same way Studio does, and links each page to that Factory's Knowledge page. (#28942) -
Fixed direct retriage from Planning. Moving an issue card from Planning back to Triage starts a fresh investigation without requiring an intermediate move through Intake. (#28958)
-
Placeholders in Studio and Factory now read the same everywhere: sentence case with no trailing ellipsis ("Search agents", "Filter traces"). (#29188)
Also in: @mastra/playground-ui@68.0.0
-
Fixed a race where two simultaneous requests filing a card for the same external source (for example the same Linear or GitHub issue) could delete the card or fail with a server error. (#29235)
@mastra/inngest@1.11.1
Patch Changes
-
Fixed
POST /agents/:agentId/threads/abortfor Inngest agents. (#28345) -
Fixed
InngestRun.watch()crashing the process with an unhandled rejection when the Inngest realtime subscription fails to connect (for examplefetch failedor a WebSocket error). (#29103) -
Fixed Inngest durable agents ending the
model_generationtrace span withoutresponseIdandresponseModel. (#29180)
@mastra/libsql@1.29.0
Minor Changes
-
Changes to an experimental, upcoming memory feature. (#28353)
The Knowledge storage domain now stores Knowledge as pages, notes and pins (schema 4) instead of nodes and records. On startup, the tables of the earlier experimental Knowledge node store (shipped in 1.79.0) are dropped, with any rows in them, and the new tables are created; this also happens if an older process recreates them during a rolling deploy. Memory and other tables are never touched. On MongoDB, Knowledge collections found without a schema version (for example, left by an interrupted first start) are adopted as schema 4 rather than refused. Don't run
dangerouslyReset()from an older version against a database the new version already uses. Knowledge pages need a@mastra/corerelease that includes them; with an older core, the other storage domains keep working.Also in: @mastra/mongodb@1.26.0, @mastra/mysql@0.16.0, @mastra/pg@1.35.0
-
Store Knowledge listing pins. The pins table gets a
kindcolumn (pageorlist) and adepthcolumn, and a page now holds one pin per kind. Existing pins become page pins; no reset is needed. (#29263)Migration step on first start. Each store upgrades its pins table once, the first time it starts with this version:
- LibSQL rebuilds the pins table (SQLite can't drop the old one-pin-per-page constraint in place): it creates the new table, copies every pin across as a page pin, and swaps it in, inside the init transaction.
- Postgres and MySQL alter the table in place (add
kindanddepth, replace the unique key on the page with one on page and kind). - MongoDB sets
kind: 'page'on existing pins and replaces the unique index onpageIdwith one onpageIdandkind.
Also in: @mastra/mongodb@1.26.0, @mastra/mysql@0.16.0, @mastra/pg@1.35.0
Patch Changes
-
Database-backed agents now retain Editor changes to static model fallback lists across restarts, and existing LibSQL and PostgreSQL databases upgrade automatically. (#27287)
Also in: @mastra/mongodb@1.26.0, @mastra/pg@1.35.0
-
Knowledge stores can list and count only the child scopes a reader can see, so
knowledge_listno longer reads every child scope to show the readable ones. (#29260)Also in: @mastra/mongodb@1.26.0, @mastra/mysql@0.16.0, @mastra/pg@1.35.0
-
The LibSQL Knowledge store keeps the page link index: a
mastra_knowledge_page_linkstable, created on startup in existing databases without a schema change. (#29260)
@mastra/mcp@2.8.0
Minor Changes
-
Added an
onServerSettledoption tolistToolsWithErrors(),listToolsetsWithErrors(), andlistToolDefinitionsWithErrors(). It is called for each server as soon as that server's discovery succeeds, fails, or times out, so you can show healthy servers right away instead of waiting for the slowest one. (#29028)A server that hits
perServerTimeoutMsnow reports an error withcode: MCP_DISCOVERY_TIMEOUT_CODE(exported from@mastra/mcp), so you can tell a slow server apart from one that failed.Added a
serverNamesoption to the same methods to discover only the named servers. Use it to retry the servers that timed out without starting every other server again.The
onServerSettledcallback may beasync. If it throws or rejects, the error is logged and discovery is unaffected — a rejected callback never surfaces as an unhandled rejection.await mcp.listToolsetsWithErrors({ perServerTimeoutMs: 60_000, onServerSettled: (serverName, { value, error, duration }) => { console.log(serverName, error ? error.message : Object.keys(value), duration); }, });
-
Added a
toolResultoption toMCPServerso tools can return images, audio, and files to MCP clients. (#29247)Set
toolResult.modelOutput: trueto build each result from the tool'stoModelOutput, the same output an agent sends to its model. Images becomeimageblocks, audio becomesaudioblocks, and other files become embedded resources. For example, a workspaceread_fileof a PNG now reaches the client as an image instead of base64 inside JSON text.const server = new MCPServer({ id: 'media-server', name: 'Media Server', version: '1.0.0', tools: { screenshotTool }, toolResult: { modelOutput: true }, });
Use
toolResult.transformto rewrite any completed tool result before it is sent, for example to forward results that are already MCP-shaped from proxied tools.Without the new option, results are unchanged. The default of
modelOutputbecomestruein the next major version; if your clients parse the raw JSON text of tools that definetoModelOutput, setmodelOutput: falseto keep the current behavior. -
Added answering MCP task input from the agent chat. When a tool call that continues as an MCP task needs input, and no
inputRequestshandler is configured, the call now suspends with the server's requests instead of failing withMCP_CLIENT_TOOL_TASK_INPUT_UNHANDLED. The remote task stays open. Resuming the call answers that same task, and the tool is never called a second time. (#29032)// The tool call suspended with { type: 'mcp-input-request', taskId, requests: { approve: { ... } } } await agent.resumeStream( { inputResponses: { approve: { action: 'accept', content: { ok: true } } } }, { runId, toolCallId }, );
If the server asks again, the call suspends again with a new
requestedAt, so a UI can tell the new request from the one it already answered. A configuredinputRequestshandler still answers first, and a directtool.execute()call without an agent or workflow still fails as before. See #28986.
Patch Changes
-
Fixed
MCPServerdenying calls to tools it had just listed when fine-grained authorization is configured. (#29077) -
Fixed
disconnect()hanging, and stdio server processes being left running, when a server never answered its connection handshake. (#29028) -
Fixed
MCPServeranswering a call to an unknown tool with anisError: truetool result. (#29204)
@mastra/memory@1.41.0
Minor Changes
-
Observational Memory semantic search now works with a vector store that generates embeddings itself, so no
embedderis needed. (#27005)With MongoDB Automated Embedding, MongoDB embeds each observation when it is written and the search string when you query:
const memory = new Memory({ storage, vector: new MongoDBVector({ id: 'vec', uri, dbName, autoEmbed: { model: 'voyage-4' } }), options: { observationalMemory: { retrieval: { vector: true } } }, }); await memory.searchMessages({ query: 'What deadline did we agree on?', resourceId: 'user-123' });
The agent's
recalltool searches the same way. Observations are indexed undermemory_observations_selfembed. Configurations that pass anembedderkeep working unchanged.
Patch Changes
-
Fixed memory recall exposing stored reasoning. (#28810)
-
Fixed Observational Memory context leaving the
<observations>block open. (#29224) -
Observational Memory now tells the Observer who sent each user signal. (#29176)
@mastra/mongodb@1.26.0
Patch Changes
-
MongoDB now stores the Knowledge page link index, so
countPageLinks()andlistPageLinks()work on MongoDB Knowledge stores. (#29260) -
Removing a Knowledge note that's already removed now fails with
KnowledgeNotFoundErroron PostgreSQL and MongoDB, as it already did on LibSQL, MySQL and in memory. (#29260)Also in: @mastra/pg@1.35.0
@mastra/mysql@0.16.0
Patch Changes
-
MySQL now stores the Knowledge page link index, so
countPageLinks()andlistPageLinks()work on MySQL Knowledge stores. (#29260) -
Fixed experiment result writes putting plaintext back over a purged dataset item. (#29106)
-
Fixed two concurrent Knowledge page creates with the same key failing with a raw duplicate-entry error; the loser now gets a key conflict error, like the other storage adapters. (#29159)
@mastra/observability@1.23.1
Patch Changes
-
Update bundled model pricing with the latest prices from models.dev (
4d3ebbd) -
Stored spans now carry the service name from the Observability config when the span metadata does not set one, so span and trace filters on that service name find them. (#29201)
@mastra/otel-bridge@1.5.21
Patch Changes
- Fixed
@mastra/otel-bridgeinstalling a second copy of@opentelemetry/apiin npm projects that use the OpenTelemetry SDK 2 packages. (#29265)
@mastra/otel-exporter@1.5.1
Patch Changes
- Fixed
@mastra/otel-exporter1.5.0 exporting no traces or logs at all after a plain npm install. (#29265)
@mastra/pg@1.35.0
Patch Changes
- Postgres now stores the Knowledge page link index, so
countPageLinks()andlistPageLinks()work on Postgres Knowledge stores. (#29260)
@mastra/platform-workspace@1.12.0
Minor Changes
-
Added
getPortAuthCookie()toPlatformSandbox. It returns a cookie that signs a browser you control, such as an agent's browser, in to a port for an hour, so it opens the port's URL fromsandbox.networking.getPortUrl()without signing in. (#28501)const url = await sandbox.networking?.getPortUrl(3000); const cookie = await sandbox.getPortAuthCookie(3000);
-
Added port URLs to
PlatformSandbox.sandbox.networking.getPortUrl(port)returns the URL of a port in the running sandbox, such as a dev server, so you can open it in a browser. Ports are private: anyone in the sandbox's organization can open the URL after signing in to Mastra. (#29227)await sandbox.processes.spawn('pnpm dev --host 0.0.0.0 --port 3000'); const url = await sandbox.networking?.getPortUrl(3000);
@mastra/playground-ui@68.0.0
Minor Changes
-
Added
canCreatetoSavedViewsProviderso hosts can hide view creation for users who may only read saved views. Whenfalse, the observability saved views picker hides "Save as new view" and "Duplicate"; views can still be applied. Combine it withcanEditfor a picker that only applies views. (#29067)<SavedViewsProvider source={source} canCreate={false} canEdit={() => false}> {children} </SavedViewsProvider>
-
Added answering an MCP server's input request from agent chat in Studio. When a tool backed by an MCP task needs input (a question, a form, or a link to open), the tool call now shows the server's request with Accept, Decline, and Cancel, and the task carries on once you answer. This works for tool calls that run in the background too, including after reloading the thread, and the chat stays free to send messages while the request is open. The task chip shows "input required" while it waits. If an answer doesn't go through, the request says why so you can answer again. A request with more than 20 questions or very large content shows a short notice with a single Cancel instead of the questions. In a network run, the request is shown but can't be answered from Studio yet. (#29032)
-
Toolbar and filter bar controls now share one look: the same paddings, icon gaps, chevron and spacing between controls. (#28818)
Time range. A filter bar's time range is now the
DateTimeRangePickeritself, so it looks and behaves the same on every page: a calendar icon, the range and a chevron, with no separate "Time" label. PassdefaultPresetto show a × that resets a custom range; it replaces the chevron while a custom range is applied.Added
FilterBar.TimeRangewithFILTER_BAR_TIME_RANGE_FIELD,FILTER_BAR_TIME_RANGE_ITEMandFILTER_BAR_TIME_RANGE_FIELD_ID. They replaceTraceTimeRangeChipand theTRACE_TIME_RANGE_*names fromdomains/traces/components/trace-time-range-chip, which are removed.import { FilterBar, FILTER_BAR_TIME_RANGE_FIELD, FILTER_BAR_TIME_RANGE_FIELD_ID, FILTER_BAR_TIME_RANGE_ITEM, } from '@mastra/playground-ui/components/FilterBar'; <FilterBar fields={[FILTER_BAR_TIME_RANGE_FIELD, ...fields]} operators={operators} value={[FILTER_BAR_TIME_RANGE_ITEM, ...items]} // The time range item is synthetic: keep it out of the stored filters. onValueChange={next => setItems(next.filter(item => item.fieldId !== FILTER_BAR_TIME_RANGE_FIELD_ID))} > <FilterBar.Chips renderChip={item => item.fieldId === FILTER_BAR_TIME_RANGE_FIELD_ID ? ( <FilterBar.TimeRange preset={preset} // A preset, including the × reset, drops the applied custom dates. onPresetChange={next => { setPreset(next); if (next !== 'custom') setRange({}); }} dateFrom={range.from} dateTo={range.to} onDateRangeChange={(from, to) => setRange({ from, to })} defaultPreset="last-7d" /> ) : ( <FilterBar.Chip item={item} /> ) } /> <FilterBar.Input /> </FilterBar>;
DateTimeRangePickergainssize,defaultPresetandref(the trigger button).Migrating removed APIs.
-
TraceTimeRangeChip→FilterBar.TimeRange(same props), andTRACE_TIME_RANGE_FIELD/_ITEM/_FIELD_ID→FILTER_BAR_TIME_RANGE_FIELD/_ITEM/_FIELD_ID, imported from@mastra/playground-ui/components/FilterBar.// Before import { TraceTimeRangeChip, TRACE_TIME_RANGE_FIELD_ID } from '@mastra/playground-ui/domains/traces/components/trace-time-range-chip'; item.fieldId === TRACE_TIME_RANGE_FIELD_ID ? <TraceTimeRangeChip preset={preset} onPresetChange={setPreset} /> : … // After import { FilterBar, FILTER_BAR_TIME_RANGE_FIELD_ID } from '@mastra/playground-ui/components/FilterBar'; item.fieldId === FILTER_BAR_TIME_RANGE_FIELD_ID ? <FilterBar.TimeRange preset={preset} onPresetChange={setPreset} /> : …
-
DateTimeRangePicker'srenderTrigger→ remove it: the trigger is always the shared calendar button. Passsizefor another rung andrefto reach the trigger. -
comboboxStyles.chevronon a hand-built trigger →<ButtonChevron size={size} />from@mastra/playground-ui/components/Button.
Other controls.
- Added
ClearFiltersButton, a round reset icon button with a tooltip, used for every toolbar's "clear filters". - Added
TriggerChevronandButtonChevron, the one chevron every dropdown trigger (Select,Combobox, the date picker) shows. ComboboxTriggeruses that chevron instead of the up-down one.- Icons in every interactive control (buttons, fields, search inputs, filter chips, menu and command rows) follow one rule: muted at rest, brighter on hover. Two new theme tokens,
--control-icon-restand--control-icon-hover, set those colours; menus retune them, and icon-only controls always brighten. Search fields and combobox fields now brighten their icons on hover like buttons do. - Icons take their size from the control they sit in, as in a Button: filter chips and search inputs no longer size their own icons.
ListSearchdefaults to thesmtoolbar size, andSearchInput's icon sits at the same inset as a button's.ActionRowandFilterBarspace their controls 6px apart.
-
-
SkillSimpleFormnow acceptsnameReadOnlyto lock only the name field while the description and instructions stay editable. (#29042)import { SkillSimpleForm } from '@mastra/playground-ui/domains/skills/skill-simple-form'; <SkillSimpleForm nameReadOnly name={name} onNameChange={setName} description={description} onDescriptionChange={setDescription} instructions={instructions} onInstructionsChange={setInstructions} />;
-
Dashboard chart cards can now link to a fuller view, and line charts can mark points in time. (#29266)
DashboardRenderertakes anexpandoption. Whenexpand.href(widget)returns a URL, the card shows its title as a link with an expand icon, in place of its views menu.MetricsLineCharttakesmarkers: dashed vertical lines at buckets, for example where a billing period starts. Each marker'sxmust match a bucket'sxKeyvalue.DateTimeRangePicker(andFilterBar.TimeRange) takenamedRanges: fixed ranges such as the current billing period, listed above the presets and named on the trigger while applied.
<DashboardRenderer definition={definition} scope="project" expand={{ href: widget => `/query?widget=${widget.id}`, LinkComponent: RouterLink }} /> <MetricsLineChart data={data} series={series} markers={[{ x: "2026-11-01", label: "Period starts" }]} />
Metrics charts now use thin lines, and the large card header no longer shifts when its value finishes loading.
Patch Changes
-
Fixed the Studio tab freezing when you select a workflow step in the trace details drawer. (#29181)
-
Sidebar hover is now instant, like menu and list rows. (#29244)
-
The Knowledge explorer now turns
[[links]]in page bodies into links. (#29214) -
Knowledge tool cards in chat now show
[[scope#path]]links in a page body as links to the page, instead of the raw[[...]]text. (#29214) -
Knowledge page references in Studio chat (
[[ref]]and addresses likedocs:mastra#guides/memory) are now links, and hovering one shows the page's name, address and child count. (#29264) -
Pinned Knowledge now shows in chat as a card instead of raw
{id: …}text. (#28942) -
The Knowledge explorer no longer lays its graph out again each time it re-renders: pages built from the tree's query results are kept while they hold the same pages. (#29216)
-
Failed Knowledge tool calls now draw as Knowledge cards that say what failed ("Couldn't read site:mastra#pricing-tiers") and show the error when opened, instead of a bare failed row. (#29215)
-
The Knowledge graph draws every child of the focused page as a labelled circle again, up to 48, and staggers them over a few rows so they all sit within reach of the centre at a readable zoom. (#29216)
-
The Knowledge graph's Back button is now a floating icon button in the graph's top right corner, with a solid background, a pointer cursor and a hover state, and shows disabled when there is nothing to go back to. (#29216)
-
The Knowledge explorer has a Back button that returns to the page you focused before, one step at a time through the pages you clicked. (#29216)
-
Knowledge graph dots are larger (20px, 28px with a link count) and have an 8px hover and click margin around them, so far pages are easier to see and hit. (#29216)
-
Knowledge graph circles now fit their text: smaller type and two lines in a crowded circle, words are never broken mid-way (a word too wide for the circle ends in an ellipsis, and the full name stays in the tooltip), circles at one level share a size, the kind label stays on one line beside its icon ("Parent" in full), and the link and child-count badges sit outside the circle instead of covering the label. (#29216)
-
A "Link counts" toggle in the Knowledge explorer writes each graph dot's total links in and out inside it. (#29216)
-
Pages linked to the focused Knowledge page draw as the legend's "Page in another branch" grey, grown into a named circle, with no "Page" caption and a name that reads on the fill. (#29216)
-
The Knowledge graph legend lists "Page in another branch" whenever a page is drawn in that grey, including the link-end circles around a focused page, not only far dots. (#29216)
-
The Knowledge graph's Fit view button now animates over the same 700ms as a refocus, and jumps instantly under reduced motion. (#29216)
-
Refocusing the Knowledge graph now zooms to fit the focus, its children and the outer ring of linked pages, in the same animation as the layout change. (#29216)
-
Focusing a page in the Knowledge graph view now loads its children into the graph, linked to it, including children the initial graph didn't load. (#29216)
-
The Knowledge graph now draws every page the focused page links to, and every page whose notes mention it, even when those pages aren't loaded yet. (#29216)
-
The focused page in the Knowledge graph is drawn larger with a heavier border and a halo, and the legend explains it. (#29216)
-
Pointing at a page in the Knowledge graph no longer rebuilds every circle and line: fading the pages that aren't connected to it is a CSS rule driven by one attribute on the graph and a mark on the few neighbours. (#29216)
-
Hovering a page in the Knowledge graph now draws its own content links and notes, fades everything it isn't connected to, and, for a dot, shows its kind, address and how many links go in and out. (#29216)
-
Hovering a page in a large Knowledge graph no longer lays the whole graph out again: links fetched for a hover are drawn but not counted, and a page's links are found through an index built once. (#29264)
-
With nothing selected, the Knowledge graph now draws its first root scope exactly like a focused scope: every child is a labelled circle (up to 48, shrinking when crowded) with its own children as dots around it, a "+N more" circle past the cap, and the other root scopes beside it. (#29216)
-
The Knowledge graph's first view now fits every drawn page, outer ring included, with a margin and clear of the legend, and never zooms in past what fits. (#29264)
-
The Knowledge graph legend now explains every line style on screen (including hover and direction lines and the faint line to a child dot) and hides the rest; link badges read
→4 14→(links in, links out) with a spoken label; with link counts on, only dots that have a count grow. (#29216) -
The Knowledge graph legend now names what each dot colour means: the circle the dot hangs under. (#29216)
-
Knowledge graph scopes and the pages above the focus stay drawn as labelled circles (at most ten far ones), marked with a layers or up-arrow icon that the legend explains. (#29216)
-
The Knowledge graph draws the pages the focused page links to, and the pages that link to it, as named circles like its children instead of bare numbered dots. (#29216)
-
On the Knowledge graph, pages in other branches that the focused page links to now sit on a ring beyond its children and their dots, as smaller named circles, instead of crowding in next to the focus. (#29216)
-
The Knowledge graph can show exact link counts from the store for every visible page, and hovering a page fetches its real links, saying how many go to pages that aren't drawn. (#29264)
-
Clicking a "+N more" circle in the Knowledge graph now draws the next batch of that page's children, counting down until every child is drawn. (#29216)
-
The Knowledge graph now loads the children of the pages it draws as circles, so their children show as small dots around them instead of dashed placeholders (up to 12 circles; the rest keep the placeholders). (#29216)
-
Refocusing a page in the Knowledge graph no longer moves the view. (#29264)
-
Knowledge graph tooltips and count badges now have a solid background so they read against the graph, and each tooltip has a tail pointing at its dot. (#29216)
-
The Knowledge graph keeps the other root pages one link from the centre when the first root's parent is not loaded. (#29216)
-
The Knowledge graph now makes the focused page's parent obvious: its circle is labelled "Parent" with the up icon, a solid cyan line joins it to the focus, and the legend has a row for it. (#29216)
-
The Knowledge graph now opens at a zoom where circle names can be read. (#29216)
-
The Knowledge graph now loads like the tree instead of drawing the first 250 pages at once. (#29216)
-
Refocusing the Knowledge graph on a page now only pans to it and keeps your zoom. (#29216)
-
Focusing a page in the Knowledge graph keeps the current layout while the pages it needs load (with a "Loading pages…" note after 150ms), then moves to the new layout once, so the transition shows instead of animating on stale data. (#29216)
-
The Knowledge graph's view transition now takes 700ms on an ease-in-out curve so the move can be followed (still off under reduced motion), and "Link counts" is on by default. (#29216)
-
The Knowledge graph hands React Flow the same event handlers on every render, so pointing at a page no longer re-renders every node and line wrapper because of a new function identity. (#29264)
-
The Knowledge graph's All links and Link counts toggles are now icon buttons floating over the graph beside Back, and the pressed state of them and of the Graph | Page switch is a solid fill with full-strength text. (#29216)
-
The Knowledge graph tooltip now steers clear of neighbouring dots as well as circles, so it no longer covers the pages around the one you hover. (#29216)
-
Knowledge graph tooltips now open on the side of the dot that covers the least: not the focused page, the highlighted lines, the legend or the Back button, and always inside the graph. (#29216)
-
Knowledge graph tooltips stay readable when the graph is zoomed out, the legend names at most four dot branches, and Back no longer swallows the next selection if the host ignores it. (#29216)
-
The Knowledge graph draws the pages around a childless focus, and the first children of each root scope when nothing is focused, as labelled circles with their own children as dots, instead of a ring of bare dots. (#29216)
-
The Knowledge graph now glides between views: pages in both the old and new view move and resize into place over 300ms, pages leaving fade out and new ones fade in. (#29216)
-
In the Knowledge graph view, clicking a page in the sidebar now focuses it in the graph first. (#29216)
-
The Knowledge graph legend now reads "Under parent page" or "Under parent scope" for each dot colour, instead of naming the parent; hovering the row names it. (#29216)
-
When the Knowledge graph's dots hang under several parent groups, each legend row now names its parent ("Under Deployment") so the colours can be told apart. (#29216)
-
Each entry in the
knowledge_listchat card now links to its page in Studio's Knowledge view, like theknowledge_searchcard does. (#29215) -
Studio chat now draws
knowledge_listtool calls as a Knowledge card instead of raw JSON. (#29215) -
Added
KnowledgePageExplorer, a Knowledge browser any host can use: search, a page tree, a graph of scopes and pages, and a page view, all on one selection. (#28361) -
Knowledge tool cards in Studio chat now show which part of a page was read or written: lines, matches and their context, an overview, or the whole page. (#29259)
-
Picking a search result while the Knowledge graph is showing now focuses that page in the graph instead of switching to the page view. (#29216)
-
A parent in the Knowledge tree now stays collapsed when you collapse it over the selected page. (#29216)
-
The Knowledge explorer tree is easier to use on large knowledge bases: (#29216)
-
When the Knowledge graph focuses a deep page, the page tree keeps scrolling to its row as the levels above it finish loading, so the row ends up in view instead of below the fold. (#29216)
-
When the Knowledge tree follows a page focused in the graph, the row now lands a third of the way down the sidebar instead of against its bottom edge. (#29216)
-
Focusing a page in the Knowledge graph now scrolls the page tree to its row (opening its ancestors first), instead of leaving the highlighted row out of sight. (#29216)
-
The Knowledge tree's follow-the-focus scroll now checks the element that actually scrolls, so a deep page's row ends up in view once its levels have loaded. (#29216)
-
Fixed Studio forms that would not submit when left blank after the zod 4 upgrade. (#29072)
-
Icons in labelled controls (the time range, selects, the filter input, search fields, filter chips) now stay muted on hover; the label and background show the hover. (#29191)
-
Fixed Studio's MCP input request card so questions keyed
constructoror__proto__can be answered and their answers are sent. (#29306) -
Knowledge tool calls in chat now draw as readable cards instead of raw JSON. (#28931)
-
Studio chat shows a pinned Knowledge listing (the
list:<pageId>entry) as a Listing row that links to the page it lists, with its lines on demand, in snapshots and in added, changed and removed updates. (#29264) -
Improved the connect card that
request_connectionrenders in chat: (#29210) -
Fixed metrics charts stretching past their cards in Safari when the page is zoomed in or out. (#29203)
-
Pagination in paged metrics tables, such as the Metrics Breakdown card, no longer jumps when a page has fewer rows. (#29185)
@mastra/rag@2.7.2
Patch Changes
-
Fix Markdown header splitting when a configured heading contains a pipe, preserving heading metadata and content around tables and fenced code blocks. (#29221)
-
Fixed JSON chunking so each chunk round-trips through a single
JSON.parse. (#29223) -
Fixed JSON chunking silently dropping array items and nested object fields when a chunk was smaller than
minSize. (#29222) -
Fixed regex separators that contain capture groups. (#29225)
@mastra/react@1.14.1
Patch Changes
-
Updated agent drafts and versions now appear immediately in Studio after model-list edits. (#27287)
-
A failed tool call whose error has no message now shows the error's name, or "The tool call failed.", instead of
[object Object]. (#29215)
@mastra/ring-signals@0.1.0
Minor Changes
-
Added
@mastra/ring-signalsfor connecting Ring doorbells and cameras to Mastra agents. Ring events such as doorbell presses and motion arrive in agent threads as notification signals, and the agent can fetch a snapshot or video clip for an event with thering_snapshotandring_cliptools. (#28864)import { Agent } from '@mastra/core/agent'; import { RingSignals } from '@mastra/ring-signals'; export const homeAgent = new Agent({ id: 'home-agent', name: 'Home Agent', instructions: 'Say briefly whether each doorbell event needs attention.', model: 'openai/gpt-6.1-sol', signals: [ new RingSignals({ events: { device_offline: { priority: 'low', summary: 'Device went offline.', coalesce: true }, }, }), ], });
Mastra adds the routes Ring calls for account linking and webhooks to the server. See the Ring integration guide.
@mastra/server@1.82.0
Minor Changes
-
Added
POST /api/background-tasks/:backgroundTaskId/resumeto answer a suspended background task over HTTP. (#28998)- Send the answer as
resumeData. - Optionally send the
suspendedAtyou read from the task. If the task has resumed or suspended again since then, the server answers409instead of delivering a stale answer. - Background task responses now include
suspendedAt. - The route needs the new
background-tasks:executepermission.
curl -X POST http://localhost:4111/api/background-tasks/$TASK_ID/resume \ -H 'Content-Type: application/json' \ -d '{"resumeData":{"approved":true},"suspendedAt":"2026-10-30T10:00:00.000Z"}'
- Send the answer as
Patch Changes
-
server.auth.authorize(path, method, user, context)can now callcontext.get('requestContext')andcontext.get('mastra')on the Express, Fastify, Koa and Elysia adapters, as it already could on Hono. (#29069)Also in: @mastra/server-adapters-test-suite@0.2.1
-
Fixed agent-builder routes in processes that run more than one Mastra instance. (#29253)
-
Trace queries that filter by
requestId,serviceName, orexperimentIdnow return501with a clear message when the configured observability store is too old to support them, instead of failing with a500. (#29194) -
Added
GET /knowledge/:knowledgeKey/link-countsandGET /knowledge/:knowledgeKey/pages/:pageId/links: exact link counts for a set of pages, and one page's links with the pages at their other ends, both gated by the caller's read scope. (#29260) -
Errors in streamed chunks now keep their message. (#29215)
@mastra/temporal@0.5.0
Minor Changes
-
Breaking: Steps now validate their input against
inputSchemaand their request context againstrequestContextSchemabefore they run, as they do on Mastra's default workflow engine. Before, a step ran on any input, so a run could returnsuccesswith a wrong value. A mismatch now fails the step without retries, and the run returnsstatus: 'failed'. Steps also get the parsed input, with schema defaults and transforms applied. Only the steps inside a nested workflow are checked, not its owninputSchema. Fixes #28308. (#29080)Setting
validateInputs: falseon a workflow skips these checks, along with the workflow input checks inrun.start():createWorkflow({ id: 'my-workflow', options: { validateInputs: false } });
The Worker build reads
validateInputsfrom thecreateWorkflow()call. When it can't, for example withoptions: sharedOptions, it prints a warning. Runs started withrun.start()orrun.startAsync()still use the workflow's setting. Runs started another way, such as from a Temporal Schedule, check step input.Check these before upgrading:
-
Step input and request context pass through Temporal's data converter, which turns a
Dateinto a string. Usez.coerce.date()instead ofz.date()in a step'sinputSchemaandrequestContextSchema:// Before inputSchema: z.object({ when: z.date() }), // After inputSchema: z.object({ when: z.coerce.date() }),
-
Zod
z.object()schemas drop keys they don't declare. A step that reads an undeclared key frominputDatanow getsundefined. Add the key to itsinputSchema. -
After a
.parallel()that contains a nested workflow, the next step receives the nested workflow's output under its Temporal workflow type, for exampleinnerWorkflowfor idinner. The default engine uses the id, so a schema written for it can fail on Temporal. -
Steps scheduled after the Worker upgrade are checked, including steps of runs that started before the upgrade.
-
Patch Changes
@mastra/valkey-streams@0.6.1
Patch Changes
- Fixed Valkey thread streams retaining saved run events until stream limits or expiration (#29095)
Other updated packages
Dependency updates only: @mastra/braintrust@1.3.26, @mastra/datadog@1.4.21, @mastra/deepeval@0.1.23, @mastra/deployer-cloud@1.82.0, @mastra/deployer-cloudflare@1.2.41, @mastra/deployer-netlify@1.2.41, @mastra/deployer-vercel@1.2.41, @mastra/e2b-desktop@0.1.12, @mastra/express@1.5.26, @mastra/fastify@1.5.26, @mastra/google-cloud-pubsub@1.1.7, @mastra/hono@1.7.24, @mastra/koa@1.7.26, @mastra/langfuse@1.6.5, @mastra/langsmith@1.4.2, @mastra/longmemeval@1.1.41, @mastra/mcp-docs-server@1.3.12, @mastra/nestjs@0.2.41, @mastra/next@0.2.40, @mastra/opencode@0.1.41, @mastra/parallel@0.1.5, @mastra/posthog@1.4.6, @mastra/sentry@1.2.31, @mastra/tanstack-start@0.2.40, @mastra/turso@0.1.20, @mastra/voice-google@0.14.5