@mastra/core@1.68.0
blacklanternsecurity/bbot@mastra/core@1.68.0Sep 23, 2026by Patrycja-J
Upgrading?
AI Summary
Introduces MCP v2 server architecture, new vector store backends (Azure AI Search, Weaviate), and significant observability enhancements including real pagination and delta polling for trace queries. Also adds performance optimizations for durable agents and token-budgeted history trimming.
Key Highlights
- MCP v2 server architecture with suspend/resume state and signed continuation tokens
- New vector stores: Azure AI Search and Weaviate
- Real pagination, discovery, and delta polling for trace queries
- Durable agent performance optimization (no running checkpoint persistence by default)
- Token-budgeted conversation history trimming
Breaking Changes
- Agent Controller stream message events changed structure (message_start, message_update, message_end)
- @mastra/mcp@2.0.0 is a major rewrite removing legacy handshake and elicitation APIs
New Features
- Extensible workspace grep text extensions (.sas, .log, .jsonl)
- Resumable prune() execution for storage adapters
- Page pagination for advanced trace queries
- Refreshing agent peer details
- orderBy support for dataset/experiment listings
- Root span details in trace query results
- Dataset snapshot format utilities
- notScorable() utility for evals
- generateTitle.emitEvent
Full Release Notes
## Highlights
### MCP v2 (2026-07-28) server architecture in `@mastra/mcp@2.0.0`
`@mastra/mcp` is now rebuilt on the MCP 2026-07-28 revision: no initialize handshake/session headers, and tools that need input use first-class suspend/resume (`context.suspend()` + `context.resumeData`) with signed, self-contained continuation state (`requestState`). Server routes now return explicit `{ status: 'suspended', suspendPayload, resumeSchema }` vs `{ status: 'completed', output }`.
### New vector stores: Azure AI Search + Weaviate
Two new `MastraVector` backends land: `@mastra/azure-ai-search@0.1.0` (Azure AI Search with auto-indexed filterable metadata for Memory/RAG) and `@mastra/weaviate@0.1.0` (full MastraVector contract with rich Mongo-style metadata filtering and deterministic vector ID mapping).
### Observability trace querying gets “real” pagination, discovery, and delta polling (core/server/client + ClickHouse/DuckDB/PG)
Advanced trace queries now support page-based pagination (with totals), bounded field/value discovery APIs, and delta polling cursors to transition from initial load → incremental updates efficiently. Trace list results also include root-span summary fields (name, createdAt, metadata, inputPreview, etc.) so UIs can render useful lists without fetching full traces.
### Durable-agent performance: stop persisting `running` checkpoints by default
Durable agents no longer write `running` snapshots unless `recovery.durableAgents: 'auto'` is enabled (or you override via `shouldPersistSnapshot`), eliminating large amounts of storage churn on normal runs while keeping `pending/paused/suspended` snapshots for human-in-the-loop resume.
### Token-budgeted conversation history trimming with `messageHistory`
Memory now supports `messageHistory: { maxTokens, atMaxRemoveTokens? }` to keep context within a token budget by dropping oldest remembered messages in chunks, persisting the trim boundary per thread so future turns stay within budget without deleting stored messages.
### Breaking Changes
- Agent Controller stream message events changed: one full `message_start`, then ID-addressed `message_update` deltas; `message_end` now contains only the message ID (store by ID and apply updates).
- `@mastra/mcp@2.0.0` is a major rewrite: MCP 2026-07-28 only (legacy handshake removed), suspend/resume replaces elicitation APIs, and several server/client transport and protocol surfaces were removed per the MCP v2 migration guide.
## Changelog
### [@mastra/core@1.68.0](https://github.com/mastra-ai/mastra/blob/@mastra/core@1.68.0/packages/core/CHANGELOG.md)
#### Minor Changes
- Make the workspace grep text-extension whitelist extensible. Added `.sas`, `.log`, and `.jsonl` to the built-in text extensions and MIME type map so they are searchable by default. Added an optional `textExtensions` option to `MastraFilesystemOptions` (exposed via `filesystem.isTextFile()`) so consumers can register additional text extensions. When an explicit file path is grepped but its extension isn't recognized as text, the grep summary now reports it as skipped so "no matches" is distinguishable from "never searched". (#24003)
```ts
import { LocalFilesystem } from '@mastra/core/workspace';
// Register extra extensions as searchable text files
const filesystem = new LocalFilesystem({
basePath: process.cwd(),
textExtensions: ['.sasx', '.myext'],
});
filesystem.isTextFile('report.sasx'); // true
```
- Added consistent, resumable `prune()` execution for storage adapters, including bounded work, pause intervals, and cancellation. (#23466)
```ts
const controller = new AbortController();
await storage.prune({
maxBatches: 10,
maxRows: 10_000,
pauseMs: 25,
signal: controller.signal,
});
```
- Added list-compatible page pagination to advanced trace queries while preserving keyset cursors. (#24061)
```ts
const result = await client.queryTraces({
timeRange,
pagination: { page: 0, perPage: 25 },
});
```
- Added support for refreshing advertised agent peer details without reclaiming thread ownership. (#23696)
```ts
const updated = agent.updateThreadPeerAdvertisement({
resourceId: 'resource-1',
threadId: 'thread-1',
peer: { title: 'Updated thread title', metadata: { mode: 'review' } },
});
```
- Added \`orderBy\` support when listing datasets, dataset items, experiments and experiment results so Studio and API consumers can sort server-side instead of receiving a fixed newest-first order. (#24567)
\`\`\`ts
const { datasets } = await mastra.datasets.listDatasets({ orderBy: { field: "name", direction: "ASC" } });
const { items } = await dataset.listItems({ orderBy: { field: "createdAt", direction: "ASC" } });
\`\`\`
Allowed fields: datasets (\`createdAt\`, \`updatedAt\`, \`name\`), items (\`createdAt\`, \`updatedAt\`), experiments (\`createdAt\`, \`status\`), experiment results (\`startedAt\`, \`createdAt\`). Unknown fields are rejected. Also exports a \`resolveListOrderBy\` helper for storage adapters.
- Added root span details to queryTraces results: name, entityId, parentSpanId, createdAt, metadata, and inputPreview. Trace lists can display these fields without fetching each full trace. createdAt uses the root span start time; inputPreview contains a shortened input preview rather than the full input. (#23958)
```ts
const { traces } = await client.queryTraces({
timeRange: { from: '2026-09-01T00:00:00Z', to: '2026-09-15T00:00:00Z' },
});
// Previously required fetching the full trace:
console.log(traces[0]?.name, traces[0]?.inputPreview, traces[0]?.metadata);
```
_Also in: @mastra/clickhouse@1.20.0, @mastra/duckdb@1.10.0, @mastra/pg@1.26.0, @mastra/playground-ui@56.0.0, @mastra/server@1.68.0_
- Added dataset snapshot format utilities to validate portable identities, preserve authored JSON fields and required item creation and update timestamps, and detect artifact changes with an integrity digest. These utilities do not read or write dataset storage. Both helpers accept a configurable `maxBytes` budget (4 MiB by default), independent of the artifact format and integrity digest. (#23902)
```ts
import { parseDatasetSnapshot } from '@mastra/core/datasets';
const snapshot = parseDatasetSnapshot(artifactJson, { maxBytes: 8 * 1024 * 1024 });
```
- Added `notScorable()`. Return it from a scorer step when a run has nothing to evaluate, for example a refund judge on a chat that never called the refund tool. Remaining steps are skipped, so the judge is never called and the run stays out of that scorer's averages. `runEvals()` omits `verdict` when every configured gate or threshold was not scorable. (#24378)
```ts
import { createScorer, notScorable } from '@mastra/core/evals';
import { extractToolCalls } from '@mastra/evals/scorers/utils';
const refundJudge = createScorer({
id: 'refund-judge',
description: 'Judges refund handling',
type: 'agent',
judge: { model: 'openai/gpt-5-mini', instructions: '...' },
})
.preprocess(({ run }) => {
const { tools } = extractToolCalls(run.output);
return tools.includes('refundCustomer') ? { tools } : notScorable('refundCustomer was not called');
})
.generateScore({
description: 'Score the refund handling from 0 to 1',
createPrompt: ({ run }) => `Rate the refund handling: ${JSON.stringify(run.output)}`,
});
```
**Reading the result.** `scorer.run()` is either scored or skipped. Check `notScorable` before using `score` as a number, including on scorers that never skip:
```ts
const result = await refundJudge.run(input);
if (result.notScorable) {
// skipped — no score
} else {
result.score;
}
```
- Added `generateTitle.emitEvent` so HTTP and stream clients receive the generated thread title without polling. (#24247)
Thread titles are generated in the background after a run finishes. The `onTitleGenerated` callback only works for in-process callers, so an app driving an agent over HTTP had no way to know when the title was ready (#21203).
With `emitEvent: true`, the run stream waits for the title and emits it as a transient `data-thread-title` chunk before `finish`:
```typescript
const memory = new Memory({
options: {
generateTitle: {
emitEvent: true,
},
},
});
// Consumers read it from the run's stream before the `finish` chunk.
for await (const chunk of stream.fullStream) {
if (chunk.type === 'data-thread-title') {
console.log(chunk.data.threadId, chunk.data.title);
}
}
```
The chunk is transient, so it is never persisted as part of the conversation. The default stays fully non-blocking: without `emitEvent`, title generation still runs in the background and does not delay the stream.
The `generateTitle` object also accepts `minMessages` (minimum number of thread messages before a title is generated, default `1`) and an optional `model` (defaults to the agent's own model), so title generation can run on a smaller or cheaper model than the conversation.
Durable and evented agents don't emit the chunk yet; the title is still generated and persisted.
- Added sandbox start options forwarding so providers can support cancellable startup operations. (#24451)
- Durable agents no longer persist `running` checkpoints by default, and `createDurableAgent()` accepts a new `shouldPersistSnapshot` option to control snapshot persistence (#23915). (#23978)
Previously, durable agents wrote a full workflow snapshot to storage on every step of every run, including `running` checkpoints that are only read by crash recovery. With recovery left at its default (`recovery.durableAgents: 'off'`), those writes were pure overhead — a single agent turn could generate over a thousand storage statements.
**What changed**
- Durable agents still always persist `pending`, `paused`, and `suspended` snapshots, so human-in-the-loop resume and tool approval keep working with no configuration.
- `running` checkpoints are now only written when the Mastra instance sets `recovery.durableAgents: 'auto'`, which is the setting that consumes them.
- `createDurableAgent()`, the `DurableAgent` constructor, and the agent-level `durable` config accept a `shouldPersistSnapshot` predicate to override the policy.
- Mastra logs a warning if a custom predicate excludes `suspended` or `paused` (breaks human-in-the-loop resume), or excludes `running` while automatic recovery is enabled (makes the agent invisible to recovery).
- Evented agents are unaffected: they always persist the full snapshot set (their engine coordinates workers through storage) and log a warning if `shouldPersistSnapshot` is set.
**Action required if you use manual recovery**: if you call `listActiveRuns()`, `recover()`, or `recoverActiveRuns()` without setting `recovery.durableAgents: 'auto'`, opt back into `running` checkpoints:
```typescript
const durableAgent = createDurableAgent({
agent,
shouldPersistSnapshot: ({ workflowStatus }) =>
['pending', 'paused', 'suspended', 'running'].includes(workflowStatus),
});
```
- Added delta polling to advanced trace queries, including a cursor on numbered pages for the initial-load-to-poll handoff. Delta cursors bind the predicate and time range; keyset pagination remains unchanged. (#24329)
```ts
// Before: load a numbered page.
const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
// After: continue polling from that page without rescanning it.
const updates = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor, limit: 100 });
```
Polling returns newly completed roots and root completions. It does not provide deletion notifications or guarantee re-emission after related-record changes.
- Added Hono handler and route types to the server exports. (#23992)
```ts
import type { MiddlewareHandler } from '@mastra/core/server';
const middleware: MiddlewareHandler = async (_context, next) => {
await next();
};
```
- Added `structuredOutput.instructions` support for JSON prompt injection, so you can replace the serialized JSON schema in the prompt with your own compact instructions (#24176)
When `jsonPromptInjection` is active and no separate structuring `model` is configured, a caller-supplied `structuredOutput.instructions` string is now injected into the prompt in place of the serialized JSON schema, in both `'system'` and `'inline'` modes. On large schemas this removes thousands of tokens from every model call.
```ts
const result = await agent.generate('Extract the customer name.', {
structuredOutput: {
schema: z.object({ name: z.string() }),
jsonPromptInjection: 'system',
instructions: 'Return a JSON object with a name field.',
},
});
```
Output is still validated against `schema`, so you stay responsible for keeping `instructions` in sync with the fields you need. When no separate structuring `model` is configured, `instructions` is also serialized across the durable agent boundary, so the same behavior applies to durable runs. Behavior is unchanged when `instructions` is absent or blank: the serialized schema is still injected as before.
- Added A2A v1.0 remote subagent delegation with explicit protocol selection on `A2AAgent`. Existing integrations continue to use v0.3 by default. (#24134)
```typescript
const remoteAgent = new A2AAgent({
url: 'https://example.com/.well-known/agent-card.json',
protocolVersion: '1.0',
});
```
- Added an `api` option to custom OpenAI-compatible model configs so a custom `url` can target the OpenAI Responses API. Set `api: "responses"` alongside `url` to reach `/v1/responses` (for example to combine function tools with reasoning models on gateways that require it); it defaults to `"chat"`, so existing configurations are unchanged. (#24029)
```typescript
const agent = new Agent({
id: 'my-agent',
name: 'My Agent',
instructions: 'You are a helpful assistant',
model: {
id: 'custom/my-model',
url: 'https://your-endpoint.com/v1',
api: 'responses',
},
});
```
- Added the `MCP_SERVER_REQUEST` span type, `MCPServerRequestAttributes`, and `EntityType.MCP_SERVER` for requests served by a Mastra `MCPServer`. Added a `skipToolSpan` tool execution option so a caller that already owns a span can run a tool without an extra `TOOL_CALL` span: (#24150)
```ts
await tool.execute(args, {
tracingContext: { currentSpan: requestSpan },
skipToolSpan: true,
});
```
See https://github.com/mastra-ai/mastra/issues/23921
- Added `usedFallbackValue` to agent `generate()` and `stream()` results. With `structuredOutput.errorStrategy: 'fallback'`, `result.object` was previously indistinguishable from a real answer once the configured `fallbackValue` had been substituted: `finishReason` stayed `'stop'`, `tripwire` stayed empty, and the only marker was a `metadata.fallback` flag on the internal `object-result` chunk, which never reached the result. The result — and the `onFinish` callback payload — now report the substitution directly, for both the native and separate-structuring-model paths. (#24424)
```ts
const result = await agent.generate('Summarize the ticket.', {
structuredOutput: { schema, errorStrategy: 'fallback', fallbackValue: { summary: 'unknown', tags: [] } },
});
if (result.usedFallbackValue) {
// result.object is the fallback, not something the model produced
}
```
- Add a `messageHistory` memory option for token-budgeted conversation history. `messageHistory: { maxTokens, atMaxRemoveTokens? }` counts the complete prompt against the token budget and drops the oldest remembered messages in chunks. It never removes the current turn's input, responses, context, or system messages. During agent runs, a per-thread boundary is advanced and persisted so trimmed history stays out of subsequent turns without deleting stored messages. (#23238)
When `messageHistory` is set without an explicit `lastMessages`, the default 10-message cap is dropped so the token budget alone defines the window. `lastMessages` remains supported and can be combined with `messageHistory`, but counting messages is a poor proxy for context size and `lastMessages` is now soft-deprecated in favour of `messageHistory`.
```ts
import { Memory } from '@mastra/memory';
const memory = new Memory({
options: {
messageHistory: { maxTokens: 8_000, atMaxRemoveTokens: 2_000 },
},
});
```
_Also in: @mastra/client-js@1.47.0, @mastra/memory@1.31.0, @mastra/server@1.68.0_
- Added canonical trace-query field descriptors and bounded discovery contracts for queryable fields and values. (#24057)
```ts
import {
getTraceQueryCanonicalFieldDescriptors,
parseGetTraceQueryValuesArgs,
planTraceQueryValues,
} from '@mastra/core/storage';
const fields = getTraceQueryCanonicalFieldDescriptors('spans');
const values = await storage.getTraceQueryValues(
planTraceQueryValues(
parseGetTraceQueryValuesArgs({
timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-02T00:00:00Z' },
predicateScope: 'spans',
path: 'model',
}),
),
);
```
- Added MCP 2026-07-28 server contracts to `MCPServerBase` while keeping MCP 1.x servers working unchanged. (#23875)
- Added `mcpVersion` to `MCPServerBase`. A server that sets it to `2` resolves `executeTool` to `MCPToolExecutionResultV2`, which reports a suspended tool (`{ status: 'suspended', suspendPayload, resumeSchema }`) instead of a bare result. Thrown errors and schema failures still reject. Existing 1.x servers need no new properties.
- Added `context.mcp.protocolVersion`, set to `'2026-07-28'` by 2.x servers. `context.mcp.extra`, `log` and `progress` keep the same shape on both server versions.
- Added `suspend`, `resumeData` and `suspendPayload` at the top level of the tool execution context for direct and MCP 2.x execution. Agents and workflows keep nesting them under `agent` and `workflow` until the next core major.
- Added `suspendPayload` to tools resumed by agents (including durable agents) and workflows, alongside `resumeData`.
- Deprecated the surfaces MCP 2026-07-28 removed, for removal in the next core major: `startSSE`, `startHonoSSE`, `MCPServerSSEOptions`, `MCPServerHonoSSEOptions`, `MCPServerHTTPOptions.options`, `context.mcp.elicitation.sendRequest`, `context.mcp.extra.sendRequest` and `context.mcp.extra.sendNotification`. On a 2.x server the deprecated `context.mcp` members throw with a message naming the replacement. `startSSE` and `startHonoSSE` are no longer abstract, so 2.x servers do not implement them.
Tools that need input mid-execution use the suspend/resume primitives `createTool` already has:
```ts
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';
const confirm = createTool({
id: 'confirm',
description: 'Ask for confirmation',
inputSchema: z.object({ amount: z.number() }),
outputSchema: z.boolean(),
suspendSchema: z.object({ phase: z.literal('confirm'), amount: z.number() }),
resumeSchema: z.object({ confirmed: z.boolean() }),
execute: async ({ amount }, context) => {
await context.mcp?.log?.('info', 'asking for confirmation', { amount });
if (!context.resumeData) {
await context.suspend?.({ phase: 'confirm', amount });
return;
}
return context.resumeData.confirmed;
},
});
```
- Added a stable resource-limit error for bounded trace-query field and value discovery. (#24169)
```ts
import { TraceQueryResourceLimitError, planTraceQueryValues } from '@mastra/core/storage';
const plan = planTraceQueryValues({
timeRange,
predicateScope: 'spans',
path: 'model',
search: 'claude',
limit: 25,
});
try {
await observability.getTraceQueryValues(plan);
} catch (error) {
if (error instanceof TraceQueryResourceLimitError) {
console.error(error.code);
}
}
```
#### Patch Changes
- Fix `agent.generate()` / `.stream()` reporting a caller `abortSignal` cancellation as a processor tripwire. When a run is aborted and no processor triggered a tripwire, the result now reports `finishReason: 'aborted'` and leaves `tripwire` undefined, instead of synthesizing a generic `{ reason: 'Processor tripwire triggered' }`. Genuine processor tripwires are unaffected. (#24008)
- Update provider registry and model documentation with latest models and providers (`1e68460`)
- Improved runtime portability by using Web Crypto for cryptographic operations that do not require changes to existing synchronous APIs. (#24369)
- Fixed durable agents passing `stepNumber: 0` and an empty `steps` list to `processLLMRequest`, `processLLMResponse`, and `processOutputStep` on every step. Processor hooks now receive the correct zero-based step index and the running step list, matching non-durable agents. Fixes #24279 (#24293)
- Fixed claimed thread owners acting on a redelivered idle signal twice. A signal that a PubSub backend redelivers is now handled once while the runtime still remembers its request id, so it no longer queues the turn again or starts a second run for the same run id. If the reply to the caller never reached the backend, the redelivery re-sends it instead of reprocessing the signal. (#24467)
- Documented the `SpanOutputProcessor.process()` contract: mutate the span you receive and return the same instance, or return `undefined` to drop it. Returning a copy is not supported because `exportSpan()` and `isValid` are instance members of the live span. Related to #23796 (#24048)
_Also in: @mastra/observability@1.17.9_
- Fixed goals that never ended after reaching their evaluation budget while waiting for user input. Once a goal uses all of its `maxRuns` evaluations, later chat turns now park it as `paused` with the budget reason. Previously they reported it as still running and rendered a `continue` verdict on every turn. Raise `maxRuns` and resume the goal to continue it. (#24402)
- Fixed structured output fallback instructions to include the requested JSON schema. (#24457)
- Ignore malformed numeric `Retry-After` values instead of parsing them as years and delaying retries up to the configured cap. (#23997)
- Fixed streamed tool events and assistant messages to retain their originating thread when a session switches threads. Consumers can use tool-event threadId to ignore delayed output from a previous conversation. (#24313)
- Fixed span output processors that return a copy of the span instead of the same instance. Previously this threw `TypeError: processedSpan?.exportSpan is not a function` out of `startSpan()` for started/updated spans, and silently dropped every ended span. Now the span is dropped with a logged processor error naming the processor, and the `SensitiveDataFilter.process()` docstring correctly states that it mutates the span in place. Fixes #23796 (#24048)
_Also in: @mastra/observability@1.17.9_
- **`createCodingAgent` now repairs recoverable bad requests instead of replaying them.** (#24469)
The default error processors ran the blind stream retry first. It claims these rejections, so a request that `ProviderHistoryCompat` or `PrefillErrorHandler` knows how to fix was retried unchanged, earned the same rejection, and surfaced as a failed turn. Both repair processors now run ahead of it.
What changes for you: a coding agent hitting a malformed tool-call id or an assistant-prefill rejection now retries a corrected request rather than an identical one. The retry budget is unchanged.
Pass your own `errorProcessors` to opt out; the default list is only used when you pass none.
- Fixed query-parser, static-site generation, parseBody, and XSS security issues by updating hono to 4.13.7. (#24027)
_Also in: @mastra/deployer@1.68.0, @mastra/editor@0.15.1, @mastra/factory@0.16.0, @mastra/hono@1.7.10, @mastra/inngest@1.9.0, @mastra/mcp@2.0.0, @mastra/mcp-docs-server@1.2.27, @mastra/mcp-registry-registry@1.1.3, @mastra/next@0.2.26, @mastra/server@1.68.0, @mastra/tanstack-start@0.2.26_
- Fixed image and file URLs returned from a tool's toModelOutput being corrupted before reaching the model. Fixes https://github.com/mastra-ai/mastra/issues/22618 (#24371)
- Remote image-url and file-url tool results are no longer rewritten into a media part with the URL stuffed into the Base64-only data field, and their providerOptions are no longer dropped.
- URL parts are preserved as-is and converted to the correct shape for the target model's specification version: image-url/file-url for v3 models, url-tagged file parts for v4 models.
- Messages persisted by older versions with a URL in the media data field are healed the same way.
- Fixed structured output to reject responses truncated by token limits or content filters. (#24464)
- Fixed model settings returned by an input processor being ignored when the agent also configured them. Fixes https://github.com/mastra-ai/mastra/issues/22395 (#24429)
Values returned from `processInputStep` or `prepareStep` now take effect for the model call, including `maxRetries`. Previously any setting the model list also specified won the conflict, so a processor could not change it. This affected single-model agents as well as fallback chains.
A processor that returns only some settings (for example just `{ temperature }`) keeps the configured retry and timeout limits. Inference telemetry now reports the settings the model actually received.
- Fixed agent and workflow delegation so model-driven resumes use framework-persisted suspended tool-call identity, including falsy resume payloads, and cannot select sibling runs by supplying a run ID. Successful resumes now retire every persisted representation of only the selected suspension. (#24258)
- Fix durable agents dropping `writer.custom()` / `writer.write()` emissions from tools. The durable tool-call step now provides a `writer` (`ToolStream`) in the tool execution context, so tools resolved from the Mastra registry on cross-process runs (e.g. an `@mastra/inngest` worker) receive a working writer instead of `undefined`. Fixes #24196. (#24229)
- Fixed ModelRouter URL capability discovery failures being hidden and permanently cached. See #24436. (#24454)
- Fix `EventedAgent.executeWorkflow()` emitting a run's terminal error from an un-awaited `.catch`, which could surface as an `unhandledRejection` during shutdown. Both terminal-error emission sites now route through `emitErrorInBackground()`, so a publish failure (e.g. pubsub/storage closed while the run finishes) is logged as a warning instead of crashing the process — matching the `DurableAgent` behavior fixed in #23168. (#24083)
- Fixed queued follow-up messages being saved without an answer after the active run is aborted. (#23926)
- Pending signals wait for a fresh run instead of being consumed by an aborted run.
- Queued messages no longer inherit the previous run's cancellation signal. Explicit cancellation supplied for a queued message is preserved.
- Pending signals stay ahead of idle messages when preparation fails, including after cancellation of a queued startup.
- Forwarding signals to a new thread owner does not duplicate the same ID already waiting in that owner's pre-run or pending signal queue.
- Fixed durable agent approval resumes so live assistant events and token usage are recorded once. (#23116) (#24265)
- Fixed attachment download failures bypassing error processors in regular agent runs. Processors can now repair the message context and retry when a historical attachment becomes unavailable. (#23988)
- Fixed completed workflow runs remaining in memory after resume. (#24267)
- **Breaking change** (#22978)
Agent Controller streams now emit one complete `message_start` payload, followed by ID-addressed `message_update` events for text, reasoning, and message-part changes. `message_end` now contains only the message ID.
**Migration**
Previously, consumers read the complete message from each update. Now, store the start payload by ID and apply subsequent updates to that message:
```ts
if (event.type === 'message_start') messages.set(event.message.id, event.message);
if (event.type === 'message_update') applyUpdate(messages.get(event.id), event.event);
```
_Also in: @mastra/client-js@1.47.0, @mastra/code-sdk@1.8.0, @mastra/server@1.68.0_
- CommonJS consumers can now use core token counting, slug generation, and workspace operations without ESM loading errors. (#24272)
- Fixed evented workflows failing with "condition is not a function" when a dountil or dowhile loop body is a nested workflow and events go through a serializing pubsub such as Redis Streams. The loop condition is now read from the live workflow registry instead of the serialized event payload, which cannot carry functions. Fixes #23111. (#24366)
- Surface filesystem read failures in the workspace `grep` tool instead of silently reporting them as a complete "0 matches" search. (#24053)
- **Partial-search reporting.** When a directory cannot be listed or a file cannot be read, those failures are now counted and appended to the result summary (`N paths skipped: read error`) so a partial search is distinguishable from a genuinely empty one.
- **Missing targets.** If the target path does not exist (`ENOENT`, or `ENOTDIR` when a path component is a file), the summary reports `target path not found: nothing searched` rather than a plain "0 matches".
- **Strict mode.** A new construction-time `strict` option makes any such read failure throw instead of being skipped, for callers that want to fail fast.
- **`.gitignore` handling.** `loadGitignore` now only swallows a genuinely-absent `.gitignore` (`ENOENT`) and rethrows permission/IO errors, which previously changed the search scope silently.
Enable strict mode when constructing the workspace tools to fail fast when any part of the target cannot be read:
```ts
import { createWorkspaceTools, WORKSPACE_TOOLS } from '@mastra/core/workspace';
const tools = await createWorkspaceTools(workspace, undefined, { grep: { strict: true } });
// Throws instead of reporting a partial result when a path cannot be read.
const result = await tools[WORKSPACE_TOOLS.FILESYSTEM.GREP].execute({ pattern: 'needle' }, { workspace });
```
- Fixed tool approval failing on runs with large workflow snapshots. `agent.approveToolCall()`, `declineToolCall()`, and `resumeStream({ toolCallId })` could throw `AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED` for a run that was genuinely suspended. This happened when saving a large snapshot took longer than the fixed 2-second validation window. The validator now waits while the run is still persisting its suspension, and rejects right away when the tool call is stale or the run has already finished. Fixes #22413. (#24171)
- Fixed aborting suspended agent runs so parked tool calls are denied, the thread is released, and messages sent immediately after Stop receive a response. (#24266)
- Fixed workflow and agent delegation tools adopting a malformed model-supplied `suspendedToolRunId`. Some models emit the literal string `"null"` for this optional auto-resume field on fresh calls; because that string is truthy, two independent workflow-tool calls could collide on a single run (silently dropping one), and agent delegation could try to resume a non-existent run and crash. Sentinel strings (`"null"`, `"undefined"`, `"none"`, `"nil"`) are now treated as absent at every point where the field crosses the model boundary. (#24121)
**Behavior change**: workflow tools now only honor a supplied `suspendedToolRunId` together with `resumeData` — fresh calls always receive a framework-generated unique run id. If you pinned `args.suspendedToolRunId` in a `beforeToolCall` hook (the documented workaround for run-id collisions), that pin is no longer applied on fresh calls — and is no longer needed. Fixes #23739.
- Fixed workspace `requireReadBeforeWrite` falsely rejecting writes with "has not been read" after suspend/resume and between conversation turns (#23772). Read records now persist per memory thread in the `threadState` storage domain — the same store that holds task lists and goal objectives — so a file read before a tool suspends (for example, awaiting plan approval or a `requireApproval` tool) no longer needs a wasteful re-read after the run resumes, including on serverless runtimes where the process is torn down between suspend and resume when a persistent storage adapter (for example LibSQL or Postgres) is configured. No configuration is needed: with in-memory storage, records survive suspend/resume within the same process, and runs without a memory thread or Mastra storage fall back to per-run tracking. Files modified on disk after being read still require a re-read before writing. Read records are scoped to the filesystem they were read from (provider + base path), so threads that resolve a different workspace or filesystem between requests cannot satisfy the write gate with a read from a different file store. (#24122)
- Fixed structured output with a top-level array of primitives (e.g. `z.array(z.string())` or `z.array(z.number())`) silently resolving `response.object` to an empty array. Array elements that are strings, numbers, booleans or null are now returned from `generate()`, `stream().object` and `objectStream`, and the final result is validated against what the model actually returned. Fixes #23980. (#24055)
- Fixed structured output fallback warnings to use the agent logger and aligned the loop fallback logger with error-level defaults. (#24455)
- Lowered the implicit error-processor retry cap from 10 to 3 and made it visible. Configuring `errorProcessors` without an explicit `maxProcessorRetries` previously allowed a processor that always requests a retry to drive 11 model calls for a single turn, silently and regardless of `maxRetries: 0`. The cap is now 3 (4 model calls worst case) and a one-time warning is logged naming the setting to configure. Every built-in error processor self-limits to at most one retry, so only a processor that never stops asking is affected; callers who need a larger budget can set `maxProcessorRetries` explicitly. (#24503)
Also aligned the durable execution path with the standard loop: `processAPIError` now runs on the final attempt too, so a processor can observe and report a terminal failure instead of being skipped once the retry budget is spent.
- Fixed unbinding a thread so it no longer aborts the run a different agent instance is executing. Detaching from a thread, switching threads, or tearing down a session now stops only run work owned by the local process; explicit cancellation still stops the run wherever it is executing. Thread peer discovery now marks advertisements published by the agent that asked, so a caller can tell its own threads apart from an in-process peer agent's. (#24510)
- Fixed idle wake signals losing the caller's request context and misreporting their outcome when the thread had a claimed owner. (#24347)
A wake that starts a run on a locally claimed owner now applies the incoming `streamOptions.requestContext` to that run. The claimed owner's own stream options were used verbatim, so a dispatcher waking a session on behalf of an authenticated caller started the run without that identity, and downstream lookups that require a caller — a workspace resolver, for example — failed. The owner's remaining options stay authoritative, since the run executes inside the owner's session.
The same path now reports `wake` instead of `deliver`. `deliver` promises that no run started locally and that the signal joined a run already in flight; callers that waited on that run, or re-sent because they believed it was still busy, never saw the work happen.
A claimed owner in another process is unchanged: the wake event carries no `requestContext`, so a remote owner still starts the run with its own options.
- Bound the output that the `execute_command` tool retains while a foreground command streams. Previously the tool kept its own unbounded copy of stdout and stderr, which was only read on the error path but could exhaust memory or kill the process with `RangeError: Invalid string length` on very large command output. (#24486)
- Fixed a WebSocket denial-of-service advisory by updating ws to 8.21.3. (#24027)
_Also in: @mastra/deployer@1.68.0, @mastra/hono@1.7.10, @mastra/voice-google-gemini-live@0.14.11, @mastra/voice-inworld@0.4.3, @mastra/voice-openai-realtime@0.14.1, @mastra/voice-xai-realtime@0.2.11_
- Add an `openai-orphan-item-id` compat rule so a turn can recover when stored history contains an assistant message with an OpenAI `itemId` (`msg_…`) but no `reasoning` item. OpenAI's Responses API replays such a message as an `item_reference` and rejects the request with a non-retryable 400 (`Item 'msg_…' of type 'message' was provided without its required 'reasoning' item`), which today ends the turn. (#24345)
**What changes for you.** A thread that was permanently stuck on that 400 can now recover on the same turn. The rule removes the item references from every message shaped like the orphan so they replay by value, then asks for one retry. Azure is covered on the same footing as OpenAI. Unrelated provider data survives the repair: cache counts, reasoning-token counts and logprobs are left intact.
**You have to install it.** A plain `Agent` has no compat processor configured. Register `ProviderHistoryCompat` in `errorProcessors` — reactive recovery needs the error lane, and on the durable path the API-error pass runs only when that list is non-empty.
```ts
import { Agent } from '@mastra/core/agent';
import { ProviderHistoryCompat } from '@mastra/core/processors';
const agent = new Agent({
// ...
errorProcessors: [new ProviderHistoryCompat()],
});
```
**The repair is in-memory for the current turn.** The healed message is not written back to storage, so each later turn on that thread still spends one rejected request before recovering — the same behavior as the existing `anthropic-tool-id-format` rule.
**It fires only after that error,** never on a thread that has not hit it. When it does fire it repairs every orphan-shaped message in the history, because the error names only the first item and one retry is available. Valid reasoning-free messages caught by that breadth still replay correctly, by value rather than by reference. One exception: a hosted `tool_search` result cannot replay by value, so it is dropped from the prompt rather than replayed. On a genuinely orphaned message that is the right outcome; on a valid message swept along with it, the model loses that search result and would have to look it up again.
- Fixed a processor-forced mid-turn retry discarding steps the assistant had already completed. When an output processor aborted a step with `{ retry: true }`, the whole in-flight assistant message was deleted. That took the reasoning and tool calls from earlier steps in the same turn that had already been accepted. The retry now discards only the rejected step. The model still never re-sees the rejected answer, and it keeps every step it had already accepted. (#24344)
Two things stop happening on this retry path as a result. An accepted tool call is no longer thrown away and run a second time. And with OpenAI reasoning models, the saved message no longer ends up carrying an assistant `itemId` (`msg_…`) with no matching `reasoning` item. OpenAI rejects that shape with a non-retryable HTTP 400: `Item 'msg_…' of type 'message' was provided without its required 'reasoning' item`. The rejection then recurs whenever that stored history is replayed on a later turn (#22291).
- Fixed replay of OpenAI-hosted `tool_search` across turns. The Responses API gives a hosted search's call and its output distinct item ids (`tsc_…` / `tso_…`); Mastra now keeps both on the stored tool part and splits them back apart when building a prompt, so each side replays as its own `item_reference` instead of the same one twice. Hosted searches are also kept provider-executed through a round trip, so their result is no longer re-serialized as a client-mode `tool_search_output`. (#23611)
Conversations recorded before this fix kept only one of the two ids, so that hosted search pair can no longer be replayed faithfully — the single id would be referenced twice. A completed hosted search (succeeded or errored) with only one id is now omitted when building a prompt, and the model rediscovers the tool on the next turn; the rest of the conversation is unaffected and the part is still retained in response messages, so nothing is deleted from stored history. In-flight searches, which legitimately carry only a call id, and client-executed tools named `tool_search` are untouched.
- Fixed thread aborts so callers can require the intended run to still be active before it is stopped. (#24452)
- Fixed manually renamed thread titles being replaced by Observational Memory. Explicitly regenerating a title enables automatic title updates again, as do programmatic title writes that opt out of pinning: (#23791)
```ts
await session.thread.rename({ title: 'Initial title', pin: false });
```
Fixes #22421
_Also in: @mastra/memory@1.31.0_
- Fixed tool calls missing from MODEL_GENERATION span output when agents run through the streaming loop or durable workflows. Observability exporters such as PostHog now receive the tool calls, so PostHog's Tools tab and `$ai_output_choices` show them for streamed generations. Fixes #24291 (#24306)
- Reduced the core install footprint by embedding the MCP context declarations instead of installing the MCP server SDK. Existing MCP tool context types remain compatible with the SDK. (#23998)
- Improved file-based storage performance by removing a redundant filesystem stat call for every directory entry when listing domain and skill files, and by skipping the ISO date check for strings that can't be dates. As part of this change, file listings no longer follow symbolic links, so a symlink pointing at a stored file or directory is no longer included in results. Fixes #23752 (#24056)
- Fixed active goals being reported to the agent as cancelled, and stopped the goal being repeated in the model's context on every step. (#24342)
A goal that was still running could be projected as having no objective, which the agent reads as "the goal was cancelled" and stops working on it. That happened when the goal state processor could not reach storage, including when `inputProcessors` was configured as a function and the processor never received the Mastra instance. The last known objective is now kept when storage cannot be read, and the instance is propagated to processors contributed by signal providers. A stale cached pause record could also be trusted over storage; the cached record is now only trusted when it shows the goal active, and storage is re-read otherwise.
The projection is append-only, so re-emitting it duplicated the objective in context instead of updating it. It re-emitted on every attempt because the change it keyed on advanced each time. An objective that is already in context is now left alone.
- Fixed a chat-library security advisory by updating chat to 4.37.0. Agent and factory chat APIs are unchanged. (#24027)
_Also in: @mastra/factory@0.16.0_
- Fixed the model capability registry trusting a nested provider's capabilities over the gateway actually serving the request. When a gateway such as OpenRouter lists a routed model (e.g. `openrouter/deepseek/deepseek-v4-flash`) without attachment support, that answer is now authoritative instead of falling back to the upstream provider's file, which caused Observational Memory to forward images to endpoints that reject them ("No endpoints found that support image input"). (#23685)
- Fixed shared tools exposing `_background` to agents that do not support background execution. (#23120)
Agents now advertise `_background` only for eligible tools. `suspendedToolRunId` and `resumeData` remain scoped to resumable tools. Repeated schema conversions no longer add nested validators.
- Fixed `mastra_workspace_read_file` never returning media parts with strict-schema providers (e.g. OpenAI, Vercel AI Gateway). Media surfacing is now decided from the file's mime type and tool config instead of the absence of the optional `encoding` argument, so configured media within `maxMediaBytes` is returned as a native file/image part regardless of the model-supplied `encoding`. (#24082)
- Improved `Agent.listSuspendedRuns()` performance when filtering by `threadId`. The thread filter is now passed down to the storage query, so supporting storage adapters narrow results inside the database instead of loading and parsing every suspended snapshot for the resource. Fixes https://github.com/mastra-ai/mastra/issues/22627 (#24376)
```ts
await agent.listSuspendedRuns({ threadId: 'thread-123' });
```
- Fixed streamed `PIIDetector` redaction so sensitive values split across chunks are redacted and overlapping detections do not remove neighboring text. Redacted streams may briefly delay trailing text until a later text or non-text chunk. (#24189)
- Fixed invalid model timeout settings being silently ignored. (#24463)
- Fixed background tool results to carry task identity and lifecycle status in metadata, including resumed tasks and streamed completions. Consumers can identify background work without interpreting tool output text. (#24313)
- Fixed subscribed thread streams missing signals that arrive while an aborted run is being cleaned up. (#23696)
- Fixed durable agents giving delegated sub-agents a shared, parent-derived memory scope instead of one derived from the calling user. The durable execution path now stamps the caller's thread and resource identity onto delegated agent-tool calls, matching the regular agent loop, so each user's sub-agent conversations stay isolated and memory continuity works across turns. Fixes #23903. (#23981)
- Fixed the `skill_read` tool corrupting binary skill files. A PNG or PDF is now reported as `Binary file: <path> (<bytes>)` with its exact size and is never decoded into the model context. Previously the file was decoded as UTF-8 before its bytes were inspected, which inflated the byte count and could put garbled text into the conversation. Binary detection now also covers NUL-free binaries such as PDFs. Text files anywhere in the skill, including under `assets/`, still read as text. (#24101)
- Fixed skill discovery for `Workspace` instances that use a dynamic `filesystem` resolver. (#24317)
When `skills` is configured without `skillSource`, discovery now uses the filesystem resolved for the request. It no longer reads skills from the server's local disk, so host-local skills cannot appear for other tenants and each tenant's own skills are found.
Skill discovery and search state are isolated per resolved filesystem, with a bounded cache so per-request filesystems do not grow the search index. Unscoped `workspace.search()` no longer returns request-scoped skill documents (from dynamic `skills` resolvers or resolver-backed filesystems) and still returns up to `topK` regular documents. Static filesystems, explicit `skillSource`, and the no-filesystem fallback are unchanged.
```ts
const workspace = new Workspace({
filesystem: ({ requestContext }) => getTenantFilesystem(requestContext.get('orgId')),
skills: ['skills'],
});
// Now reads from the tenant's filesystem, not process.cwd()
const scoped = await workspace.skills!.getScoped!({ requestContext });
await scoped.list();
```
- Fixed fallback models restarting from the primary model between agent tool-call steps. (#23725)
- Fixed requests failing with a 400 "Requests ending with a model turn are not supported" error on Gemini 3 models when the conversation ends with an assistant message. Fixes #23320. (#23609)
- The trailing-message guard that Anthropic models already had under native structured output now also covers Google, Vertex AI, and gateway-routed Gemini 3+ models, for every request rather than only structured-output ones.
- The guard is attached whenever an agent has input processors, because a processor can switch the model mid-step. It checks the final model before running and is skipped entirely, with no processor span, when that model does not need it.
- The guard mirrors prompt conversion: assistant messages that end on a tool result are left alone, and history that ends on assistant text followed by an unfinished tool call is guarded correctly.
- The synthetic continuation turn is added as request-only context instead of being saved to the thread, so memory and chat UIs no longer show a "Continue." or "Generate the structured response." message the user never sent.
- `PrefillErrorHandler` also recognizes the Gemini error so the reactive retry path covers it too.
- Explicitly versioned Gemini 2.x models and Anthropic prefill behavior are unchanged. Unversioned Google ids such as `gemini-flash-latest` or `gemma-*` are guarded conservatively because they can resolve to a Gemini 3 model.
- Fixed a memory and connection leak in `DurableAgent`. After a run finished, the automatic cleanup timer released the run's registry state but left the stream subscription attached for the life of the process, so memory (and on Redis/Valkey streams, a client connection per run) grew with every turn. `stream()`, `resume()`, and `recover()` now release the subscription during automatic cleanup, the same way `observe()` already did. Fixes #24070. (#24104)
- Fix `stableStringify` dropping own `__proto__` keys, which collapsed distinct values onto one cache key (affecting message dedup in `CacheKeyGenerator.fromDBParts` and the agent response cache). (#24081)
- Removed the redundant direct Ajv dependency. Schema compatibility bundles its validator and standalone types without requiring a separate Ajv installation. (#23998)
- Fixed token usage and finish reason handling when a model router model is wrapped with AI SDK v7 `wrapLanguageModel`. The AI SDK compatibility shim nests usage and finish reason one level deeper, which made `result.usage` come back as the string `"0[object Object]…"` and made multi-step agent turns run to `maxSteps`. Mastra now unwraps repeated envelopes so token counts stay numbers and the loop stops on `stop`. Fixes https://github.com/mastra-ai/mastra/issues/23735 and https://github.com/mastra-ai/mastra/issues/23746 (#23914)
- **Fixed commands that read stdin hanging until timeout** (#24336)
Commands that read standard input without being given anything to read — a bare `cat`, `grep` or `rg` with no path argument, `read` — used to block until the command timeout expired, leaving tools stuck for minutes.
`executeCommand()` now runs commands with standard input closed, so anything that reads stdin sees end-of-input immediately and exits:
```ts
// previously hung until the timeout when the command read stdin
await sandbox.executeCommand('/bin/sh', ['-c', 'rg -n "pattern" --files-with-matches | head']);
```
`execute_command` with `background: true` also closes standard input: a background command that reads stdin now sees end-of-input instead of staying alive until it is killed. Retrieving that background process's handle no longer provides a writable stdin — use `processes.spawn()` with the default `'pipe'` mode for interactive processes.
`processes.spawn()` keeps a writable stdin by default so long-running processes can be driven with `sendStdin()`. It now also accepts a public `stdinMode` option — pass `'ignore'` to close stdin when nothing will feed it:
```ts
// opt-in: close stdin on a spawned process
const handle = await sandbox.processes.spawn('node server.js', { stdinMode: 'ignore' });
```
Output-only execution paths (`executeCommand()` and `execute_command` with `background: true`) pass this option automatically. Honored by the local, Docker, and E2B providers; other providers may not expose stdin control.
- Fixed execute command exit events to preserve provider termination details for Studio status displays. (#24453)
- Fixed model router cache initialization so Cloudflare Workers can load bundles before request handling begins. (#24454)
- Sanitize delegated agent lifecycle payloads in thread-stream broadcasts to prevent conversation data from compounding across delegation levels. (#24399)
- Fixed strict structured-output failures when using a separate structuring model. Failed requests now retry up to `maxProcessorRetries`. Warn and fallback behavior is unchanged. (#24059)
- Fixed agent thread streams and durable stream adapters leaving PubSub deliveries unacknowledged. Every delivered event is now acknowledged once handled, so persistent backends like Redis Streams or GCP Pub/Sub no longer accumulate pending messages on these subscriptions. (#24462)
- Throw a clear isolation error when LocalSandbox is configured to use macOS Seatbelt on Windows. (#24273)
- Fixed durable agents on cross-process engines (like Inngest) dropping `requestContext` values written by input processors. (#24133)
Values set with `requestContext.set(...)` inside an input processor now reach tools and scorers running on a separate worker process. Framework-internal entries (model version overrides, memory instances, auth tokens) are still kept out of the persisted workflow input. Fixes #23904
- Fix `ToolCallFilter` with `preserveModelOutput` retaining raw fallback tool results in the model prompt. Filtered results now preserve only explicitly produced compact model output, while raw tool arguments and results remain excluded. Fixes #22630. (#23962)
- Fixed `TokenLimiterProcessor` truncation (`strategy: 'truncate'`) splitting UTF-16 surrogate pairs. (#24096)
Truncated text that ends inside an emoji or other astral character no longer contains a lone surrogate. Lone surrogates are replaced with `U+FFFD`, so the output round-trips through UTF-8 unchanged and strict JSON consumers can reuse truncated messages as history. This matches the repair already applied to workspace tool output.
- Fixed structured output failures to preserve validation errors and raw invalid model output for diagnostics. (#24465)
- Fixed sub-agent failures being reported to the supervisor as an empty successful result when using `stream()`. A sub-agent whose model call fails (for example an invalid API key) now produces an error tool result in both `generate()` and `stream()`, with a generic error message by default. The underlying cause remains available to the completion hook and diagnostics rather than being included in the supervisor's tool-result text. `onDelegationComplete` receives `success: false` with the error, and calling `bail()` from that hook on a failed delegation now stops the supervisor loop as expected. Partial streamed child messages and available result metadata remain available to the completion hook. Failed invocations retain child-thread references so Studio can restore the partial transcript after reload. Background failures follow the configured retry policy, with completion hooks invoked per attempt. A failure hook's `resultText` replaces the error text seen by the supervisor without recovering the failed delegation or discarding its original error. (#23420)
- Fixed unrecorded conditional arms appearing successful when time-travelling past them. Preserve explicit replacement output for recorded failed arms. (#24184)
- Fixed workflow experiments skipping a suspended branch with resume data when an earlier suspended branch has none. The dataset experiment auto-resume loop now scans all suspended branches and resumes the first one with matching `resumeSteps`/`resumeData`, instead of stopping at the first suspended branch. (#24041)
- Allow setting `resourceId` on workflow schedules so scheduled runs are attributed to a resource. The optional `resourceId` is accepted on create and update, returned in schedule responses, and carried through both the scheduler and manual fire paths into the run snapshot, enabling multi-tenant correlation and filtering. `schedules.list({ resourceId })` now matches workflow schedules too. Unlike agent schedules (where `resourceId` is part of thread identity), a workflow schedule's `resourceId` is pure run-attribution metadata and can be updated via PATCH. `resourceId` is optional, so existing schedules and callers are unaffected. (#24173)
```ts
// Attribute scheduled runs to a resource
const schedule = await mastra.schedules.create({
workflowId: 'daily-report',
cron: '0 9 * * *',
resourceId: 'tenant-123',
});
// resourceId can be updated later
await mastra.schedules.update(schedule.id, { resourceId: 'tenant-456' });
```
_Also in: @mastra/server@1.68.0_
- Add `'canceled'` to the public workflow step-status contract. `StepResult`, `SerializedStepResult`, and the derived `WorkflowStepStatus` now include a `StepCanceled` variant, matching the `status: 'canceled'` results the runtime already emits and persists for canceled control-flow steps (e.g. `foreach` and loops). Typed consumers of `getWorkflowRunById()`, `WorkflowState.steps`, and lifecycle callback step results can now represent canceled steps without casts. (#24098)
```ts
import type { WorkflowStepStatus } from '@mastra/core/workflows';
const run = await workflow.getWorkflowRunById(runId);
const step = run?.steps?.['process-items'];
if (step && !Array.isArray(step)) {
const status: WorkflowStepStatus = step.status; // may now be 'canceled'
if (status === 'canceled') {
console.log('canceled with partial output:', step.output);
}
}
```
Note: if you have an exhaustive `switch` or a `Record<WorkflowStepStatus, ...>` over step statuses, TypeScript will now require a `'canceled'` case. This reflects a value the runtime was already producing.
- Fixed durable agent runs so thread title generation no longer delays completion. (#24335)
### [@mastra/ai-sdk@1.10.4](https://github.com/mastra-ai/mastra/blob/@mastra/ai-sdk@1.10.4/client-sdks/ai-sdk/CHANGELOG.md)
#### Patch Changes
- Fixed progressive streams for deeply nested agents. `data-tool-agent` and `data-tool-agent-step` parts now include the ordered delegation path, nesting depth, and immediate parent agent ID. (#21735)
### [@mastra/arize@1.3.17](https://github.com/mastra-ai/mastra/blob/@mastra/arize@1.3.17/observability/arize/CHANGELOG.md)
#### Patch Changes
- Map the OpenInference `LLM` span kind to the exported `chat` call (`model_inference`) instead of `model_generation` and `model_step`, so Phoenix counts each model call's tokens once. The generation loop and its steps are now `CHAIN` spans. (#23910)
### [@mastra/arthur@0.4.17](https://github.com/mastra-ai/mastra/blob/@mastra/arthur@0.4.17/observability/arthur/CHANGELOG.md)
#### Patch Changes
- Map the OpenInference `LLM` span kind to the exported `chat` call (`model_inference`) instead of `model_generation` and `model_step`, so each model call's tokens are counted once. The generation loop and its steps are now `CHAIN` spans. (#23910)
### [@mastra/auth-auth0@1.2.4](https://github.com/mastra-ai/mastra/blob/@mastra/auth-auth0@1.2.4/auth/auth0/CHANGELOG.md)
#### Patch Changes
- Fixed a jose security advisory by updating jose to 6.2.11. Auth token verification APIs are unchanged. (#24027)
_Also in: @mastra/auth-google@0.1.3, @mastra/auth-neon@0.3.3, @mastra/auth-okta@0.2.3_
### [@mastra/auth-better-auth@1.1.6](https://github.com/mastra-ai/mastra/blob/@mastra/auth-better-auth@1.1.6/auth/better-auth/CHANGELOG.md)
#### Patch Changes
- Fixed Better Auth so attackers cannot tell whether an account exists or send requests as a signed-in user from another site. Updated better-auth to 1.7.4. (#24027)
### [@mastra/auth-clerk@1.3.0](https://github.com/mastra-ai/mastra/blob/@mastra/auth-clerk@1.3.0/auth/clerk/CHANGELOG.md)
#### Minor Changes
- Added single-organization login restriction to `@mastra/auth-clerk`. Set `organizationId` or `organizationSlug` (or the `CLERK_ORGANIZATION_ID` / `CLERK_ORGANIZATION_SLUG` env vars) to only allow members of one Clerk organization to sign in. Non-members are denied during authorization and SSO callback. (#24188)
```typescript
new MastraAuthClerk({
jwksUri: process.env.CLERK_JWKS_URI,
publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
secretKey: process.env.CLERK_SECRET_KEY,
organizationId: process.env.CLERK_ORGANIZATION_ID,
});
```
#### Patch Changes
- Fixed a stored XSS advisory in Clerk by updating @clerk/backend to 3.17.2. `createClerkClient` and the rest of the Mastra Clerk auth API are unchanged. (#24027)
### [@mastra/azure-ai-search@0.1.0](https://github.com/mastra-ai/mastra/blob/@mastra/azure-ai-search@0.1.0/stores/azure-ai-search/CHANGELOG.md)
#### Minor Changes
- Add `@mastra/azure-ai-search`, a vector store backed by Azure AI Search. Use it anywhere Mastra accepts a vector store, including RAG pipelines and Memory semantic recall. Metadata filtering works out of the box: filterable fields are added to the index the first time a metadata key is written (`autoIndexMetadata`, on by default), so Memory's `thread_id`/`resource_id` filters need no schema setup. Any string is accepted as a vector ID. Hybrid (vector + full-text), semantic, and multi-vector queries are available through `hybridQuery()`, `advancedQuery()`, and `multiVectorQuery()`. (#24103)
```ts
import { AzureAISearchVector } from '@mastra/azure-ai-search';
const store = new AzureAISearchVector({
id: 'azure-search-vectors',
endpoint: process.env.AZURE_AI_SEARCH_ENDPOINT!,
credential: process.env.AZURE_AI_SEARCH_CREDENTIAL!,
});
await store.createIndex({ indexName: 'my-collection', dimension: 1536 });
await store.upsert({ indexName: 'my-collection', vectors: embeddings });
```
### [@mastra/browser-viewer@0.2.4](https://github.com/mastra-ai/mastra/blob/@mastra/browser-viewer@0.2.4/browser/browser-viewer/CHANGELOG.md)
#### Patch Changes
- Remove unused dependency (#23998)
_Also in: @mastra/mcp-docs-server@1.2.27, @mastra/mongodb@1.18.9_
### [@mastra/clickhouse@1.20.0](https://github.com/mastra-ai/mastra/blob/@mastra/clickhouse@1.20.0/stores/clickhouse/CHANGELOG.md)
#### Minor Changes
- Added discovery-specific ClickHouse timeout and memory budgets with stable resource-limit errors. (#24169)
```ts
const store = new ClickhouseStoreVNext({
id: 'clickhouse-storage',
url: 'http://localhost:8123',
username: 'default',
password: 'password',
observability: {
traceQuery: {
discovery: {
timeoutMs: 5_000,
memoryLimitBytes: 256 * 1024 * 1024,
},
},
},
});
```
- Added idempotent TTL updates for existing ClickHouse observability tables through `applyRetention()`. When every observability signal has a retention period, deletion-request records expire after the longest signal retention plus 30 days. If any signal is unbounded, deletion-request records remain unbounded so they continue to prevent deleted data from being reintroduced. (#23466)
```typescript
const observability = new ObservabilityStorageClickhouseVNext({
client,
retention: {
tracing: 30,
logs: 30,
metrics: 30,
scores: 90,
feedback: 90,
},
});
await observability.applyRetention();
```
- Added list-compatible page pagination for advanced trace queries in ClickHouse storage. (#24061)
```ts
const result = await client.queryTraces({
timeRange,
pagination: { page: 0, perPage: 25 },
});
```
- Added ClickHouse support for handing numbered trace-query pages to delta polling, with stable cursor tie-breaking and the existing two-day retention window. (#24329)
Numbered pages remain available without a polling cursor when the installed core version lacks trace-query delta support.
```ts
// Start with a numbered page.
const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
// Continue with delta polling.
const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });
```
- Added bounded trace-query field and value discovery for ClickHouse observability storage. (#24075)
```ts
const observability = await storage.getStore('observability');
const fields = await observability?.getTraceQueryObservedFields(fieldsPlan);
const values = await observability?.getTraceQueryValues(valuesPlan);
```
#### Patch Changes
- Added an index on ClickHouse deletion requests so the check that blocks updates to deleted feedback reads fewer rows instead of scanning every deletion request in the tenant scope. Existing deployments pick up the index automatically on the next start; previously written data is covered as it merges. (#23990)
- Fixed rewritten observability scores so ordinary reads and trace predicates use compact current state and consistently return the latest sequentially written value for each score ID. Overlapping concurrent rewrites of one score ID have an undefined winner. (#24242)
### [@mastra/client-js@1.47.0](https://github.com/mastra-ai/mastra/blob/@mastra/client-js@1.47.0/client-sdks/client-js/CHANGELOG.md)
#### Minor Changes
- Added page-based pagination for advanced trace queries. Paginated responses include `pagination` metadata with `total`, `page`, `perPage`, and `hasMore`. (#24061)
```ts
const result = await client.queryTraces({
timeRange,
pagination: { page: 0, perPage: 25 },
});
```
- Added typed `queryTraceThreads()` methods for querying thread identities across eligible traces. (#23920)
```ts
const result = await mastraClient.queryTraceThreads({
traces: {
timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' },
},
});
```
`queryTraces()` remains trace-only, while `queryTraceThreads()` returns observability-derived thread identities.
- Added Client JS methods for bounded trace-query field and value discovery. Requests can now override client-level retry and abort settings with the per-request `retries` and `signal` options. Retry counts must be non-negative safe integers, and aborted requests stop without retrying. (#24109)
```ts
const fields = await mastraClient.getTraceQueryFields({
timeRange,
predicateScope: 'trace',
});
const values = await mastraClient.getTraceQueryValues({
timeRange,
predicateScope: 'spans',
path: 'model',
});
```
- Added delta polling inputs and response types for queryTraces(), including the cursor returned with numbered pages. (#24329)
```ts
// Start with a numbered page.
const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
// Continue with delta polling.
const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });
```
#### Patch Changes
- Accept A2A v1 PascalCase JSON-RPC method names when the `A2A-Version: 1.0` header is present. Normalize method names before dispatch and streaming response selection while preserving legacy slash-style methods. (#24260)
For example, retrieve an existing task with `GetTask` (replace the agent and task IDs with your own):
```http
POST /api/a2a/my-agent HTTP/1.1
Content-Type: application/json
A2A-Version: 1.0
{"jsonrpc":"2.0","id":"request-1","method":"GetTask","params":{"id":"task-1"}}
```
_Also in: @mastra/server@1.68.0_
- **Added** (#24261)
Added methods to `MastraClient.getA2AV1()` to create, get, list, and delete task push-notification configurations without switching to the v0.3 client. List results include pagination metadata.
For an existing `MastraClient` instance, register a callback for a task:
```ts
const a2a = client.getA2AV1('agent-id');
await a2a.createTaskPushNotificationConfig({
tenant: 'tenant-1',
id: 'config-1',
taskId: 'task-1',
url: 'https://example.com/callback',
token: 'callback-token',
authentication: { scheme: 'Bearer', credentials: 'callback-secret' },
});
```
- # Add pagination support to listThreadMessages (#23669)
The `listThreadMessages` client API now accepts pagination parameters to allow fetching previous chunks of conversation history.
```ts
const messages = await client.listThreadMessages('thread-123', {
page: 1,
perPage: 40,
});
```
- Fixed a jose security advisory by updating jose to 6.2.11. Client JWT helpers are unchanged. (#24027)
- Added expectedRunId support when aborting agent thread runs. (#24452)
- Fixed an uncaught `ERR_INVALID_STATE` error when a consumer cancels an agent stream after its `finish` chunk. (#24303)
Stream cancellation no longer produces an unhandled rejection.
- Improved client request and response types so they stay aligned with Mastra server routes. (#23961)
- Fixed request context query parsing for POST requests. (#23961)
_Also in: @mastra/express@1.5.12, @mastra/fastify@1.5.12, @mastra/hono@1.7.10, @mastra/playground-ui@56.0.0, @mastra/server@1.68.0_
- Fixed `agent.stream()` cancellation in `@mastra/client-js`. Cancelling a returned stream now aborts the underlying HTTP request and stops pending client-tool executions and follow-up requests. Fixes #24271. (#24310)
Added a per-call `abortSignal` option to `stream()`, `streamUntilIdle()`, `resumeStream()`, `resumeStreamUntilIdle()`, `approveToolCall()`, `declineToolCall()`, `streamLegacy()`, `generate()` and `generateLegacy()`. It is merged with the client-wide `abortSignal`, and aborted requests are not retried.
```ts
const controller = new AbortController();
const response = await agent.stream('Hello', { abortSignal: controller.signal });
// later
controller.abort();
```
- `MCPTool.execute()` is typed as the REST route's response (`{ result }` or `{ status: 'suspended', suspendPayload, resumeSchema }`) and accepts `resumeData` and `suspendPayload` so a suspended tool on a 2026-07-28 MCP server can be continued. (#23875)
```ts
const tool = client.getMcpServerTool('returns', 'createReturn');
const first = await tool.execute({ data: { orderId: 'ord_1' } });
if ('status' in first && first.status === 'suspended') {
const done = await tool.execute({
data: { orderId: 'ord_1' },
resumeData: { confirmed: true },
suspendPayload: first.suspendPayload,
});
}
```
- Added an optional `notScorable` field on experiment item score results so clients can tell a skipped run apart from a scorer error. (#24378)
```ts
for (const score of item.scores) {
if (score.notScorable) {
// skipped — score and error are null
} else if (score.error) {
// scorer failed
} else {
// score.score is a number
}
}
```
_Also in: @mastra/server@1.68.0_
- `GetWorkflowRunByIdResponse.serializedStepGraph` is typed as the core `SerializedStepFlowEntry[]`, like `GetWorkflowResponse.stepGraph`, instead of the generated route shape. (#24030)
- Clients can now send the full `generateTitle` configuration with a memory config: `minMessages` (minimum thread messages before a title is generated), `emitEvent` (stream the generated title as a transient `data-thread-title` chunk before `finish`), and an optional `model` (defaults to the agent's model). (#24247)
```ts
const agent = client.getAgent('assistant');
const response = await agent.stream('Plan my trip to Kyoto', {
memory: {
thread: 'thread-1',
resource: 'user-1',
options: {
generateTitle: { emitEvent: true, minMessages: 2 },
},
},
});
await response.processDataStream({
onChunk: async chunk => {
if (chunk.type === 'data-thread-title') {
console.log(chunk.data.threadId, chunk.data.title);
}
},
});
```
- Fix `getA2AV1()` to send PascalCase A2A v1 JSON-RPC method names for message and task operations, enabling interoperability with v1-compliant servers. The v0.3 client is unchanged. (#24262)
- Added \`orderBy\` to \`listDatasets\`, \`listDatasetItems\`, \`listExperiments\`, \`listDatasetExperiments\` and \`listDatasetExperimentResults\`. (#24567)
\`\`\`ts
await client.listDatasets({ orderBy: { field: "updatedAt", direction: "DESC" } });
\`\`\`
### [@mastra/cloudflare-sandbox@0.5.0](https://github.com/mastra-ai/mastra/blob/@mastra/cloudflare-sandbox@0.5.0/workspaces/cloudflare-sandbox/CHANGELOG.md)
#### Minor Changes
- Added bucket mounts to `CloudflareSandbox` so files can survive container sleep. Cloudflare stops an idle container after `sleepAfter`, and the next command starts fresh, so `/workspace` was silently lost between agent turns. The sandbox now implements the Workspace `mounts` hook: any `S3Filesystem` (including Cloudflare R2) listed under `mounts` is mounted through the bridge on `start()`. A slept container boots without its mounts, so the sandbox detects and re-mounts any dropped paths before the next filesystem operation, keeping mounted data durable across sleep. (#24100)
```typescript
const workspace = new Workspace({
sandbox: new CloudflareSandbox({ baseUrl, apiToken }),
mounts: {
'/workspace/data': new S3Filesystem({
bucket: 'agent-data',
region: 'auto',
endpoint,
accessKeyId,
secretAccessKey,
}),
},
});
```
The default instructions now tell the model that `/workspace` is scratch space that does not survive between commands and list the mounted paths to use instead. Fixes #23706.
### [@mastra/code-sdk@1.8.0](https://github.com/mastra-ai/mastra/blob/@mastra/code-sdk@1.8.0/mastracode/sdk/CHANGELOG.md)
#### Minor Changes
- Added native background tool and delegated subagent support to Mastra Code sessions. (#19960)
Set `backgroundTools.enabled` to `true` in Mastra Code settings to make read-only workspace tools and the Alexandria expert background-eligible. Eligible tools remain foreground by default, and agents can opt individual calls into `deferred` or `awaited` execution through the Core `_background.disposition` override.
Mastra Code factory results now expose `backgroundCompletionEvents`, which publishes reconciled `completed`, `failed`, and `cancelled` events for the originating resource and thread:
```ts
const mastraCode = await createMastraCode(options);
const unsubscribe = mastraCode.backgroundCompletionEvents.subscribe(event => {
console.log(event.taskId, event.status);
});
```
- Zero-config MCP OAuth now identifies Mastra Code with its Client ID Metadata Document (`https://code.mastra.ai/.well-known/oauth-client/mastracode.json`) instead of dynamic client registration, which `@mastra/mcp` 2.x no longer performs. Servers whose authorization server accepts URL-based client IDs keep working with a bare `url` entry; servers that require a registered client need `oauth.clientId` in `mcp.json`. (#23876)
```json
{
"mcpServers": {
"notes": {
"url": "https://notes.example.com/mcp"
},
"billing": {
"url": "https://billing.example.com/mcp",
"oauth": {
"clientId": "mastra-code-billing",
"scopes": ["invoices:read"]
}
}
}
}
```
- Added `context.getStorage()` for plugins that start nested controllers. It supplies the host's storage, backend, and vector instances so plugins can avoid opening shared SQLite files through separate native libraries. (#24132)
```ts
const sharedStorage = context.getStorage?.();
if (!sharedStorage) throw new Error('Shared storage requires a newer Mastra Code host.');
const nested = await bootLocalAgentController({ cwd: context.cwd, ...sharedStorage });
```
The host owns these instances. Nested controllers must not close them or run storage maintenance on them.
#### Patch Changes
- Rotate OAuth accounts automatically on eligible request failures. When the active account is rate-limited, quota-exhausted, or fails authentication after one forced token refresh, Mastra Code activates the next account in the pool and retries the request. Server errors and outages exhaust the transient retry budget first and then surface without another account being activated. Every switch appears in the transcript as a one-line notice and is persisted in thread history. (#23711)
Add accounts through the TUI — `/login` on an already-connected provider offers **Add another account**:
```text
/login
→ Add another account # completes OAuth, returns to the manager
→ (submenu) Set as active # optional; rotation happens on demand anyway
```
No configuration is needed beyond having two or more accounts for a provider; rotation walks the provider's accounts in insertion order, starting from the account the pool is currently on.
- Fixed a zod version mismatch that could make tool and workflow schemas built by these packages incompatible with schemas from @mastra/core. (#24428)
_Also in: @mastra/factory@0.16.0_
- Fixed low-priority fire-and-forget signals being reported as failed after they were queued for notification summaries. Summarized reply requests now persist the notification but return a clear error instead of falsely recording a reply obligation, so callers can send a new request at a priority that routes directly. Policy-discarded signals remain retryable. (#23696)
- Fixed Mastra Code exporting its own traces into whichever Mastra project's `.env` it was launched from, and crashing on startup when that project's `MASTRA_PROJECT_ID` was not a valid id. Mastra Code cloud observability now only reads its own environment variables (or the `/observability connect` settings) and ignores `MASTRA_PLATFORM_OBSERVABILITY_ENDPOINT` from the environment. (#24627)
If you configured Mastra Code cloud observability through environment variables, rename them:
```sh
# before
export MASTRA_CLOUD_ACCESS_TOKEN=...
export MASTRA_PROJECT_ID=...
# after
export MASTRACODE_CLOUD_ACCESS_TOKEN=...
export MASTRACODE_PROJECT_ID=...
```
- Added multiple OAuth accounts per provider. Sign in with as many accounts per provider as you like. Running `/login` on an already-connected provider opens an account manager. There you can add another account, switch the active one, re-authenticate, or remove accounts. Added accounts stay inactive until you select one. Accounts carry labels — email for ChatGPT/xAI, GitHub login for Copilot. Credentials keep the same `auth.json` slot format, so existing setups are untouched. (#23709)
Account ids are assigned once, when an account is first registered, and no longer derived from the refresh token — so refreshing a token or re-authenticating an account no longer changes which account it is, and adding an account you already have updates it instead of registering a duplicate for the same subscription. Existing `auth.json` files are read as-is; registered accounts additionally learn their provider's stable account identifier on next load, where the provider exposes one.
Add and select accounts from `/login`:
```text
/login # choose a provider you are already signed in to
Add another account # sign in again; the new account is registered but inactive
Set as active # make an added account the one new requests use
```
- Fixed the goal box appearing twice in the transcript. (#24342)
Starting or resuming a goal drew the goal box locally and the agent also echoed the same reminder back into the live transcript, so the goal was shown twice in a row. The box is now rendered once, from the echoed reminder.
The reminder keeps the goal's attempt budget and judge model, so the box shows both instead of dropping the attempt count. The same reminder is also deduplicated, so a repeated signal cannot render the box a second time.
- Improved cross-agent discovery with live thread titles and passive sender attribution for individually delivered peer signals. (#23696)
Inbound peer signals now expose the stable sender id as `sourcePeerId`, including fire-and-forget messages, while `returnPeerId` is included only when a reply is required. Low-priority signals may first appear as a count-only notification summary and expose their full attribution when opened from the notification inbox:
```xml
<notification sourcePeerId="code-agent:resource-1:thread-1" expectsReply="false">
Peer work completed.
</notification>
```
- Added fallback model packs and pack-specific subscription routing. In `/models`, you can configure a fallback chain and choose the OAuth account each model in a pack uses. A selected account is used **exclusively** for that model: if it fails, the request moves to the pack's fallback chain instead of another account, so a heavy model cannot spend a second subscription's quota. `Automatic` keeps rotating through the provider's accounts in insertion order, starting from the account the pool is currently on, and a fallback pack applies its own routing. Pack hops remain visible in the transcript and persist when you reopen the thread. (#23725)
Configure both from `/models` — select a pack, then:
```
/models
→ Set fallback… # choose the pack to hop to when this pool is exhausted
→ Set subscription routing… # per model: pin one account, or Automatic
```
Custom packs can also define an observational memory model. When set, the OM observer and reflector resolve from the active pack and its fallback chain — so OM keeps working when a pack's provider is down. Packs without an OM model keep using your standalone OM configuration, and explicit `/om` overrides still win.
Both settings live in `settings.json` if you prefer to edit them directly:
```json
{
"customModelPacks": [
{
"name": "Daily",
"models": {
"build": "anthropic/claude-sonnet-4-6",
"memory": "anthropic/claude-haiku-4-5"
}
}
],
"models": {
"packFallbacks": { "custom:Daily": "anthropic" },
"packAccountPreferences": {
"custom:Daily": { "anthropic/claude-sonnet-4-6": "anthropic:a1b2c3d4" }
}
}
}
```
- Fixed cross-agent signals so a peer stays reachable after a session moves to another conversation. A session advertised only its active thread, so peers that had saved an earlier thread could no longer send messages to it after the user started a new thread or switched threads. Every thread a session has loaded now stays claimed, and a wake sent to a saved thread runs on that thread instead of the session's current one. Peer listings no longer show a session's own earlier threads as discoverable agents. (#24510)
### [@mastra/codemod@1.1.3](https://github.com/mastra-ai/mastra/blob/@mastra/codemod@1.1.3/packages/codemod/CHANGELOG.md)
#### Patch Changes
- Fixed codemod previews and result summaries so they appear in the terminal. (#24530)
- Fixed codemods so projects under hidden parent directories are processed while hidden directories inside the target remain excluded. (#24528)
- Fixed v0 RuntimeContext imports from `@mastra/core/di` so they migrate to RequestContext. (#24472)
- Fixed unknown codemod names so they fail clearly before processing files. (#24470)
- Fixed verbose codemod runs so they pass a valid diagnostic level to jscodeshift. (#24251)
### [@mastra/connect@0.2.0](https://github.com/mastra-ai/mastra/blob/@mastra/connect@0.2.0/packages/connect/CHANGELOG.md)
#### Minor Changes
- Added the generated Linear provider with 46 tools for issues, projects, cycles, teams, users, workflow states, comments, attachments, relations, and labels. (#23527)
`connect()` now automatically exposes the Linear toolset when the project has a matching `linear` connection. Set `MASTRA_LINEAR_CONNECTION_ID` or pass `integrations.linear.connectionId` when the project has multiple Linear connections.
- Added automatic discovery of MCP-backed integrations from the Mastra Platform catalog. Connected MCP providers require no checked-in provider registration: `connect()` discovers their tools through Platform, keeps provider credentials outside the application process, and preserves Platform proxy analytics. Discovered MCP tools require tool approval unless the application lists them in `autoApproveTools`. (#23996)
```ts
import { connect } from '@mastra/connect';
const tools = connect({
projectId: process.env.MASTRA_PROJECT_ID,
client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN },
integrations: {
// An MCP-backed integration attached to the project; tools are discovered at runtime.
neon: { autoApproveTools: ['neon_list_projects', 'neon_describe_project'] },
},
});
```
- Added broad Resend and incident.io tool coverage backed by Platform connections. Resend covers email operations and account resources such as domains, templates, audiences, contacts, broadcasts, and webhooks. incident.io covers incident response, alerts, on-call data, teams, users, postmortems, and catalog reads. (#23631)
```ts
import { connect } from '@mastra/connect';
const tools = connect({
projectId: process.env.MASTRA_PROJECT_ID,
client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN },
integrations: {
resend: { allowTools: ['resend_send_email', 'resend_get_email'] },
'incident-io': { allowTools: ['incident_io_list_incidents'] },
},
});
```
- Added Snowflake tools backed by Platform connections using the OAuth snowflake integration. Agents can run SQL statements (with async statement polling and cancellation) and browse warehouses, databases, schemas, tables, columns, views, stages, streams, tasks, roles, and users. The execute-statement tool runs any SQL the connection's Snowflake role permits, so scope the connected user to least privilege or restrict the toolset with allowTools. (#24066)
```ts
import { connect } from '@mastra/connect';
const tools = connect({
projectId: process.env.MASTRA_PROJECT_ID,
client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN },
integrations: {
snowflake: { allowTools: ['snowflake_execute_statement', 'snowflake_list_tables'] },
},
});
```
- Add a generated Jira provider with 37 tools covering issue lifecycle (create, update, transition, link), comments, worklogs, watchers, projects, users, and metadata lookups. The tool runtime now supports the template `updateMetadata` helper by caching derived connection facts (such as the Atlassian cloud ID) in memory for the lifetime of a toolset, so tools skip repeat discovery round-trips. (#24066)
- Added built-in Clerk and WorkOS providers with 42 and 40 tools respectively for identity, organization, directory, connection, invitation, membership, and domain administration. Provider tools can now forward repeated query parameters for multi-value filters. (#23527)
- Added built-in Anthropic, Notion, OpenAI, and Supabase providers. Generated tools can now read safe connection configuration and metadata through the platform proxy, enabling providers with connection-specific API hosts. (#23527)
#### Patch Changes
- Fixed OpenAI image generation tools to send base64 images to models as multimodal image content instead of JSON text, preventing generated images from consuming the text context window. Updated the generated OpenAI tools to the current API parameters. (#23527)
- Removed warnings for providers that do not have project connections. Connect now silently skips unavailable providers while continuing to report actionable connection problems. (#23527)
- Raise the `@mastra/core` peer floor to `>=1.68.0-0` to match `@mastra/mcp` 2.x, which `@mastra/connect` uses for catalog MCP discovery. (#23876)
- Validate `baseUrlOverride` on the connection-proxy client before it is forwarded as a request header. Only absolute HTTPS URLs without embedded credentials are accepted; unparseable values, non-HTTPS schemes, and userinfo-bearing URLs are rejected with `invalid_options` so a compromised connection config cannot redirect authenticated proxy traffic to an unintended origin. (#23527)
### [@mastra/daytona@0.11.1](https://github.com/mastra-ai/mastra/blob/@mastra/daytona@0.11.1/workspaces/daytona/CHANGELOG.md)
#### Patch Changes
- Fixed Daytona sandbox destruction so temporary deletion failures are reported and can be retried. (#24449)
### [@mastra/deployer@1.68.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer@1.68.0/packages/deployer/CHANGELOG.md)
#### Patch Changes
- Fixed deploy builds to stop when a workspace package import cannot be bundled. Add the package as a direct dependency or correct the workspace package configuration. (#24210)
- Fixed builds to reject unresolved subpath imports from externalized workspace packages instead of producing bundles that fail at runtime. (#24450)
- Fixed builds that use dependency subpath imports when the package exposes a nested module package.json. Fixes #12535. (#24137)
- Fixed builds with externals so workspace subpath imports used by other workspace packages are compiled instead of failing at runtime. Fixes #22851. (#24131)
- Fixed build output manifests including dependencies from inactive NODE_ENV branches. (#24385)
- Fixed build dependency resolution so bundled output stays consistent when a project is built from its app directory or from a monorepo root. (#24212)
- Fixed builds with configured externals by preventing the analyzer from loading those dependencies. (#24549)
- Fixed deployment builds to reuse and update the source package-manager lockfile while installing dependencies. (#24226)
- Fixed monorepo builds when packaging scoped workspace dependencies with ESM-only slug generation. (#24272)
### [@mastra/docker@0.9.0](https://github.com/mastra-ai/mastra/blob/@mastra/docker@0.9.0/workspaces/docker/CHANGELOG.md)
#### Minor Changes
- Add a `DockerTemplate` API for preparing reusable, content-addressed baseline images for the local Docker sandbox, with the same builder and repo-template contract as the E2B and platform providers. (#24199)
Prepare an environment once — a base image plus ordered setup commands, env vars, and package installs — then spawn multiple disposable `DockerSandbox`es from it via the new `template` option. Each sandbox is a fresh container with its own writable layer over the shared read-only image, so their filesystems are independent. The baseline is produced by synthesizing a `Dockerfile` and running `docker build`, so setup is baked into reproducible, cached layers.
```typescript
import { DockerSandbox, DockerTemplate, createDockerRepoTemplate } from '@mastra/docker';
const template = new DockerTemplate({ baseImage: 'node:22-slim' })
.aptInstall(['git', 'ca-certificates'])
.runCmd('git clone --depth=1 https://example.com/repo /workspace/app')
.setWorkdir('/workspace/app')
.runCmd('npm ci');
// Builds the image on first start() and reuses it afterwards; the sandbox's
// working directory follows the template's setWorkdir().
const a = new DockerSandbox({ template });
const b = new DockerSandbox({ template });
// Repository checkout pinned to the current head of a branch, rebuilt when it moves.
const sandbox = new DockerSandbox({
template: createDockerRepoTemplate({
getRepositoryAccess: async () => ({ cloneUrl: 'https://github.com/acme/app.git' }),
setupCommand: ['npm ci', 'npm run build'],
}),
});
```
- Immutable, chainable builder methods (`from`, `setWorkdir`, `setEnvs`, `runCmd`, `runWithSecrets`, `aptInstall`, `pipInstall`, `npmInstall`) matching the E2B/platform builders.
- Content-addressed image tag (`mastra-template:<hash>`); `build()` is idempotent and reuses an existing image unless `{ force: true }` is passed. Build failures are returned as `{ status: 'failed' }` and retried on the next attempt.
- `DockerSandbox({ template })` accepts a template or an async template factory resolved once per container-creating `start()`.
- Build-time secrets via `runWithSecrets(command, { secrets, output })`: the step runs in a throwaway build stage forked from the steps before it, secret values are passed by value (`{ secrets }` on the template or `build()`) and delivered through BuildKit secret mounts, and only `output` is copied into the image, so values never land in any layer, history entry, or build-cache metadata, nor in the template identity.
- `createDockerRepoTemplate({ getRepositoryAccess, ref, setupCommand, buildEnv, workingDirectory })` prepares a repository checkout and setup as a template factory, resolving the head of `ref` on each sandbox start and pinning it into the identity. The credential from `getRepositoryAccess` is used only for the head lookup and the clone stage.
- Added AbortSignal cancellation for Docker template builds, repository template resolution, and lazy sandbox starts. Cancelling startup stops local template-preparation streams and sessions, rejects with `SandboxAbortError` while preserving the signal's custom reason as the error cause, and leaves the template retryable. (#24451)
```typescript
const startController = new AbortController();
const start = sandbox.start({ abortSignal: startController.signal });
startController.abort(new Error('request cancelled'));
try {
await start;
} catch (error) {
if (!(error instanceof SandboxAbortError)) throw error;
}
const buildController = new AbortController();
const build = template.build({ abortSignal: buildController.signal });
buildController.abort(new Error('request cancelled'));
try {
await build;
} catch (error) {
if (!(error instanceof SandboxAbortError)) throw error;
}
```
#### Patch Changes
- Fixed Docker sandbox process kills keeping helper response streams open. (#24448)
- **Fixed commands that read stdin hanging until timeout** (#24336)
Commands that read standard input without being given anything to read — a bare `cat`, or `grep`/`rg` with no path argument — blocked until the command timeout expired. The exec no longer attaches stdin unless something will feed it, so these commands see end-of-input and exit immediately.
`processes.spawn()` is unchanged: it still attaches stdin by default so long-running processes can be driven with `sendStdin()`.
### [@mastra/dsql@1.5.0](https://github.com/mastra-ai/mastra/blob/@mastra/dsql@1.5.0/stores/dsql/CHANGELOG.md)
#### Minor Changes
- Added configurable age-based pruning for observability spans. (#23466)
```typescript
import { DSQLStore } from '@mastra/dsql';
const storage = new DSQLStore({
id: 'dsql-storage',
host: 'abc123.dsql.us-east-1.on.aws',
retention: {
observability: {
spans: { maxAge: '30d', batchSize: 1_000 },
},
},
});
await storage.prune({ maxBatches: 10, maxRows: 10_000, pauseMs: 25 });
```
### [@mastra/duckdb@1.10.0](https://github.com/mastra-ai/mastra/blob/@mastra/duckdb@1.10.0/stores/duckdb/CHANGELOG.md)
#### Minor Changes
- Added list-compatible page pagination for advanced trace queries in DuckDB storage. (#24061)
```ts
const result = await client.queryTraces({
timeRange,
pagination: { page: 0, perPage: 25 },
});
```
- Added bounded trace-query field and value discovery for DuckDB observability storage. (#24075)
```ts
const observability = await storage.getStore('observability');
const fields = await observability?.getTraceQueryObservedFields(fieldsPlan);
const values = await observability?.getTraceQueryValues(valuesPlan);
```
- Added configurable age-based pruning for DuckDB observability spans, metrics, logs, scores, and feedback. (#23466)
**Before**
DuckDB observability data was retained until it was deleted explicitly.
**After**
```typescript
const storage = new DuckDBStore({
path: 'mastra.duckdb',
retention: {
observability: {
spans: { maxAge: '30d' },
logs: { maxAge: '7d' },
},
},
});
await storage.prune();
```
- Added DuckDB support for handing numbered trace-query pages to delta polling. The initial page and polling watermark share a snapshot, and polls detect completed root writes. (#24329)
Numbered pages remain available without a polling cursor when the installed core version lacks trace-query delta support.
```ts
// Start with a numbered page.
const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
// Continue with delta polling.
const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });
```
#### Patch Changes
- Fixed repeated score IDs in a single batch so the last accepted observability score remains current. (#24242)
- Returned stable trace-query resource-limit errors when DuckDB reports memory exhaustion during discovery. (#24169)
### [@mastra/e2b@0.12.1](https://github.com/mastra-ai/mastra/blob/@mastra/e2b@0.12.1/workspaces/e2b/CHANGELOG.md)
#### Patch Changes
- **Fixed commands that read stdin hanging until timeout** (#24336)
Commands that read standard input without being given anything to read — a bare `cat`, or `grep`/`rg` with no path argument — blocked until the command timeout expired. Commands run through `executeCommand()` no longer keep stdin open, so these commands see end-of-input and exit immediately.
`processes.spawn()` is unchanged: it still keeps stdin open so long-running processes can be driven with `sendStdin()`.
- Honor the configured `timeout` when reconnecting to or resuming an existing E2B sandbox. Previously `timeout` was only applied on sandbox creation; the reconnect and resume paths called `Sandbox.connect` without `timeoutMs`, so the e2b SDK fell back to its 5-minute default. With the default `lifecycle.onTimeout: 'pause'`, this meant a paused sandbox always came back with a 5-minute window regardless of the configured `timeout`. Both `Sandbox.connect` paths now forward `timeoutMs`, so a resumed sandbox gets at least the configured window. (#24636)
- Commands without an explicit timeout now use the sandbox timeout (5 minutes by default) instead of hitting E2B's 60 second connection deadline and failing with `[deadline_exceeded]`. (#24484)
### [@mastra/editor@0.15.1](https://github.com/mastra-ai/mastra/blob/@mastra/editor@0.15.1/packages/editor/CHANGELOG.md)
#### Patch Changes
- `ComposioToolProvider.listTools()` now rejects with the original SDK error when the Composio catalog request fails (auth, rate limit, network, outage) instead of resolving a successful empty page. An empty result now reliably means the catalog returned no matching tools. (#24292)
- Fix `editor.agent.clearCache()` leaving version-specific stored agents registered with Mastra. Agents hydrated via `versionId`, `versionNumber`, or a status override skip the value cache but were still registered in the runtime registry, so a no-ID clear-all never evicted them. The Editor agent namespace now tracks the stored-agent IDs it registers and evicts any remaining ones during clear-all, while code-defined agents remain registered. (#24000)
- Derive inline workspace identity from a canonical, key-order-independent hash so semantically equivalent configs resolve to the same `inline-<hash>` ID. Previously the ID was hashed from raw `JSON.stringify`, which preserves object insertion order, so reordered-key configs produced different IDs and created duplicate stored workspaces with unstable references. Array order and value differences remain significant. (#24009)
- Fixed conditional processor graphs running their fallback branch alongside a matching rule. A default branch (a condition with no rules) now runs only when no explicit rule matches. The internal pass-through runs only when no rule matches and no default exists. A fallback processor no longer mutates messages or causes side effects when an explicit condition already matched. (#24095)
- Allow the editor to run alongside @mastra/mcp 2.x, whose hydrated servers use the core MCP v2 base. (#23876)
- Fixed hydrating a stored processor graph so that graph nodes reusing the same processor no longer collapse into a single workflow step. Each node now keeps its own unique step identity, preventing configured nodes from overwriting each other and keeping parallel and conditional branch results distinguishable. (#24099)
### [@mastra/express@1.5.12](https://github.com/mastra-ai/mastra/blob/@mastra/express@1.5.12/server-adapters/express/CHANGELOG.md)
#### Patch Changes
- Fixed denial-of-service and CRLF injection advisories by updating @fastify/busboy to 3.2.2. (#24027)
_Also in: @mastra/fastify@1.5.12, @mastra/koa@1.7.12, @mastra/nestjs@0.2.27_
### [@mastra/factory@0.16.0](https://github.com/mastra-ai/mastra/blob/@mastra/factory@0.16.0/mastracode/factory/CHANGELOG.md)
#### Minor Changes
- Brought incident.io follow-up intake to full parity with Linear and Jira. (#24319)
- **Connections**: in-app connect and reconnect for Platform-managed incident.io accounts, runtime discovery of multiple installations, and no more `MASTRA_INCIDENT_IO_CONNECTION_ID`.
- **Auto-ingestion**: observed follow-ups materialize as Work-board cards through configurable event rules (`followUpObserved`, `followUpClosed`), with close events transitioning cards to done/canceled.
- **Agent tools**: board runs get `incidentio_get_follow_up` for reading follow-up details.
- **Board UX**: follow-up cards carry assignee, creator, labels, priority, and incident metadata, plus the same Investigate/Build actions and work-item menu as Linear cards.
- **Routing**: teams choose which Factory and board receive follow-ups; incidents stay unrouted.
```ts
import { IncidentioIntegration } from '@mastra/factory';
const incidentio = new IncidentioIntegration({
apiKey: process.env.INCIDENT_IO_API_KEY!,
// Optionally override the default follow-up rules:
rules: { followUpClosed: null }, // disable automatic close transitions
});
```
- Added an explicit `delivery` choice to Factory worker guidance. (#24138)
Use `delivery: "send"` for immediate guidance or `delivery: "queue"` to wait for the worker’s current run to complete.
- Added GitLab as a Factory source-control and work-intake provider for direct and Platform-managed deployments, with the same session, board, review, and notification behaviour that GitHub repositories get. Both credential modes can back Factory sessions with GitLab repositories. (#24604)
- Intake discovers GitLab projects, ingests issues with labels, label colours, assignees, weight-derived priority, and comment counts, reads discussions, adds comments, updates issue state, and routes selected projects to factories from the Settings UI. GitLab issue and GitHub issue routing are stored separately.
- Version control registers repositories, manages the merge request lifecycle, creates and edits merge request notes, manages diff-anchored review discussions, and adds or removes individual reviewers. Direct tokens and Platform connection credentials both provide authenticated clone and push for repository-backed sessions.
- Webhook ingress verifies `X-Gitlab-Token` and routes supported issue, note, and merge request events into Factory rules. Unsupported events, including push events, are acknowledged without changing board state. Issue and merge request reconcilers cover missed terminal events and settle cards the same way the GitHub reconcilers do.
- Sessions that open a merge request through `source_control_create_change_request` are subscribed to it automatically. New notes, closes, and merges wake the subscribed session as the user who created it, with a notification that links to the merge request. `gitlab_subscribe_mr` and `gitlab_unsubscribe_mr` manage subscriptions by hand, `gitlab_get_issue` fetches a routed issue with its discussion, and `GET /web/gitlab/subscriptions` lists a thread's subscriptions for the UI.
- Review board runs use the `factory-gitlab-review` and `factory-gitlab-rereview` skills, check out the merge request head, and re-review when new commits arrive. The UI names merge requests as `!n`, shows merged and closed states, and offers GitLab in onboarding, repository settings, intake routing, and board empty states.
GitLab approvals are exposed as an approval snapshot and are used for submitted `approve` reviews; submitted `comment` reviews become merge request notes. Individual listed approvals and comment reviews can be fetched through synthetic review IDs. GitLab has no equivalent for mutable pending reviews, request-changes reviews, approval dismissal, team review requests, or synchronous rebase-and-merge, so those operations fail explicitly with a not-supported response.
```ts
import { GitLabIntegration } from '@mastra/factory/integrations/gitlab/integration';
const gitlab = new GitLabIntegration({
baseUrl: 'https://gitlab.example.com',
accessToken: process.env.GITLAB_ACCESS_TOKEN!,
accessTokenType: 'group', // Or 'personal'.
webhookSecret: process.env.GITLAB_WEBHOOK_SECRET,
});
```
Direct mode reads the same values from `GITLAB_ACCESS_TOKEN`, `GITLAB_ACCESS_TOKEN_TYPE`, `GITLAB_BASE_URL`, and `GITLAB_WEBHOOK_SECRET` when constructor options are omitted. Personal and Group Access Tokens are both supported; use `api` and `write_repository` scopes. `GITLAB_BASE_URL` must use HTTPS except for loopback development instances.
When Platform credentials are configured, Factory discovers active GitLab connections for the organization, and `MASTRA_GITLAB_CONNECTION_ID` optionally pins one of them. Requests go through `/v2/connections/{connectionId}/proxy`, and `MASTRA_GITLAB_WEBHOOK_SECRET` verifies webhooks. Explicit direct credentials take precedence. Platform-managed clone and push resolve a fresh repository credential for the selected connection; the connection selector itself is never used as a Git token.
- Added Linear team intake sources so Factory boards can ingest active projectless issues while preserving project precedence for overlapping selections. (#23929)
Select a whole Linear team under Settings, Intake, or send the team source id directly. Team source ids come from `GET /web/linear/teams`; the Platform-backed integration issues opaque ids, the self-managed integration uses `linear-team:<teamId>`.
```ts
await fetch('/web/intake/config', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
linear: { enabled: true, sourceIds: ['linear-team:team-1', 'project-1'] },
}),
});
```
A team source syncs active issues in the triage, backlog, unstarted, and started states, including issues without a project. When a project and its team are both selected, the project selection takes precedence for that project's issues.
Rerouting a Linear issue no longer creates a duplicate card. The Factory that already has the card keeps it, and the card's details stay available there. A new card appears on the newly routed Factory only after the existing card is finished.
- Added a Jira Cloud intake integration for the Software Factory with full Linear-equivalent behavior, supporting both direct credentials and Platform-managed connections. (#20579)
Direct mode: set `JIRA_BASE_URL`, `JIRA_EMAIL`, and `JIRA_API_TOKEN` to use a deployment-global Atlassian API token — no OAuth app setup, intended for self-hosted/single-tenant deployments. Platform mode: with `MASTRA_PLATFORM_ACCESS_TOKEN` or `MASTRA_PLATFORM_SECRET_KEY` configured, Factory automatically discovers visible Platform Jira connections (multiple sites supported) and proxies Jira requests through the Platform integrations service; an explicitly configured `JiraIntegration` takes precedence.
Factory settings and onboarding connect Jira accounts in-app, select Jira projects as intake sources, and route each project to a Factory board. Observed issues on routed projects materialize automatically as work items, closed issues transition their linked card to done or canceled, and both Jira integrations accept `rules` overrides for the `issueObserved` and `issueClosed` events. A background reconciliation worker keeps imported work items fresh (`MASTRACODE_JIRA_RECONCILE_ENABLED`, `MASTRACODE_JIRA_RECONCILE_INTERVAL_MS`). Work cards preserve Jira descriptions, labels, reporters, assignees, priority, project, site, state, and timestamps, appear in the board's teammate filters, and offer the same investigate and build actions as Linear issues. Agents get `jira_get_issue` and `jira_create_comment` tools, including on automated board runs.
#### Patch Changes
- Fixed GitHub and Linear intake selections being personal: which repositories and Linear projects feed a Factory board is now one organization-wide setting, so every member sees the same intake instead of an empty board until they enable the sources themselves. Existing per-member selections are merged into the shared one on the next start (a source stays selected if any member was syncing it), and the Intake settings now carry the Org-wide badge. (#23982)
- Fixed Factory plan handoffs when Auto-approve plans is off. (#24001)
Plan agents now leave work items in Planning until a maintainer approves the plan. Factory does not queue a build before that approval.
Preapproved plans and projects with Auto-approve plans enabled continue to advance automatically. Arming an autonomous run does not approve a plan.
Fixes #23742.
- Fix `prepareRunStart` replaying a dead run binding after abort recovery. When a stage was re-entered following an abort, the replay guard matched the prior pending-start row by kickoff key alone and returned its original (already revoked) binding, so the re-entry re-bound the dead session and never used the freshly minted one. The replay branch now honors a pending-start row only when its binding is still active; a revoked or missing binding is discarded so a fresh binding is minted instead. (#24097)
- Fixed synchronous `onAccepted` hook failures rejecting an already-committed Factory transition and skipping its `stage_moved` audit record. Synchronous throws are now isolated and logged the same way as asynchronous rejections. (#24304)
- Fixed Factory session threads keeping their initial work-item title forever. Those titles are derived from the work item rather than typed by a user, so they no longer opt out of automatic title updates. (#23791)
- Fixed integration feed publishers so an integration that implements `feedPublisher()` without `channels()` now receives work-item comment feed events. Previously the publisher was only wired for integrations that also provided a chat channel, so non-channel feed mirrors (webhooks, issue trackers) were silently never called. (#23999)
- Fixed git credentials appearing in command lines and remote URLs inside session sandboxes. Clone, fetch, push and pull request creation now receive the token through the process environment only. Branch names git would refuse (`a..b`, `x.lock`, a trailing `/`) are now rejected when saved instead of failing later at checkout. (#24604)
- Fixed Platform-managed GitLab connections created with a personal access token not being discovered. Factory now lists connections from every GitLab credential flow the Platform offers, and the documentation no longer presents `MASTRA_GITLAB_CONNECTION_ID` as required; it only pins discovery to one connection. (#24630)
- Platform-connected GitLab deployments now receive issue, note, merge request and push events by polling the Platform event log, so they no longer need a direct project webhook to Factory or a shared `MASTRA_GITLAB_WEBHOOK_SECRET`. Polling is on by default and controlled with `MASTRA_PLATFORM_GITLAB_POLLING_ENABLED` and `MASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS`. (#24631)
A Platform-connected deployment needs no webhook configuration; polling starts with the integration:
```ts
import { PlatformGitLabIntegration } from '@mastra/factory/integrations/platform/gitlab/integration';
// Discovers every active Platform GitLab connection and polls its event log.
const gitlab = new PlatformGitLabIntegration();
// Tune or disable polling per deployment instead of through the environment.
const quiet = new PlatformGitLabIntegration({ pollingIntervalMs: 60_000 });
const webhookOnly = new PlatformGitLabIntegration({ pollingEnabled: false });
```
- Fixed the Factory keeping a review or work run executing after an external terminal transition revoked its binding. Terminal-stage cleanup now aborts the live run on each retired seat (leaving alone the seat that drove its own transition and any successor that took the session over), a resumed session whose binding was revoked on a settled item now surfaces a clear retirement error instead of silently dropping its transition tool, and a settled card no longer retries its close-out to the attempt limit as a false blocked state. (#24005)
- Fixed Factory sign-in button contrast when the app uses a light theme. (#24006)
- Fixed Factory re-entry re-appending the entire skill document on same-stage re-runs. This happened, for example, when a review re-triggered as a pull request was updated. The session now continues with a compact message that references the already-active skill and carries only the fresh context. This avoids re-pasting the full skill body every time and sharply cuts redundant prompt-cache token usage. (#24084)
- Fixed Factory skill dispatch sending a second kickoff into a binding whose previous run is still in flight. A new skill decision for a binding now waits for that binding's live run to end before delivering. This holds across dispatcher replicas: the dispatcher hosting a run records its ownership in the shared open-run ledger and heartbeats it, and a replica that sees a fresh claim from another owner retries later instead of starting a duplicate. A stale record left behind by a crashed run no longer blocks the binding. Delivery outcomes that could not be confirmed now fail with the stable codes `skill_delivery_ambiguous` and `run_terminal_event_missing` instead of `unknown`. (#23968)
- Fixed sessions woken by a pull request comment or close failing with `missing-user-context` or `No usable openai credential`. Woken runs now execute as the user who subscribed, with that organization's credentials available. (#24604)
- Improved Factory PR reviews and re-reviews: (#24626)
- The reviewer writes its own design for the problem before opening the diff, then judges whether the PR's approach and scope are justified — not only whether the implementation works.
- Before every verdict it checks its own requested changes: why each belongs in this PR, what happens if the author follows them exactly, and whether each verification probe could actually detect the failure it claims to rule out.
- Requested changes in the posted review are written as direct, evidence-backed change requests with no optional tiers.
- The reviewer loads bundled per-category review guidance (behavior change, bug fix, public API, schema/storage, security, and others) matched to the change, before opening the diff and again as the change reveals further categories.
### [@mastra/github-signals@0.5.0](https://github.com/mastra-ai/mastra/blob/@mastra/github-signals@0.5.0/signals/github/CHANGELOG.md)
#### Minor Changes
- Added authorization for GitHub App bot comments when the app is owned by the repository organization or by a user with authorized repository access. Explicitly ignored bots and bots whose app ownership cannot be resolved remain denied. (#24246)
```ts
import { GithubSignals } from '@mastra/github-signals';
const githubSignals = new GithubSignals({
authorizedPermissions: ['admin', 'maintain', 'write'],
authorizedBots: ['coderabbitai[bot]', 'devin-ai-integration[bot]'],
});
```
Bots not listed in `authorizedBots` can now trigger notifications when their GitHub App is owned by the repository organization or by a user with one of the configured `authorizedPermissions`.
#### Patch Changes
- Fixed PR syncing failing with a 401 when an expired GitHub token was exported in the environment. Each sync now resolves a fresh credential from the `gh` CLI and uses it when one is available, so a stale `GITHUB_TOKEN` or `GH_TOKEN` is replaced instead of being sent to GitHub. Author permission and GitHub app owner lookups use the same credential. (`8808c0e`)
### [@mastra/hono@1.7.10](https://github.com/mastra-ai/mastra/blob/@mastra/hono@1.7.10/server-adapters/hono/CHANGELOG.md)
#### Patch Changes
- Fixed custom upload routes waiting for body parsing when no fine-grained authorization provider is configured. (#24563)
- Fixed a Hono security advisory (CVE-2026-84363 family) by raising the hono dependency and peer ranges to `^4.13.5`. (#24308)
_Also in: @mastra/mcp@2.0.0, @mastra/next@0.2.26, @mastra/tanstack-start@0.2.26_
### [@mastra/inngest@1.9.0](https://github.com/mastra-ai/mastra/blob/@mastra/inngest@1.9.0/workflows/inngest/CHANGELOG.md)
#### Minor Changes
- Added a `shouldPersistSnapshot` option to `createInngestAgent()` for API symmetry with `createDurableAgent()`. InngestAgent logs a warning and ignores it: Inngest's step memoization and replay own durability, so Mastra snapshots are only persisted for `suspended` runs (human-in-the-loop resume). (#23978)
#### Patch Changes
- Fixed a crash risk in Inngest durable agents where failing to publish an error event to pubsub (for example when the realtime endpoint is unreachable) became an unhandled promise rejection instead of a logged warning. Error reporting is now best-effort: publish failures are caught and logged, matching how abort requests already behave. (#24125)
- Fixed durable agent abort requests being ignored when the run executes on an Inngest worker. Calling abort() on a run in another process now stops generation and ends the stream with finishReason "abort". Fixes #22543. (#24368)
- Fixed processor steps created with the Inngest adapter's createStep losing their saved state between calls. Processor state written in one phase (for example processInput) is now visible in later phases and in chained processor steps, matching the behavior of steps created with @mastra/core/workflows. Fixes #23671. (#24163)
- Fixed completed Inngest workflow runs remaining in memory after resume. (#24267)
- Fixed durable agents on Inngest never running output processors, persisting memory, or generating thread titles at the end of a turn. Finalization was wrapped in a nested Inngest step, which the Inngest protocol does not support — in HTTP/serve deployments the run hung and never emitted its finish event. Finish side effects now run directly inside the existing durable step boundary, matching the built-in durable engine. Fixes #23815 and #22450. (#24125)
### [@mastra/laminar@1.3.19](https://github.com/mastra-ai/mastra/blob/@mastra/laminar@1.3.19/observability/laminar/CHANGELOG.md)
#### Patch Changes
- Exported `MCP_SERVER_REQUEST` spans with `SpanKind.SERVER`. (#24150)
### [@mastra/langfuse@1.5.8](https://github.com/mastra-ai/mastra/blob/@mastra/langfuse@1.5.8/observability/langfuse/CHANGELOG.md)
#### Patch Changes
- Fixed exact OpenRouter generation costs in observability spans and Langfuse exports, including BYOK upstream charges. (#20336)
_Also in: @mastra/observability@1.17.9_
- Fixed the Langfuse exporter sending each span's input and output twice. The payload was written to the observation's input and output fields and then repeated inside its metadata, roughly doubling what Langfuse ingests and stores for workflow, agent and processor spans. Traces look the same as before: the values still appear in the observation's input and output, only the duplicate copy in the metadata is gone. Model generation and tool call spans were never affected. Fixes #23955. (#23956)
### [@mastra/libsql@1.23.1](https://github.com/mastra-ai/mastra/blob/@mastra/libsql@1.23.1/stores/libsql/CHANGELOG.md)
#### Patch Changes
- Fixed unique-index update errors in the Factory storage adapters. Updates now throw `UniqueViolationError`, the same error inserts already threw, so callers can handle a duplicate claim consistently. (#23929)
_Also in: @mastra/pg@1.26.0_
- Added storage-level filtering for `Agent.listSuspendedRuns()` thread lookups. The thread id embedded in suspended run snapshots is now filtered inside SQLite instead of loading every suspended snapshot into the application. Part of https://github.com/mastra-ai/mastra/issues/22627 (#24376)
- Dataset, dataset item, experiment and experiment result listings now honor the `orderBy` option instead of always returning a fixed order. This requires `@mastra/core` 1.68.0 or newer. (#24567)
_Also in: @mastra/mongodb@1.18.9, @mastra/mysql@0.10.0, @mastra/pg@1.26.0, @mastra/spanner@1.8.0_
- Fixed `prune()` to use one cutoff instant across all retained tables so rows near the retention boundary are handled consistently. (#23466)
_Also in: @mastra/pg@1.26.0_
### [@mastra/mcp@2.0.0](https://github.com/mastra-ai/mastra/blob/@mastra/mcp@2.0.0/packages/mcp/CHANGELOG.md)
#### Major Changes
- Rebuilt `@mastra/mcp` on the MCP 2026-07-28 revision. Servers serve that revision only, and every request is self-contained: there is no `initialize` handshake, session header, `ping`, or standalone HTTP+SSE transport. Streamable HTTP responses still stream as Server-Sent Events. Requires `@mastra/core` 1.68 or newer. The full migration guide is at `/reference/migrations/mcp-v2`. (#23876)
**Migration.** A tool that needs input from the caller no longer awaits `context.mcp.elicitation.sendRequest()`. It calls `context.suspend(payload)` and returns; the server answers `input_required`, and when the caller replies the tool runs again with `context.resumeData` and `context.suspendPayload`. On the client, `mcp.elicitation.onRequest()` becomes a per-server `inputRequests` handler.
```diff
execute: async ({ orderId }, context) => {
- const answer = await context.mcp!.elicitation.sendRequest({ message: 'Address?', requestedSchema });
- if (answer.action !== 'accept') return { confirmed: false };
- return { confirmed: await book(orderId, answer.content.address) };
+ if (!context.resumeData) return context.suspend({ phase: 'address' });
+ return { confirmed: await book(orderId, context.resumeData.address) };
},
```
```diff
- mcp.elicitation.onRequest('returns', async params => askUser(params));
+ const mcp = new MCPClient({
+ servers: { returns: { url, inputRequests: async ({ key, params }) => askUser(key, params) } },
+ });
```
```ts
import { createTool } from '@mastra/core/tools';
import { MCPClient, MCPServer } from '@mastra/mcp';
import { z } from 'zod';
constRelated Repositories
public-apis/public-apis★ 491,149
A collective list of free APIs
NousResearch/hermes-agent★ 255,992
The agent that grows with you
yt-dlp/yt-dlp★ 200,099
A feature-rich command-line audio/video downloader
microsoft/markitdown★ 191,501
Python tool for converting files and office documents to Markdown.
Significant-Gravitas/AutoGPT★ 187,704
AutoGPT is the vision of accessible AI for everyone, to use and to build on. Our mission is to provide the tools, so that you can focus on what matters.